前言
在新公司入职的两个月时间里学习到了不少新的知识。其中聊天室就是近期在研究公司代码时学习碰到的一个技术点。其实在上一家公司也接触过做即时通讯的需求,但之前由于工期比较紧所以就选择了用环信(一个比较主流的第三方即时通讯sdk)来实现。但其中实现的原理没有深究,学到的东西并不多。因此借着这个机会拜读一下同事的代码,写下这篇学习笔记补充一下即时通讯这一块漏掉的知识。
实现方式
iOS 不用第三方sdk实现即时通讯的主流方法主要有4种:1、基于Scoket原生:代表框架 CocoaAsyncSocket。2、基于WebScoket:代表框架 SocketRocket。3、基于MQTT:代表框架 MQTTKit。4、基于XMPP:代表框架 XMPPFramework。这四种方式都各有利弊。我们公司选择的实现方式是基于WebSocket实现的,因此我先从这个方式入手,其他3种方式待以后探究。
什么是WebSocket?
我们在客户端开发的过程中,相信我们遇到最多的网络协议是HTTP协议。WebSocket 和HTTP 一样是网络协议的一种。那么我们已经有强大的HTTP协议了为什么还需要另外一种网络协议呢?那是因为HTTP协议有一个很大的弊端--通讯只能有客户端发起。客户端发起的request 和服务器下发的respond 是一一对应的。在HTTP协议下如果客户端有连续的状态变化,客户端想要获取就比较麻烦。我们只能依靠轮询机制(每隔一段时间向服务器请求一次,了解服务器最新的数据)来获取。但是轮询不但耗费性能,而且也并非真正意义上的实现即时性。这就导致HTTP 协议并不适合用于即时通讯。而WebSocket就是解决这一问题而发明的。WebSocket借用了HTTP的协议来完成一次握手,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据了。
如何在iOS项目中使用WebSocket
在上面介绍即时通讯的实现方式时提到,WebSocket的代表框架是SocketRocket。团队的项目也是用SocketRocket来实现聊天室功能的。因此我也来顺带了解一下SocketRocket这个框架。SocketRocket是Facebook开源的一个用于 iOS, macOS and tvOS客户端的websocket框架。SocketRocket是对WebSocket的封装。
1、集成
集成SocketRocket方法非常简单,用cocoapods 在podfile中加入 pod 'SocketRocket',执行pod install指令就可以完成集成。
2、实现聊天室功能需要做哪些事情?
那我们在一次建立一个即时聊天的过程中,我们需要做哪些事情呢?①、首先我们需要建立连接 ②、遵守并指定代理 ③、打开连接加载请求 ④、关闭连接 ⑤、发送消息 ⑥、通过代理方法来获取接收到的消息
-(void)SRWebSocketOpen{
//如果是同一个url return
if (self.socket) {
return;
}
self.socket = [[SRWebSocket alloc] initWithURLRequest: [NSURLRequest requestWithURL:[NSURL URLWithString:@"ws:xxxxxxxxxxx"]]];//这里填写你服务器的地址
self.socket.delegate = self; //SRWebSocketDelegate 协议
[self.socket open]; //开始连接
}
①、建立连接 ②、遵守并指定代理 ③、打开连接加载请求
在SRWebSocketOpen方法里,我们先创建一个SRWebSocket对象,并设置了SocketRocket的回调代理。在完成以上两个操作后,调用[_socket Open];开始建立连接。
-(void)SRWebSocketClose{
if (self.socket){
[self.socket close];
self.socket = nil;
//断开连接时销毁心跳
[self destoryHeartBeat];
}
}
④、关闭连接:
在SRWebSocketClose方法里,通过调用[_socket close]; 关闭连接并销毁心跳。等一下,什么是心跳?这部分内容在下一个章节说明,暂时先可以理解为检测连接是否正常的一个机制。
- (void)sendData:(id)data {
[self.socket send:data] ;
}
⑤、发送消息
- (void)sendData:(id)data 方法中通过调用[_socket send:data];方法发送消息。这个data可以是一个UTF8的字符串或者NSData对象。
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
if (webSocket == self.socket) {
NSLog(@"接收到后台下发的信息,在这解析。");
}
}
⑥、通过代理方法来获取接收到的消息
当接收到信息时,会通过- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message代理方法回调。
按照常规,实现以上方法就已经实现了即时聊天的基本功能。但是,在现实的使用过程中,往往会出现一些特殊的情况需要处理。例如:1、在建立了链接后,如何确保客户端和服务端的之间的链接有效可用?2、虽然WebSocket通过握手建立了链接,但是在链接过程中可能遇到因为网络不好等的原因导致的连接中断情况,链接断了如何处理?3、在即时聊天项目中通常还要实现APSN即时推送功能,那服务器如何判断什么时候通过WebSocket发送消息,什么时候走APNs离线推送呢?
解决以上三个问题,就涉及到了三个webSocket的机制
1、心跳机制
用NSTimer每隔固定时间向服务器发一个心跳包,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。
//初始化心跳
- (void)initHeartBeat {
dispatch_main_async_safe(^{
[self destoryHeartBeat]; //心跳设置为3分钟,NAT超时一般为5分钟
heartBeat = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(sentheart) userInfo:nil repeats:YES]; //和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小
[[NSRunLoop currentRunLoop]addTimer:heartBeat forMode:NSRunLoopCommonModes];
})
}
//取消心跳
- (void)destoryHeartBeat {
dispatch_main_async_safe(^{
if (heartBeat) {
if ([heartBeat respondsToSelector:@selector(isValid)]){
if ([heartBeat isValid]){
[heartBeat invalidate];
heartBeat = nil;
}
}
}
})
}
-(void)sentheart{
//发送心跳 和后台可以约定发送什么内容 一般可以调用ping 这里根据后台的要求 发送了data给他
[self sendData:@"heart"];
}
2、重连机制
重连机制比较好理解,值得注意的是当重连到一定次数仍然失败后要提示用户网络存在问题,就没必要再继续重连了。
- (void)reConnect {
[self SRWebSocketClose];
if (reConnectTime > 50) {
// 重连50次都失败
reConnectTime = 0;
// 在这里弹出提示告知用户网络不好
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.socket = nil;
[self SRWebSocketOpen];
NSLog(@"重连");
});
reConnectTime ++;
}
3、pingpong机制
当服务端发出一个Ping,客户端没有在约定的时间内返回响应的ack,则认为客户端已经不在线,这时我们Server端会主动断开Scoket连接,并且改由APNS推送的方式发送消息。
//pingPong
- (void)ping{
if (self.socket.readyState == SR_OPEN) {
[self.socket sendPing:nil];
}
}
//sendPing的时候,如果网络通的话,则会收到回调,但是必须保证ScoketOpen,否则会crash
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload {
NSLog(@"收到pong回调");
}
至此,用SocketRocket建立一个聊天室所需的基本方法大概都列了一下。当然,中间仍有许多代码逻辑需要处理。这些内容会在仔细研读后继续整理出来。
写在最后
这篇文章只是我自己的学习笔记。第一次写简书,有点语无伦次,不好意思。文中的代码内容大多都是参考公司项目及网上的一些简书作者的文章。非常感谢这些博主及公司同事的无私分享。最后附上这些博主的原文。
iOS--SocketRocket框架的使用及测试服务器的搭建