服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - iOS WebSocket长链接的实现方法

iOS WebSocket长链接的实现方法

2021-05-17 16:28suiling IOS

WebSocket是HTML5一种新的协议,它实现了浏览器与服务器全双工通信,本篇文章介绍了iOS WebSocket长链接的使用,有一定的参考价值,感兴趣的小伙伴们可以参考一下

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

延伸 · 阅读

精彩推荐
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28