IM开发需要面对的技术问题
网络通讯技术选择
IM主流网络通讯技术有两种:
- 基于TCP的长连接;
- 基于HTTP短连接PULL的方式。
后者常见于WEB IM系统(当然现在很多WEB IM都是基于WebSocket实现),它的优点是实现简单,方便开发上手,问题是流量大,服务器负载较大,消息及时性无法很好地保证,对大规模的用户量支持不够,比较适合小型的IM系统,如小网站的客户系统。
基于TCP长连接则能够更好地支持大批量用户,问题是客户端和服务器的实现比较复杂。当然也还有一些变种,如下行使用MQTT进行服务器通知/消息的下发,上行使用HTTP短连接进行指令和消息的上传。这种方式能够保证下行消息/指令的及时性,但是在弱网络下上行慢的问题还是比较严重。早期的来往就是基于这种方式。
协议的设计制定
IM协议选择原则一般是:易于拓展,方便覆盖各种业务逻辑,同时又比较节约流量。后一点的需求在移动端IM上尤其重要。常见的协议有:XMPP、SIP、MQTT、私有协议。
1.XMPP
- **优点:**协议开源,可拓展性强,在各个端(包括服务器)有各种语言的实现,开发者接入方便;
- **缺点:**缺点也是不少,XML表现力弱、有太多冗余信息、流量大,实际使用时有大量天坑。
2.SIP
- SIP协议多用于VOIP相关的模块,是一种文本协议,流量不小。
3.MQTT
- ** 优点:**协议简单,流量少;
- ** 缺点:**它并不是一个专门为IM设计的协议,多使用于推送。
4.私有协议
市面上几乎所有主流IM APP都是是使用私有协议,一个被良好设计的私有协议优点非常明显。
- **优点:**高效,节约流量(一般使用二进制协议),安全性高,难以破解;
- **缺点:**在开发初期没有现有样列可以参考,对于设计者的要求比较高。
5.结论
- 一个好的协议需要满足如下条件:高效,简洁,可读性好,节约流量,易于拓展。
该如何设计私有通信协议?
1.协议格式设计
如TCP的应用层协议,IM协议也分为包头和包体。包头一般用于表示每个请求/反馈的公共部分,如包长,请求类型,返回码等。 而包体则填充不同请求/反馈对应的信息。
一个最简单的包头可以定义为:
struct` `PackHeader
{
``int32_t length_; ``//包长度
``int32_t serial_; ``//包序列号
``int32_t command_; ``//包请求类型
``int32_t code_; ``//返回码
};
以心跳包为例,假设当前的serial为1,心跳包的command为10,那么使用MessagePack做序列化时:length=4,serial=1,command=10,code=0,每个字段各占一个字节,包体为空,仅需要4个字节。
当然这是最简单的一个例子,面对真正的业务逻辑时,包体里面会需要塞入更多地信息,这个需要开发根据自己的业务逻辑总结公共部分,如为了兼容加入的协议版本号,为了负载均衡加入的模块id等。
2.序列化与反序列化
移动互联网相对于有线网络最大特点是:带宽低,延迟高,丢包率高和稳定性差,流量费用高。在私有协议的序列化上一般使用二进制协议,而不是文本协议。
常见的二进制序列化库有protobuf和MessagePack,当然你也可以自己实现自己的二进制协议序列化和反序列的过程。但是无论是可拓展性还是可读性可能都不如前面两者,所以大部分情况下还是不推荐自己去实现二进制协议的序列化和反序列化过程。
其他不可忽视的问题
1.协议加密
为了保证协议不容易被破解,市面上几乎所有主流IM都会对协议进行加密传输。常见的流程和HTTPS加密相似:建立连接后,客户端和服务器进行进行协商,最终客户端获得一个当前Sessino的秘钥,后续的数据传输都通过这个秘钥进行加解密。一般出于效率的考虑都会采用流式加密,如RC4。而前期协商过程则推荐使用RSA等非对称加密以增加破解难度。
2.快速连接/掉线重连
常见优化思路如下:
- 本地缓存服务器IP并定期刷新。
- 合并部分请求。如加密和登录操作可以合并为同一个操作,这样就可以减少一次不必要的网络请求来回的时间;
- 简化登录后的同步请求,部分同步请求可以推迟到UI操作时进行,如群成员信息刷新。
3.连接保持检测/心跳机制
一般APP实现连接保持的方式无非是采用应用层的心跳,通过心跳包的超时和其他条件(网络切换)来执行重连操作。那么问题来了:为什么要使用应用层心跳和如何设计应用层心跳。众所周知TCP协议是有KEEPALIVE这个设置选项,设置为KEEPALIVE后,客户端每隔N秒(默认是7200s)会向服务器发送一个心跳包。
但实际设计实现中我们更多的是使用应用层心跳。原因如下:
- KEEPALIVE对服务器负载压力比较大;
- socks代理对KEEPALIVE并不支持;
- 部分复杂情况下KEEPALIVE会失效,如路由器挂掉,网线直接被拔除。
在实际操作时为了节约流量和电量一般会在心跳包上做一些小优化:
- 精简心跳包,保证一个心跳包大小在10字节之内;
- 心跳包只在空闲时发送;
- 根据APP前后台状态调整心跳包间隔 (主要是安卓)。
4.消息可达/QoS机制
在移动网络下,丢包,网络重连等情况非常之多,为了保证消息的可达,一般需要做消息回执和重发机制。参考易信,每条消息会最多会有3次重发,超时时间为15秒,同时在发送之前会检测当前连接状态,如果当前连接并没有正确建立,缓存消息且定时检查(每隔2秒检查一次,检查15次)。所以一条消息在最差的情况下会有2分钟左右的重试时间,以保证消息的可达。
因为重发的存在,接受端偶尔会收到重复消息,这种情况下就需要接收端进行去重。通用的做法是每条消息都戴上自己唯一的message id(一般是uuid)。
5.文件上传优化
IM消息(包括SNS模块)内包含大量的文件上传的需求,如何优化文件的上传就成了一个比较大的主题
常见优化思路:
- 将上传流程提前:音频提供边录边传。朋友圈的图片进行预上传,选择图片后用户一般会进行文本输入,在这段时间内后台就可以默默将选好的图片进行上传;
- 提供闪电上传的方式:服务器根据MD5进行文件去重;
- 优化和上传服务器的连接(参考快速连接),提供连接重用的功能;
- 文件分块上传:因为移动网络丢包严重,将文件分块上传可以使得一个分组包含合理数量的TCP包,使得重试概率下降,重试代价变小,更容易上传到服务器;
- 在分包的前提下支持上传的pipeline,避免不必要的网络等待时间;
- 支持断点续传。