iOS websocket(SocketRocket)
项目工程中需要对服务端的一些硬件操作,之后等待服务端回调,思来想去只能使用websocket了。
什么是websocket?
我们先来一下HTTP,为什么有何还要使用websocket呢?
这就要有HTTP协议的局限性决定的,通信只能由客户端发起。
那么我们需要等待服务端回调的时候就出现了问题,我们无法获取服务端的回调信息,我们能做的只有两种方式:
1、按间隔不停的向服务端发送请求,直到收到服务端的结果。
2、HTTP连接始终打开连接
HTTP长连接和短连接区别?
这两种方式归根到底都浪费了资源,于是就有了webSocket,我们看一下HTTP和WebSocket的区别。
webSocket的特点
1、建立在 TCP 协议之上,服务器端的实现比较容易。
2、与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
3、数据格式比较轻量,性能开销小,通信高效。
4、可以发送文本,也可以发送二进制数据。
5、没有同源限制,客户端可以与任意服务器通信。
6、协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
ws://example.com:80/some/path
心跳机制
心跳就是用来查看TCP连接双方是否可用,而TCP的KeepAlive则是保证连接的存在,并不能保证双方的可用性。
心跳ping 有客户端发起,假如在指定时间内未收到回调,那此时就判断此时连接不可用,我们应该主动断开连接,当然服务端也维护一份scoket心跳,在未收到客户端的心跳之后,服务端任务连接失效,也会主动断开。
SocketRocket
SocketRocket 是facebook下对websocket的封装,具体源码就需要在github上看了。
我们直接通过pod 引入就好了:
platform :ios, '9.0'
target 'iosWebSocket' do
pod 'SocketRocket'
pod 'AFNetworking','~>3.1.0'
end
iOS客户端部分的步骤:
1、建立连接
-(void)connectServer{
if(self.webScoket){
return;
}
self.webScoket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:@"ws://127.0.0.1:7272"]];
self.webScoket.delegate = self;
[self.webScoket open];
}
2、建立连接之后我们需要向服务端发送心跳,此时我们需要在SRWebSocketDelegate的delegate中等待成功之后去发送心跳请求。
心跳的方法就不展开了,后面去看源码或者demo吧,其实就是调用SRWebSocket 的 sendPing方法
//已经连接
-(void)webSocketDidOpen:(SRWebSocket *)webSocket{
NSLog(@"已经连接,开启心跳");
self.isConnect = YES;
self.socketStatus = WebSocketStatusConnect;
[self initHeartBeat];//开始心跳
}
3、发送数据
注意:我们在sendData的时候,我们一定要判断webScoket.readyState 是SR_OPEN,此时才能发送数据,否则会crash的。
//发送数据给服务器
-(void)sendDataToServer:(NSString *)data{
[self.sendDataArray addObject:data];
//没有网络
if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){
//开启网络检测定时器
[self noNetWorkStartTesting];
}else{
if (self.webScoket != nil) {
//只有长连接OPEN开启状态才能调用send方法
if (self.webScoket.readyState == SR_OPEN) {
[self.webScoket send:data];
}else if (self.webScoket.readyState == SR_CONNECTING){
//正在连接
NSLog(@"正在连接中,重连后会去自动同步数据");
}else if(self.webScoket.readyState == SR_CLOSING || self.webScoket.readyState == SR_CLOSED){
//调用 reConnectServer 方法重连,连接成功后 继续发送数据
[self reConnectServer];
}
}else{
[self connectServer];//连接服务器
}
}
}
4、服务端的回调
服务端的回调,我们只需要监听didReceiveMessage方法就好了。
自己定义delegate方法,之后在使用的地方实现代理协议,这样我们就能获取到服务端的回调了。
//接收消息
-(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{
NSLog(@"接收消息 ---- %@", message);
if (self.delegate && [self.delegate respondsToSelector:@selector(webSocketDidReceiveMessage:)]) {
[self.delegate webSocketDidReceiveMessage:message];
}
}
5、关闭webSocket
//重新连接
-(void)reConnectServer{
//关闭之前的连接
[self webSocketClose];
//重连10次 2^10 = 1024
if (self.reConnectTime > 1024) {
self.reConnectTime = 0;
return;
}
__weak typeof(self)ws = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (ws.webScoket.readyState == SR_OPEN && ws.webScoket.readyState == SR_CONNECTING) {
return ;
}
[ws connectServer];
NSLog(@"重新连接......");
if (ws.reConnectTime == 0) {//重连时间2的指数级增长
ws.reConnectTime = 2;
}else{
ws.reConnectTime *= 2;
}
});
}
WebSocketManager.h
//
// WebSocketManager.h
// iosWebSocket
//
// Created by yanglele on 2019/5/30.
// Copyright © 2019. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "SRWebSocket.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, WebSocketStatus){
WebSocketStatusDefault = 0, //初始状态,未连接
WebSocketStatusConnect, //已连接
WebSocketStatusDisConnect, //断开连接
};
@protocol WebSocketManagerDelegate<NSObject>
-(void)webSocketDidReceiveMessage:(NSString *)string;
@end
@interface WebSocketManager : NSObject
@property(nonatomic, strong) SRWebSocket *webScoket;
@property(nonatomic, weak) id<WebSocketManagerDelegate> delegate;
@property(nonatomic, assign) BOOL isConnect; //是否连接
@property(nonatomic, assign) WebSocketStatus socketStatus;
+(instancetype)shared;
-(void)connectServer;//建立长连接
-(void)reConnectServer;//重新连接
-(void)webSocketClose;//关闭连接
-(void)sendDataToServer:(NSString *)data; //向服务器发送数据
@end
NS_ASSUME_NONNULL_END
WebSocketManager.m
//
// WebSocketManager.m
// iosWebSocket
//
// Created by yanglele on 2019/5/30.
// Copyright © 2019. All rights reserved.
//
#import "WebSocketManager.h"
#import "AFNetworking.h"
#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
@interface WebSocketManager ()<SRWebSocketDelegate>
@property(nonatomic, strong) NSTimer *headerBeatTimer; //心跳定时器
@property(nonatomic, strong) NSTimer *networkTestingTimer; //没有网络的时候检测定时器
@property(nonatomic, assign) NSTimeInterval reConnectTime; //重连时间
@property(nonatomic, strong) NSMutableArray *sendDataArray; //存储要发送给服务器的数据
@property(nonatomic, assign) BOOL isActiveClose; //用于判断是否主动关闭长连接,如果是主动断开连接,连接失败的代理中,就不用执行 重新连接方法
@end
@implementation WebSocketManager
+(instancetype)shared{
static WebSocketManager *__instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__instance = [[WebSocketManager alloc] init];
});
return __instance;
}
-(instancetype)init{
self = [super init];
if (self) {
self.reConnectTime = 0;
self.isActiveClose = NO;
self.sendDataArray = [[NSMutableArray alloc] init];
}
return self;
}
//建立长连接
-(void)connectServer{
if(self.webScoket){
return;
}
self.webScoket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:@"ws://127.0.0.1:7272"]];
self.webScoket.delegate = self;
[self.webScoket open];
}
-(void)sendPing:(id)sender{
NSLog(@"sendPing heart");
// NSString *heart = @"heart";
NSData *heartData = [[NSData alloc] initWithBase64EncodedString:@"heart" options:NSUTF8StringEncoding];
[self.webScoket sendPing:heartData];
// [self.webScoket sendPing:nil error:NULL];
}
//关闭长连接
-(void)webSocketClose{
self.isActiveClose = YES;
self.isConnect = NO;
self.socketStatus = WebSocketStatusDefault;
if (self.webScoket) {
[self.webScoket close];
self.webScoket = nil;
}
//关闭心跳定时器
[self destoryHeartBeat];
//关闭网络检测定时器
[self destoryNetWorkStartTesting];
}
#pragma mark socket delegate
//已经连接
-(void)webSocketDidOpen:(SRWebSocket *)webSocket{
NSLog(@"已经连接,开启心跳");
self.isConnect = YES;
self.socketStatus = WebSocketStatusConnect;
[self initHeartBeat];//开始心跳
}
//连接失败
-(void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{
NSLog(@"连接失败");
self.isConnect = NO;
self.socketStatus = WebSocketStatusDisConnect;
NSLog(@"连接失败,这里可以实现掉线自动重连,要注意以下几点");
NSLog(@"1.判断当前网络环境,如果断网了就不要连了,等待网络到来,在发起重连");
NSLog(@"2.判断调用层是否需要连接,不需要的时候不k连接,浪费流量");
NSLog(@"3.连接次数限制,如果连接失败了,重试10次左右就可以了");
//判断网络环境
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
//没有网络,开启网络监测定时器
[self noNetWorkStartTesting];//开启网络检测定时器
}else{
[self reConnectServer];//连接失败,重新连接
}
}
//接收消息
-(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{
NSLog(@"接收消息 ---- %@", message);
if (self.delegate && [self.delegate respondsToSelector:@selector(webSocketDidReceiveMessage:)]) {
[self.delegate webSocketDidReceiveMessage:message];
}
}
//关闭连接
-(void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{
self.isConnect = NO;
if (self.isActiveClose) {
self.socketStatus = WebSocketStatusDefault;
return;
}else{
self.socketStatus = WebSocketStatusDisConnect;
}
NSLog(@"被关闭连接,code:%ld,reason:%@,wasClean:%d",code,reason,wasClean);
[self destoryHeartBeat]; //断开时销毁心跳
//判断网络
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
//没有网络,开启网络监测定时器
[self noNetWorkStartTesting];
}else{
//有网络
NSLog(@"关闭网络");
self.webScoket = nil;
[self reConnectServer];
}
}
/**
接受服务端发生Pong消息,我们在建立长连接之后会建立与服务器端的心跳包
心跳包是我们用来告诉服务端:客户端还在线,心跳包是ping消息,于此同时服务端也会返回给我们一个pong消息
*/
-(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongData{
NSLog(@"接受ping 数据 --> %@",pongData);
}
#pragma mark NSTimer
//初始化心跳
-(void)initHeartBeat{
if (self.headerBeatTimer) {
return;
}
[self destoryHeartBeat];
dispatch_main_async_safe(^{
self.headerBeatTimer = [NSTimer timerWithTimeInterval:10 target:self selector:@selector(senderheartBeat) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.headerBeatTimer forMode:NSRunLoopCommonModes];
});
}
//重新连接
-(void)reConnectServer{
//关闭之前的连接
[self webSocketClose];
//重连10次 2^10 = 1024
if (self.reConnectTime > 1024) {
self.reConnectTime = 0;
return;
}
__weak typeof(self)ws = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (ws.webScoket.readyState == SR_OPEN && ws.webScoket.readyState == SR_CONNECTING) {
return ;
}
[ws connectServer];
NSLog(@"重新连接......");
if (ws.reConnectTime == 0) {//重连时间2的指数级增长
ws.reConnectTime = 2;
}else{
ws.reConnectTime *= 2;
}
});
}
//发送心跳
-(void)senderheartBeat{
NSLog(@"senderheartBeat");
//和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小
__weak typeof (self) ws = self;
dispatch_main_async_safe(^{
if (ws.webScoket.readyState == SR_OPEN) {
[ws sendPing:nil];
}else if (ws.webScoket.readyState == SR_CONNECTING){
NSLog(@"正在连接中");
[ws reConnectServer];
}else if (ws.webScoket.readyState == SR_CLOSED || ws.webScoket.readyState == SR_CLOSING){
NSLog(@"断开,重连");
[ws reConnectServer];
}else{
NSLog(@"没网络,发送失败,一旦断网 socket 会被我设置 nil 的");
}
});
}
//取消心跳
-(void)destoryHeartBeat{
__weak typeof(self) ws = self;
dispatch_main_async_safe(^{
if (ws.headerBeatTimer) {
[ws.headerBeatTimer invalidate];
ws.headerBeatTimer = nil;
}
});
}
//没有网络的时候开始定时 -- 用于网络检测
-(void)noNetWorkStartTestingTimer{
__weak typeof(self)ws = self;
dispatch_main_async_safe(^{
ws.networkTestingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(noNetWorkStartTesting) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:ws.networkTestingTimer forMode:NSDefaultRunLoopMode];
});
}
//定时检测网络
-(void)noNetWorkStartTesting{
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable) {
//关闭网络检测定时器
[self destoryNetWorkStartTesting];
//重新连接
[self reConnectServer];
}
}
//取消网络检测
-(void)destoryNetWorkStartTesting{
__weak typeof(self) ws = self;
dispatch_main_async_safe(^{
if (ws.networkTestingTimer) {
[ws.networkTestingTimer invalidate];
ws.networkTestingTimer = nil;
}
});
}
//发送数据给服务器
-(void)sendDataToServer:(NSString *)data{
[self.sendDataArray addObject:data];
//没有网络
if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){
//开启网络检测定时器
[self noNetWorkStartTesting];
}else{
if (self.webScoket != nil) {
//只有长连接OPEN开启状态才能调用send方法
if (self.webScoket.readyState == SR_OPEN) {
[self.webScoket send:data];
}else if (self.webScoket.readyState == SR_CONNECTING){
//正在连接
NSLog(@"正在连接中,重连后会去自动同步数据");
}else if(self.webScoket.readyState == SR_CLOSING || self.webScoket.readyState == SR_CLOSED){
//调用 reConnectServer 方法重连,连接成功后 继续发送数据
[self reConnectServer];
}
}else{
[self connectServer];//连接服务器
}
}
}
@end
websocket服务端
1、安装node
npm install node
2、安装ws模块
npm install ws
3、找到服务端 websocketService 程序,执行
node websocketService.js
至此我们开启了服务端的服务,开始监听客户端的连接。