前段时间项目里重写了长连接,自己就学习了一番,并根据大佬的教学来做个小分享吧
背景
传统的APP开发中通常都是通过HTTPS请求来进行和服务端的通信的,但是对于一些复杂的业务,是需要服务端来通知客户端的,也就是全双工的通信。
其本质是通过WebSocket来建立一个长连接,然后通过长连接来进行全双工通信,这样不仅解决了客户端轮询通信的问题,也降低了宽带。
本质
长连接的本质呢,其实就是通过WebSocket来连接服务器的ip和端口号来建立连接,我们这里就不关心那些socket的字段定义之类的问题了,而为了实现我们自己项目里的长连接呢,我们需要自定义每次传输的协议。
HEADER格式
通常我们将发送的自定义数据的格式定义为Header和Body,通常情况下,定义Header的格式通常有三种。
1.定长
固定长度的Header主要将每个位置都标记成不同功能的代表,比如我们将Header定义成18byte,每个字节都带表不同的含义,比如:
第一个 byte 代表 消息版本
第二个 byte 代表 消息类型(ping pong,空消息,握手,auth 等阶段)
第三个 byte 代表 用户信息
第四个 byte 代表 body长度
如果考虑扩展的话我们还可以增加一个字段留给以后扩展
显而易见,定长的header的方式不方便后期扩展,对版本升级等不太友好
2.预留字段变长
为了方便后期扩展,方便后期增加不同字段,我们可以在Head的固定位增加一个字节来代表扩展的字段长度,这样后期解析的时候,我们就可以增加新的功能了
3.特殊字节变长
参考HTTP的格式,我们也可以在HEAD结尾处加一个特殊字符,来代表HEAD读取完毕,接下来可以读取body数据。
可以看出变长的HEAD对后期维护更方便一些。
大小端
iOS里数据是小段模式
// 0x 是十六进制
int num = 0x12345678; // 十进制为305419896
char a = num & 0xff; // 取(0 ~ 7位)一个子节
char b = num >> 8 & 0xff; // 取(8 ~15位)一个子节
char c = num >> 16 & 0xff; // 取(16~23位)一个子节
char d = num >> 24 & 0xff; // 取(24~31位)一个子节
printf("%x, %x, %x, %x", a, b, c, d); // 小端模式将输出78,56,34,12
但后端都是大端,所以在获取后端数据的时候,解析header的时候,需要注意大小端问题
安全
我们知道HTTPS是在HTTP的基础上添加了SSL协议,那么为了长连接的安全,防止被运营商抓包,或被第三方劫持,我们也要给自己的长连接加上一层安全协议,一般都是加SSL或TLS协议。
在握手阶段完成后,需要建立安全连接,其实主要就是交换对称加密的密钥,其大致流程如下:
Client Service
生成cPublicKey,cPriKey ----send cPublicKey---> 生成sPublicKey,sPriKey
校验签名(sPublicKey) <----send sPublicKey & 签名 ----- |
生成key 生成key
| ------ auth ----------> |
| <----- authAck & info ------- 生成info
保存info <----- 收发使用 info -------> 保存info
主要的安全流程就是通过上述的流程来进行的,先通过非对称加密来进行签名,最终来达到交换对称加密的key的目的,验证过后就可以交换业务信息了。
Body
body主要用来定义我们要发送的model,一般我们会采用JSON或者某些特定格式的三方库来完成model的解析,我们项目里用的就是 Protobuf 三方库,用起来还算方便。
异常重连
既然我们建立了长连接那么就要维护好这个长连接,避免时不时的就断开,而长连接的重连机制我们主要监控三方面来完成。
1.前后台切换
2.网络抖动
3.NAT超时
心跳机制
大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 NAT 表中的对应项,造成链路中断。
为了避免长连接断连,长连接心跳间隔必须要小于NAT超时时间(aging-time),如果超过aging-time不做心跳,TCP长连接链路就会中断,Server就无法发送Push给手机,只能等到客户端下次心跳失败后,重建连接才能取到消息。
而所谓的心跳机制,其实就是发送一个ping,让后后端给一个pong,看下现在链路是否正常连接。
客户端实现上其实就是一个timer,每个一段时间发送一次心跳。
为了避免资源浪费,对于心跳机制还有多种算法可供选择。
常见的两种处理机制是:
1.顺延:在接收到新的消息或者应答包的时候,如果还没有发送ping包,就将这个ping包在这个时间的基础上顺延一定时间。
2.渐进式:从刚开始每秒发送一次,如果正常收到的话就将事件放大一点点,通过监控通讯来控制ping的频率。
其实方法2和网络拥塞的快重传机制比较相似,但不管是哪种算法,其最终目的都是为了节省资源,总而减轻服务器压力。
总结
其实长连接就是在websocket的基础上增加了自己定义的一些传输协议,我们常遇到的问题也就大概上面这些,需要对网络的东西多多了解才能更好的封装。
Tip
AFNetworking的HTTPShouldUsePipelining这个属性是干啥的?
如果设置为yes的话,对于同一个tcp连接,可以多次放松请求,这样就需要taskid来标示每条TCP的任务的先后顺序,但这个服务器的要求太高了,现在还很少能做到,所以客户端请求的时候一般不会打开。
之所以对服务器要求高,是因为对一个TCP连接而言,如果同时收到多个请求,如果某个任务处理完了,服务端很难确定这个任务是哪个请求的回调,目前很少有无服务会标示,且标示的难度很大