SocketRocket的简单封装调用

通过Copilot AI实现了一个健壮的 iOS WebSocket 长连接管理组件,用于 Objective-C 项目中,基于 SocketRocket 封装。其功能和作用总结如下:


主要功能

  1. WebSocket 长连接管理

    • 封装了 WebSocket 的连接、断开、消息收发等基础操作,简化业务调用。
  2. 自动心跳检测与丢失处理

    • 支持设置心跳包内容和发送间隔,自动定时发送心跳数据。
    • 检查心跳响应,支持自定义心跳判定与回复逻辑。
    • 检测多次心跳丢失后自动断开并尝试重连,保障长连存活率。
  3. 断网检测与自动重连

    • 集成系统 SCNetworkReachability,实时感知设备网络状态。
    • 断网时自动断开 WebSocket,不会在无网络时盲目重连。
    • 网络恢复后自动发起重连,保证服务可靠性。
  4. 可配置的重连策略

    • 支持最大重连次数、重连间隔自定义,防止无限重连。
    • 断线、心跳丢失、网络波动等场景下,均能自动恢复。
  5. 回调通知机制

    • 通过 delegate 协议通知连接、断开、重连、心跳丢失、消息接收等状态,便于 UI 和业务响应。
  6. 简单灵活的数据收发接口

    • sendData:completion: 支持直接发送 NSData 或 NSString。
    • 不耦合分片、多路复用等业务协议,可灵活由上层自行扩展。
    • 支持异步回调,方便上层获知消息发送结果。

代码作用与适用场景

  • 适用于需要稳定双向通信的 iOS App,如 IM、实时数据推送、游戏等。
  • 保证在弱网、断网等复杂网络环境下自动管理连接,提升用户体验。
  • 业务开发者无需关心底层连接与重连细节,只需调用简单接口即可高效完成消息收发。
  • 支持灵活拓展,业务可按需实现多路复用、分片等高级协议,无需修改底层管理器。

结构说明

  1. SRWebSocketManager
    • 核心管理器,负责 WebSocket 生命周期和心跳、重连、数据发送。
  2. SRWebSocketConfig
    • 配置对象,集中管理心跳、重连等参数。
  3. SRWebSocketDelegate
    • 回调协议,通知各种连接和消息事件。
  4. SRReachabilityHelper
    • 网络状态监听工具,基于系统 SCNetworkReachability 实现,保障只在有网时重连。

总结

本组件为 iOS App 提供了一个高可用、易扩展的 WebSocket 长连接管理基础设施,适合各类需要稳定长连接的业务场景。
开发者只需关注业务消息格式和处理逻辑,底层连接、心跳、重连、断网重连等由组件自动处理,大幅降低接入和维护成本。

代码如下:

SRWebSocketConfig

#import <Foundation/Foundation.h>

/// WebSocket连接配置对象,包含心跳、重连等参数
@interface SRWebSocketConfig : NSObject

@property (nonatomic, strong) NSURL *url;                                  ///< ws/wss 地址
@property (nonatomic, assign) NSTimeInterval heartbeatInterval;            ///< 心跳间隔(s)
@property (nonatomic, strong, nullable) NSData *customHeartbeatData;       ///< 自定义心跳包
@property (nonatomic, assign) NSUInteger heartbeatMissLimit;               ///< 允许最大心跳丢失次数
@property (nonatomic, assign) NSUInteger maxRetryCount;                    ///< 最大重连次数
@property (nonatomic, assign) NSTimeInterval retryInterval;                ///< 重连间隔(s)
@property (nonatomic, copy, nullable) BOOL(^heartbeatResponseValidator)(NSData *data); ///< 心跳响应判定
@property (nonatomic, copy, nullable) NSData* _Nullable(^heartbeatReplyGenerator)(NSData *receiveData); ///< 心跳检测包回复

@end

#import "SRWebSocketConfig.h"

@implementation SRWebSocketConfig

@end

SRWebSocketDelegate

#import <Foundation/Foundation.h>

/// WebSocket状态与数据回调协议
@protocol SRWebSocketDelegate <NSObject>
@optional
- (void)webSocketDidOpen;                                                              ///< 连接已建立
- (void)webSocketDidCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; ///< 连接关闭
- (void)webSocketDidFailWithError:(NSError *)error;                                    ///< 连接出错
- (void)webSocketDidReceiveData:(NSData *)data;                                        ///< 收到业务数据
- (void)webSocketDidMissHeartbeat;                                                     ///< 心跳丢失
- (void)webSocketWillReconnectWithRetryCount:(NSUInteger)retryCount;                   ///< 即将重连
- (void)webSocketDidReconnect;                                                         ///< 重连成功
- (void)webSocketReconnectFailed;                                                      ///< 连续重连失败
@end

