SocketRocket的简单封装调用
通过Copilot AI实现了一个健壮的 iOS WebSocket 长连接管理组件,用于 Objective-C 项目中,基于 SocketRocket 封装。其功能和作用总结如下:
主要功能
-
WebSocket 长连接管理
- 封装了 WebSocket 的连接、断开、消息收发等基础操作,简化业务调用。
-
自动心跳检测与丢失处理
- 支持设置心跳包内容和发送间隔,自动定时发送心跳数据。
- 检查心跳响应,支持自定义心跳判定与回复逻辑。
- 检测多次心跳丢失后自动断开并尝试重连,保障长连存活率。
-
断网检测与自动重连
- 集成系统 SCNetworkReachability,实时感知设备网络状态。
- 断网时自动断开 WebSocket,不会在无网络时盲目重连。
- 网络恢复后自动发起重连,保证服务可靠性。
-
可配置的重连策略
- 支持最大重连次数、重连间隔自定义,防止无限重连。
- 断线、心跳丢失、网络波动等场景下,均能自动恢复。
-
回调通知机制
- 通过 delegate 协议通知连接、断开、重连、心跳丢失、消息接收等状态,便于 UI 和业务响应。
-
简单灵活的数据收发接口
- sendData:completion: 支持直接发送 NSData 或 NSString。
- 不耦合分片、多路复用等业务协议,可灵活由上层自行扩展。
- 支持异步回调,方便上层获知消息发送结果。
代码作用与适用场景
- 适用于需要稳定双向通信的 iOS App,如 IM、实时数据推送、游戏等。
- 保证在弱网、断网等复杂网络环境下自动管理连接,提升用户体验。
- 业务开发者无需关心底层连接与重连细节,只需调用简单接口即可高效完成消息收发。
- 支持灵活拓展,业务可按需实现多路复用、分片等高级协议,无需修改底层管理器。
结构说明
- SRWebSocketManager
- 核心管理器,负责 WebSocket 生命周期和心跳、重连、数据发送。
- SRWebSocketConfig
- 配置对象,集中管理心跳、重连等参数。
- SRWebSocketDelegate
- 回调协议,通知各种连接和消息事件。
- 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);
}];
未经作者授权,禁止转载
本文来自博客园,作者:CoderWGB,转载请注明原文链接:https://www.cnblogs.com/wgb1234/articles/18960445
THE END

浙公网安备 33010602011771号