iOS WebSocket长链接的实现方法

时间:2023-01-12 23:53:26

websocket

websocket 是 html5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 tcp 之上,同 http 一样通过 tcp 来传输数据,但是它和 http 最大不同是:websocket 是一种双向通信协议.

由于项目需要创建一个聊天室,需要通过长链接,和后台保持通讯,进行聊天,并且实时进行热点消息的推送.

目前facebook的socketrocket应该是目前最好的关于socketrocket使用的框架了.而且简单易用.

使用

一般一个项目在启动后的某个时机会启动创建一个长链接,如果需要多个就多次创建.如果只要一个就可以封装为一个单例,全局使用.

可以使用podpod管理库, 在podfile中加入

pod 'socketrocket'

在使用命令行工具cd到当前工程 安装

pod install

导入头文件后即可使用.

为求稳定,我的做法是copy的facebook里socketrocket库到项目里. --> socketrocket地址

1.首先创建一个名为 websocketmanager 的单例类,

?
1
+(instancetype)shared;

2.创建一个枚举,分别表示websocket的链接状态

?
1
2
3
4
5
typedef ns_enum(nsuinteger,websocketconnecttype){
  websocketdefault = 0,  //初始状态,未连接,不需要重新连接
  websocketconnect,    //已连接
  websocketdisconnect  //连接后断开,需要重新连接
};

3.创建连接

?
1
2
//建立长连接
- (void)connectserver;

4.处理连接成功的结果;

?
1
-(void)websocketdidopen:(rmwebsocket *)websocket; //连接成功回调

5.处理连接失败的结果

?
1
- (void)websocket:(srwebsocket *)websocket didfailwitherror:(nserror *)error;//连接失败回调

6.接收消息

?
1
2
///接收消息回调,需要提前和后台约定好消息格式.
- (void)websocket:(srwebsocket *)websocket didreceivemessagewithstring:(nonnull nsstring *)string

7.关闭连接

?
1
- (void)websocket:(srwebsocket *)websocket didclosewithcode:(nsinteger)code reason:(nsstring *)reason wasclean:(bool)wasclean;///关闭连接回调的代理

8.为保持长链接的连接状态,需要定时向后台发送消息,就是俗称的:心跳包.

需要创建一个定时器,固定时间发送消息.

9.链接断开情况处理:

首先判断是否是主动断开,如果是主动断开就不作处理.

如果不是主动断开链接,需要做重新连接的逻辑.

具体代码如下:

websocketmanager.h 中的代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#import
<foundation foundation="" h="">
 
#import "rmwebsocket.h"
typedef ns_enum(nsuinteger,websocketconnecttype){
  websocketdefault = 0, //初始状态,未连接
  websocketconnect,   //已连接
  websocketdisconnect  //连接后断开
};
@class websocketmanager;
@protocol websocketmanagerdelegate
 <nsobject>
 
- (void)websocketmanagerdidreceivemessagewithstring:(nsstring *)string;
@end
ns_assume_nonnull_begin
@interface websocketmanager : nsobject
@property (nonatomic, strong) rmwebsocket *websocket;
@property(nonatomic,weak) id
 <websocketmanagerdelegate nbsp="">
  delegate;
@property (nonatomic, assign)  bool isconnect; //是否连接
@property (nonatomic, assign)  websocketconnecttype connecttype;
+(instancetype)shared;
- (void)connectserver;//建立长连接
- (void)reconnectserver;//重新连接
- (void)rmwebsocketclose;//关闭长连接
- (void)senddatatoserver:(nsstring *)data;//发送数据给服务器
@end
ns_assume_nonnull_end
 </websocketmanagerdelegate>
 </nsobject>
</foundation>

websocketmanager.m 中的代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#import "websocketmanager.h"
@interface websocketmanager ()
<rmwebsocketdelegate>
 