SRReachabilityHelper

#import <Foundation/Foundation.h>

/// 系统SCNetworkReachability网络检测辅助类
@interface SRReachabilityHelper : NSObject

@property (nonatomic, assign, readonly) BOOL networkReachable; ///< 当前是否有可用网络

+ (instancetype)sharedHelper;

/// 启动网络监听,网络变化时触发block,回调在主线程
- (void)startMonitorWithStatusChangeBlock:(void(^)(BOOL reachable))block;

/// 停止网络监听
- (void)stopMonitor;

@end

#import "SRReachabilityHelper.h"
#import <SystemConfiguration/SystemConfiguration.h>
#import <netinet/in.h>

@interface SRReachabilityHelper ()
@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef;
@property (nonatomic, copy) void(^statusBlock)(BOOL);
@property (nonatomic, assign, readwrite) BOOL networkReachable;
@end

@implementation SRReachabilityHelper

+ (instancetype)sharedHelper {
    static SRReachabilityHelper *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[SRReachabilityHelper alloc] init];
    });
    return instance;
}

- (void)dealloc {
    [self stopMonitor];
}

- (void)startMonitorWithStatusChangeBlock:(void(^)(BOOL reachable))block {
    [self stopMonitor];
    self.statusBlock = block;
    struct sockaddr_in address;
    memset(&address, 0, sizeof(address));
    address.sin_len = sizeof(address);
    address.sin_family = AF_INET;
    self.reachabilityRef = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&address);
    if (!self.reachabilityRef) return;

    SCNetworkReachabilityContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
    SCNetworkReachabilitySetCallback(self.reachabilityRef, ReachabilityCallback, &context);
    SCNetworkReachabilityScheduleWithRunLoop(self.reachabilityRef, CFRunLoopGetMain(), kCFRunLoopCommonModes);

    // 立即获取一次当前状态
    SCNetworkReachabilityFlags flags;
    BOOL success = SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags);
    [self updateStatusWithFlags:flags notify:success];
}

- (void)stopMonitor {
    if (self.reachabilityRef) {
        SCNetworkReachabilityUnscheduleFromRunLoop(self.reachabilityRef, CFRunLoopGetMain(), kCFRunLoopCommonModes);
        CFRelease(self.reachabilityRef);
        self.reachabilityRef = NULL;
    }
    self.statusBlock = nil;
}

static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) {
    SRReachabilityHelper *helper = (__bridge SRReachabilityHelper *)info;
    [helper updateStatusWithFlags:flags notify:YES];
}

- (void)updateStatusWithFlags:(SCNetworkReachabilityFlags)flags notify:(BOOL)notify {
    BOOL reachable = (flags & kSCNetworkReachabilityFlagsReachable) &&
                    !(flags & kSCNetworkReachabilityFlagsConnectionRequired);
    self.networkReachable = reachable;
    if (notify && self.statusBlock) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.statusBlock(reachable);
        });
    }
}

@end

SRWebSocketManager

#import <Foundation/Foundation.h>
#import "SRWebSocketConfig.h"
#import "SRWebSocketDelegate.h"

/// WebSocket长连接通信管理器
@interface SRWebSocketManager : NSObject

+ (instancetype)sharedManager;

/// 建立WebSocket连接
- (void)connectWithConfig:(SRWebSocketConfig *)config;

/// 主动断开连接
- (void)disconnect;

/// 发送数据
- (void)sendData:(NSData *)data
      completion:(void(^)(BOOL success, NSError * _Nullable error))completion;

/// 设置回调代理
@property (nonatomic, weak) id<SRWebSocketDelegate> delegate;

@end

#import "SRWebSocketManager.h"
#import <SocketRocket/SRWebSocket.h>
#import "SRReachabilityHelper.h"

@interface SRWebSocketManager () <SRWebSocketDelegate>
@property (nonatomic, strong) SRWebSocket *webSocket;
@property (nonatomic, strong) SRWebSocketConfig *config;
@property (nonatomic, strong) NSTimer *heartbeatTimer;
@property (nonatomic, assign) BOOL isConnected;
@property (nonatomic, assign) NSUInteger missHeartbeatCount;
@property (nonatomic, strong) NSDate *lastHeartbeatResponseDate;
@property (nonatomic, assign) NSUInteger currentRetryCount;
@property (nonatomic, assign) BOOL isManuallyClosed;
@property (nonatomic, assign) BOOL shouldReconnectWhenNetworkBack;
@end

@implementation SRWebSocketManager

