websocket
WebSocket是通过单个TCP连接提供全双工(双向通信)通信信道的计算机通信协议。用户可以向服务器发送消息并接收事件驱动的响应,而无需轮询服务器。 它可以让多个用户连接到同一个实时服务器,并通过API进行通信并立即获得响应。它于http通信最大的区别是双向通信,可以由服务端主动向客户端发送消息,不需要由客户端发起轮询。
在ios中实现
本文采用目前最为广泛使用的 SocketRocket , 先使用cocoapod引入该库
pod 'SocketRocket'
使用单例的管理类来使用ws,直接贴代码:
WebSocketManager.h
#import <Foundation/Foundation.h>
@class SRWebSocket;
@class WebSocketManager;
typedef NS_ENUM(NSUInteger,WebSocketConnectType){
WebSocketDefault = 0, //初始状态,未连接
WebSocketConnect, //已连接
WebSocketDisconnect //连接后断开
};
@protocol WebSocketManagerDelegate <NSObject>
- (void)webSocketManagerDidReceiveMessageWithString:(NSString *)string;
@end
NS_ASSUME_NONNULL_BEGIN
@interface WebSocketManager : NSObject
@property(nonatomic,weak) id<WebSocketManagerDelegate > delegate;
@property (nonatomic, assign, readonly) WebSocketConnectType connectType;
+(instancetype)shared;
- (void)connectTo:(NSString*)host;
- (void)reConnect;
- (void)disconnect;
- (void)sendStringToServer:(NSString *)string;
-(BOOL) isConnect;
@end
NS_ASSUME_NONNULL_END
WebSocketManager.m
#import "WebSocketManager.h"
#import <SocketRocket/SocketRocket.h>
#import <AFNetworking/AFNetworking.h>
#define WeakSelf __weak typeof(self) weakSelf = self;
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
#endif
@interface WebSocketManager ()<SRWebSocketDelegate>
@property (nonatomic, copy) NSString* host;
@property (nonatomic, strong, readonly) SRWebSocket *webSocket;
@property (nonatomic, assign) WebSocketConnectType connectType;
@property (nonatomic, strong) NSTimer *heartBeatTimer; //心跳定时器
@property (nonatomic, strong) NSTimer *netWorkTestingTimer; //没有网络的时候检测网络定时器
@property (nonatomic, assign) NSTimeInterval reConnectTime; //重连时间
@property (nonatomic, strong) NSMutableArray *sendQueue; //存储要发送给服务端的数据
@property (nonatomic, assign) BOOL isActivelyClose; //用于判断是否主动关闭长连接,如果是主动断开连接,连接失败的代理中,就不用执行 重新连接方法
@end
@implementation WebSocketManager
+(instancetype)shared{
static WebSocketManager *_instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc]init];
});
return _instance;
}
- (instancetype)init
{
self = [super init];
if(self){
self.reConnectTime = 0;
self.isActivelyClose = NO;
self.sendQueue = [[NSMutableArray alloc] init];
}
return self;
}
- (void)connectTo:(NSString *)host
{
self.host = host;
[self connect];
}
-(void) connect
{
self.isActivelyClose = NO;
self.webSocket.delegate = nil;
[self.webSocket close];
_webSocket = nil;
_webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:self.host]];
self.webSocket.delegate = self;
[self.webSocket open];
}
- (void)reConnect
{
if(self.webSocket.readyState == SR_OPEN){
return;
}
if(self.reConnectTime > 1024){
self.reConnectTime = 0;
return;
}
WeakSelf
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if(weakSelf.webSocket.readyState == SR_OPEN && weakSelf.webSocket.readyState == SR_CONNECTING) {
return;
}
[weakSelf connect];
//THLog(@"正在重连......");
if(weakSelf.reConnectTime == 0){ //重连时间2的指数级增长
weakSelf.reConnectTime = 2;
}else{
weakSelf.reConnectTime *= 2;
}
});
}
- (void)disconnect
{
_isActivelyClose = YES;
self.connectType = WebSocketDefault;
if(self.webSocket)
{
[self.webSocket close];
_webSocket = nil;
}
//关闭心跳定时器
[self _destoryHeartBeat];
//关闭网络检测定时器
[self _destoryNetWorkStartTesting];
}
-(void) sendStringToServer:(NSString *)string
{
[self.sendQueue addObject:string];
[self _sendQueue];
}
-(void) _sendQueue
{
if(_sendQueue.count == 0)
{
return;
}
//没有网络
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable)
{
//开启网络检测定时器
[self _noNetWorkStartTestingTimer];
}
else
{
if(self.webSocket != nil)
{
if(self.webSocket.readyState == SR_OPEN)
{
NSString *data = _sendQueue.firstObject;
[_webSocket send:data];
[_sendQueue removeObjectAtIndex:0];
if(_sendQueue.count>0)
{
[self _sendQueue];
}
}
else if (self.webSocket.readyState == SR_CONNECTING) //正在连接
{
//DLog(@"正在连接中,重连后会去自动同步数据");
}
else if (self.webSocket.readyState == SR_CLOSING || self.webSocket.readyState == SR_CLOSED) //断开连接
{
[self reConnect];
}
}
else
{
[self connect];
}
}
}
- (void)sendPing
{
[_webSocket sendPing:nil];
}
#pragma makr - private functions
- (void) _startHeartBeat
{
//心跳没有被关闭
if(self.heartBeatTimer) {
return;
}
[self _destoryHeartBeat];
dispatch_main_async_safe(^{
self.heartBeatTimer = [NSTimer timerWithTimeInterval:10.f target:self selector:@selector(_senderheartBeat) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] addTimer:self.heartBeatTimer forMode:NSRunLoopCommonModes];
});
}
- (void)_destoryHeartBeat{
WeakSelf
dispatch_main_async_safe(^{
if(weakSelf.heartBeatTimer)
{
[weakSelf.heartBeatTimer invalidate];
weakSelf.heartBeatTimer = nil;
}
});
}
- (void)_senderheartBeat{
//和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小
WeakSelf
dispatch_main_async_safe(^{
if(weakSelf.webSocket.readyState == SR_OPEN){
[weakSelf sendPing];
}
});
}
- (void) _noNetWorkStartTestingTimer{
WeakSelf
dispatch_main_async_safe(^{
weakSelf.netWorkTestingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(_noNetWorkStartTesting) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:weakSelf.netWorkTestingTimer forMode:NSDefaultRunLoopMode];
});
}
- (void) _noNetWorkStartTesting{
//有网络
if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable)
{
//关闭网络检测定时器
[self _destoryNetWorkStartTesting];
//开始重连
[self reConnect];
}
}
- (void) _destoryNetWorkStartTesting{
WeakSelf
dispatch_main_async_safe(^{
if(weakSelf.netWorkTestingTimer)
{
[weakSelf.netWorkTestingTimer invalidate];
weakSelf.netWorkTestingTimer = nil;
}
});
}
#pragma mark - SRWebSocketDelegate
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message
{
if ([self.delegate respondsToSelector:@selector(webSocketManagerDidReceiveMessageWithString:)]) {
[self.delegate webSocketManagerDidReceiveMessageWithString:message];
}
}
- (void)webSocketDidOpen:(SRWebSocket *)webSocket
{
NSLog(@"socket 开始连接");
self.connectType = WebSocketConnect;
[self _startHeartBeat];///开始心跳
[self _sendQueue];
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error
{
NSLog(@"连接失败");
self.connectType = WebSocketDisconnect;
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){
[self _noNetWorkStartTestingTimer];
}else{
[self reConnect];
}
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean
{
if(self.isActivelyClose)
{
self.connectType = WebSocketDefault;
return;
}
self.connectType = WebSocketDisconnect;
[self _destoryHeartBeat]; //断开连接时销毁心跳
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){ //没有网络
[self _noNetWorkStartTestingTimer];//开启网络检测
}else{ //有网络
NSLog(@"关闭连接");
_webSocket = nil;
[self reConnect];//连接失败就重连
}
}
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload
{
NSLog(@"接受pong数据--> %@",pongPayload);
}
-(BOOL) isConnect
{
return _connectType == WebSocketConnect;
}
@end
其实代码很简单,在这里要阐述的是ws的ping pong机制,上述代码中当webSocketDidOpen
方法中(当链接建立后)开启了心跳机制,心跳机制的原理简单描述如下:
websocket协议定义了心跳机制:一方可以通过发送ping消息给另一方,
另一方收到ping后应该尽可能快的返回pong。
从上述描述可以看出,ping消息即可以从服务端发起也可以从客户端发起,(协议本身支持,但底层库是否支持另说),从性能考虑建议尽量考虑客户端发起。ping发送的请求还可以带数据,在pong时接受,这样很简单的就可以计算出网络延迟的时间了。
附:
demo源码下载
运行效果:
还没仔细看,先给点个赞👍🏻