@property (nonatomic, strong) nstimer *heartbeattimer; //心跳定时器
@property (nonatomic, strong) nstimer *networktestingtimer; //没有网络的时候检测网络定时器
@property (nonatomic, assign) nstimeinterval reconnecttime; //重连时间
@property (nonatomic, strong) nsmutablearray *senddataarray; //存储要发送给服务端的数据
@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.senddataarray = [[nsmutablearray alloc] init];
  }
  return self;
}
//建立长连接
- (void)connectserver{
  self.isactivelyclose = no;
  
  self.websocket.delegate = nil;
  [self.websocket close];
  _websocket = nil;
//  self.websocket = [[rmwebsocket alloc] initwithurl:[nsurl urlwithstring:@"https://dev-im-gateway.runxsports.com/ws/token=88888888"]];
  self.websocket = [[rmwebsocket alloc] initwithurl:[nsurl urlwithstring:@"ws://chat.workerman.net:7272"]];
  self.websocket.delegate = self;
  [self.websocket open];
}
- (void)sendping:(id)sender{
  [self.websocket sendping:nil error:null];
}
#pragma mark --------------------------------------------------
#pragma mark - socket delegate
///开始连接
-(void)websocketdidopen:(rmwebsocket *)websocket{
  
  nslog(@"socket 开始连接");
  self.isconnect = yes;
  self.connecttype = websocketconnect;
  [self initheartbeat];///开始心跳
  
}
///连接失败
-(void)websocket:(rmwebsocket *)websocket didfailwitherror:(nserror *)error{
  nslog(@"连接失败");
  self.isconnect = no;
  self.connecttype = websocketdisconnect;
  dlog(@"连接失败,这里可以实现掉线自动重连,要注意以下几点");
  dlog(@"1.判断当前网络环境,如果断网了就不要连了,等待网络到来,在发起重连");
  dlog(@"3.连接次数限制,如果连接失败了,重试10次左右就可以了");
  
  //判断网络环境
  if (afnetworkreachabilitymanager.sharedmanager.networkreachabilitystatus == afnetworkreachabilitystatusnotreachable){ //没有网络
  
    [self nonetworkstarttestingtimer];//开启网络检测定时器
  }else{ //有网络
  
    [self reconnectserver];//连接失败就重连
  }
}
///接收消息
-(void)websocket:(rmwebsocket *)websocket didreceivemessagewithstring:(nsstring *)string{
  
  nslog(@"接收消息---- %@",string);
  if ([self.delegate respondstoselector:@selector(websocketmanagerdidreceivemessagewithstring:)]) {
    [self.delegate websocketmanagerdidreceivemessagewithstring:string];
  }
}
///关闭连接
-(void)websocket:(rmwebsocket *)websocket didclosewithcode:(nsinteger)code reason:(nsstring *)reason wasclean:(bool)wasclean{
  
  self.isconnect = no;
  if(self.isactivelyclose){
    self.connecttype = websocketdefault;
    return;
  }else{
    self.connecttype = websocketdisconnect;
  }
  
  dlog(@"被关闭连接,code:%ld,reason:%@,wasclean:%d",code,reason,wasclean);
  
  [self destoryheartbeat]; //断开连接时销毁心跳
  
  //判断网络环境
  if (afnetworkreachabilitymanager.sharedmanager.networkreachabilitystatus == afnetworkreachabilitystatusnotreachable){ //没有网络
    [self nonetworkstarttestingtimer];//开启网络检测
  }else{ //有网络
    nslog(@"关闭连接");
    _websocket = nil;
    [self reconnectserver];//连接失败就重连
  }
}
///ping
-(void)websocket:(rmwebsocket *)websocket didreceivepong:(nsdata *)pongdata{
  nslog(@"接受pong数据--> %@",pongdata);
}
#pragma mark - nstimer
//初始化心跳
- (void)initheartbeat{
  //心跳没有被关闭
  if(self.heartbeattimer) {
    return;
  }
  [self destoryheartbeat];
  dispatch_main_async_safe(^{
    self.heartbeattimer = [nstimer timerwithtimeinterval:10 target:self selector:@selector(senderheartbeat) userinfo:nil repeats:true];
    [[nsrunloop currentrunloop]addtimer:self.heartbeattimer formode:nsrunloopcommonmodes];
  })
  
}
//重新连接
- (void)reconnectserver{
  if(self.websocket.readystate == rm_open){
    return;
  }
  
  if(self.reconnecttime > 1024){ //重连10次 2^10 = 1024
    self.reconnecttime = 0;
    return;
  }
  
  ws(weakself);
  dispatch_after(dispatch_time(dispatch_time_now, (int64_t)(self.reconnecttime *nsec_per_sec)), dispatch_get_main_queue(), ^{
    
    if(weakself.websocket.readystate == rm_open && weakself.websocket.readystate == rm_connecting) {
      return;
    }
    
    [weakself connectserver];
    //    cthlog(@"正在重连......");
    
    if(weakself.reconnecttime == 0){ //重连时间2的指数级增长
      weakself.reconnecttime = 2;
    }else{
      weakself.reconnecttime *= 2;
    }
  });
  
}
//发送心跳
- (void)senderheartbeat{
  //和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小
  ws(weakself);
  dispatch_main_async_safe(^{
    if(weakself.websocket.readystate == rm_open){
      [weakself sendping:nil];
    }
  });
}
//没有网络的时候开始定时 -- 用于网络检测
- (void)nonetworkstarttestingtimer{
  ws(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 reconnectserver];
  }
}
//取消网络检测
- (void)destorynetworkstarttesting{
  ws(weakself);
  dispatch_main_async_safe(^{
    if(weakself.networktestingtimer)
    {
      [weakself.networktestingtimer invalidate];
      weakself.networktestingtimer = nil;
    }
  });
}
//取消心跳
- (void)destoryheartbeat{
  ws(weakself);
  dispatch_main_async_safe(^{
    if(weakself.heartbeattimer)
    {
      [weakself.heartbeattimer invalidate];
      weakself.heartbeattimer = nil;
    }
  });
}
//关闭长连接
- (void)rmwebsocketclose{
  self.isactivelyclose = yes;
  self.isconnect = no;
  self.connecttype = websocketdefault;
  if(self.websocket)
  {
    [self.websocket close];
    _websocket = nil;
  }
  
  //关闭心跳定时器
  [self destoryheartbeat];
  
  //关闭网络检测定时器
  [self destorynetworkstarttesting];
}
//发送数据给服务器
- (void)senddatatoserver:(nsstring *)data{
  [self.senddataarray addobject:data];
  
  //[_websocket sendstring:data error:null];
  
  //没有网络
  if (afnetworkreachabilitymanager.sharedmanager.networkreachabilitystatus == afnetworkreachabilitystatusnotreachable)
  {
    //开启网络检测定时器
    [self nonetworkstarttestingtimer];
  }
  else //有网络
  {
    if(self.websocket != nil)
    {
      // 只有长连接open开启状态才能调 send 方法,不然会crash
      if(self.websocket.readystate == rm_open)
      {
//        if (self.senddataarray.count > 0)
//        {
//          nsstring *data = self.senddataarray[0];
          [_websocket sendstring:data error:null]; //发送数据
//          [self.senddataarray removeobjectatindex:0];
//
//        }
      }
      else if (self.websocket.readystate == rm_connecting) //正在连接
      {
        dlog(@"正在连接中,重连后会去自动同步数据");
      }
      else if (self.websocket.readystate == rm_closing || self.websocket.readystate == rm_closed) //断开连接
      {
        //调用 reconnectserver 方法重连,连接成功后 继续发送数据
        [self reconnectserver];
      }
    }
    else
    {
      [self connectserver]; //连接服务器
    }
  }
}
@end
</rmwebsocketdelegate>

注意点

我们在发送消息之前,也就是调用  senderheartbeat/ senddatatoserver:方法之前,一定要判断当前scoket是否连接,如果不是连接状态,程序则会crash。

ios手机屏幕息屏或者回主页的时候有可能会造成链接断开,我这边的处理是在回到屏幕的时候,判断状态,如果已经断开,就重新连接.

在 appdelegate 中:

?
1
2
3
4
5
6
- (void)applicationdidbecomeactive:(uiapplication *)application {
  // restart any tasks that were paused (or not yet started) while the application was inactive. if the application was previously in the background, optionally refresh the user interface.
  if ([websocketmanager shared].connecttype == websocketdisconnect) {
    [[websocketmanager shared] connectserver];   
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:http://www.cocoachina.com/ios/20181030/25327.html