+ (instancetype)sharedManager {
    static SRWebSocketManager *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[SRWebSocketManager alloc] init];
    });
    return instance;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        __weak typeof(self) weakSelf = self;
        [[SRReachabilityHelper sharedHelper] startMonitorWithStatusChangeBlock:^(BOOL reachable) {
            [weakSelf handleNetworkStatusChange:reachable];
        }];
    }
    return self;
}

#pragma mark - 连接与断开

- (void)connectWithConfig:(SRWebSocketConfig *)config {
    [self disconnect];
    self.config = config;
    self.isManuallyClosed = NO;
    self.currentRetryCount = 0;
    self.missHeartbeatCount = 0;
    self.lastHeartbeatResponseDate = [NSDate date];
    [self tryOpenWebSocket];
}

- (void)disconnect {
    self.isManuallyClosed = YES;
    [self stopHeartbeat];
    if (self.webSocket) {
        self.webSocket.delegate = nil;
        [self.webSocket close];
        self.webSocket = nil;
    }
    self.isConnected = NO;
}

- (void)tryOpenWebSocket {
    if (![SRReachabilityHelper sharedHelper].networkReachable) {
        self.shouldReconnectWhenNetworkBack = YES;
        return;
    }
    self.webSocket = [[SRWebSocket alloc] initWithURL:self.config.url];
    self.webSocket.delegate = self;
    [self.webSocket open];
}

#pragma mark - 心跳

- (void)startHeartbeat {
    [self stopHeartbeat];
    if (!self.config.heartbeatInterval || self.config.heartbeatInterval <= 0) return;
    self.heartbeatTimer = [NSTimer scheduledTimerWithTimeInterval:self.config.heartbeatInterval
                                                          target:self
                                                        selector:@selector(heartbeatTick)
                                                        userInfo:nil
                                                         repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:self.heartbeatTimer forMode:NSRunLoopCommonModes];
}

- (void)stopHeartbeat {
    [self.heartbeatTimer invalidate];
    self.heartbeatTimer = nil;
}

- (void)heartbeatTick {
    if (!self.isConnected) return;
    // 1. 发送心跳
    NSData *heartbeatData = self.config.customHeartbeatData ?: [@"ping" dataUsingEncoding:NSUTF8StringEncoding];
    [self.webSocket send:heartbeatData];

    // 2. 检测心跳响应超时
    NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:self.lastHeartbeatResponseDate];
    NSUInteger missLimit = self.config.heartbeatMissLimit ?: 2;
    if (interval > self.config.heartbeatInterval * missLimit) {
        self.missHeartbeatCount++;
        if (self.missHeartbeatCount >= missLimit) {
            if ([self.delegate respondsToSelector:@selector(webSocketDidMissHeartbeat)]) {
                [self.delegate webSocketDidMissHeartbeat];
            }
            [self stopHeartbeat];
            [self.webSocket close];
            self.isConnected = NO;
            [self tryReconnect];
            return;
        }
    } else {
        self.missHeartbeatCount = 0;
    }
}

#pragma mark - 发送数据

- (void)sendData:(NSData *)data completion:(void(^)(BOOL success, NSError * _Nullable error))completion {
    if (!self.isConnected) {
        if (completion) completion(NO, [NSError errorWithDomain:@"SRWebSocket" code:-1 userInfo:@{NSLocalizedDescriptionKey:@"Not connected"}]);
        return;
    }
    [self.webSocket send:data];
    if (completion) completion(YES, nil);
}

#pragma mark - 重连逻辑

- (void)tryReconnect {
    if (self.isManuallyClosed) return;
    if (![SRReachabilityHelper sharedHelper].networkReachable) {
        self.shouldReconnectWhenNetworkBack = YES;
        return;
    }
    NSUInteger maxRetry = self.config.maxRetryCount ?: NSUIntegerMax;
    if (self.currentRetryCount >= maxRetry) {
        if ([self.delegate respondsToSelector:@selector(webSocketReconnectFailed)]) {
            [self.delegate webSocketReconnectFailed];
        }
        return;
    }
    self.currentRetryCount++;
    if ([self.delegate respondsToSelector:@selector(webSocketWillReconnectWithRetryCount:)]) {
        [self.delegate webSocketWillReconnectWithRetryCount:self.currentRetryCount];
    }
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.config.retryInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (self.isConnected || self.isManuallyClosed || ![SRReachabilityHelper sharedHelper].networkReachable) return;
        [self tryOpenWebSocket];
    });
}

#pragma mark - 网络监听

- (void)handleNetworkStatusChange:(BOOL)reachable {
    if (reachable) {
        if (self.shouldReconnectWhenNetworkBack) {
            self.shouldReconnectWhenNetworkBack = NO;
            [self tryReconnect];
        }
    } else {
        if (self.webSocket) {
            self.shouldReconnectWhenNetworkBack = YES;
            [self disconnect];
        }
    }
}

#pragma mark - SRWebSocketDelegate

- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
    self.isConnected = YES;
    self.currentRetryCount = 0;
    self.missHeartbeatCount = 0;
    self.lastHeartbeatResponseDate = [NSDate date];
    [self startHeartbeat];
    if ([self.delegate respondsToSelector:@selector(webSocketDidOpen)]) {
        [self.delegate webSocketDidOpen];
    }
    if (self.currentRetryCount > 0 && [self.delegate respondsToSelector:@selector(webSocketDidReconnect)]) {
        [self.delegate webSocketDidReconnect];
    }
}

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
    self.isConnected = NO;
    [self stopHeartbeat];
    if ([self.delegate respondsToSelector:@selector(webSocketDidFailWithError:)]) {
        [self.delegate webSocketDidFailWithError:error];
    }
    [self tryReconnect];
}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
    self.isConnected = NO;
    [self stopHeartbeat];
    if ([self.delegate respondsToSelector:@selector(webSocketDidCloseWithCode:reason:wasClean:)]) {
        [self.delegate webSocketDidCloseWithCode:code reason:reason wasClean:wasClean];
    }
    [self tryReconnect];
}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
    NSData *msgData = ([message isKindOfClass:[NSData class]]) ? message : [[NSString stringWithFormat:@"%@", message] dataUsingEncoding:NSUTF8StringEncoding];

    // 1. 判断是否为心跳响应
    BOOL isHeartbeatResp = NO;
    if (self.config.heartbeatResponseValidator) {
        isHeartbeatResp = self.config.heartbeatResponseValidator(msgData);
    } else {
        NSString *msgStr = [[NSString alloc] initWithData:msgData encoding:NSUTF8StringEncoding];
        isHeartbeatResp = [msgStr isEqualToString:@"pong"];
    }
    if (isHeartbeatResp) {
        self.lastHeartbeatResponseDate = [NSDate date];
        self.missHeartbeatCount = 0;
        if (self.config.heartbeatReplyGenerator) {
            NSData *replyData = self.config.heartbeatReplyGenerator(msgData);
            if (replyData) [self.webSocket send:replyData];
        }
        return;
    }

    // 普通消息,直接分发
    if ([self.delegate respondsToSelector:@selector(webSocketDidReceiveData:)]) {
        [self.delegate webSocketDidReceiveData:msgData];
    }
}

@end

调用展示

#import "SRWebSocketManager.h"

@interface MyWebSocketHandler : NSObject <SRWebSocketDelegate>
@end

@implementation MyWebSocketHandler

- (void)webSocketDidOpen {
    NSLog(@"连接已建立");
}

- (void)webSocketDidCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
    NSLog(@"连接关闭 code:%zd reason:%@ clean:%@", code, reason, wasClean ? @"YES" : @"NO");
}

- (void)webSocketDidFailWithError:(NSError *)error {
    NSLog(@"连接失败: %@", error);
}

- (void)webSocketDidReceiveData:(NSData *)data {
    NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"收到数据: %@", msg);
}

- (void)webSocketDidMissHeartbeat {
    NSLog(@"心跳丢失,已断开,准备重连...");
}

- (void)webSocketWillReconnectWithRetryCount:(NSUInteger)retryCount {
    NSLog(@"第%zd次重连...", retryCount);
}

- (void)webSocketDidReconnect {
    NSLog(@"重连成功!");
}

- (void)webSocketReconnectFailed {
    NSLog(@"重连失败,已达最大次数");
}

@end

// 使用示例
SRWebSocketConfig *config = [SRWebSocketConfig new];
config.url = [NSURL URLWithString:@"wss://yourserver/ws"];
config.heartbeatInterval = 10;
config.heartbeatMissLimit = 3;
config.maxRetryCount = 5;
config.retryInterval = 3;
config.heartbeatResponseValidator = ^BOOL(NSData *data) {
    // 业务自定义心跳响应判定
    NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    return [msg containsString:@"HEARTBEAT_OK"];
};
config.heartbeatReplyGenerator = ^NSData *(NSData *receiveData) {
    // 业务自定义心跳回复
    return [@"I_AM_ALIVE" dataUsingEncoding:NSUTF8StringEncoding];
};

SRWebSocketManager *manager = [SRWebSocketManager sharedManager];
manager.delegate = [[MyWebSocketHandler alloc] init];
[manager connectWithConfig:config];

// 发送消息
NSData *data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
[manager sendData:data completion:^(BOOL success, NSError * _Nullable error) {
    NSLog(@"发送状态:%d, error:%@", success, error);
}];
posted @ 2025-07-01 21:15  CoderWGB  阅读(106)  评论(0)    收藏  举报