IM协议设计
在实际开发中,为了及时的通知APP端一些事情,我们会借助第三方平台,进行推送。今天,我们来分析一下推送系统协议。
推送系统遇到的问题
在设计协议之前,我们考虑一些实际的问题:
APP没有一个固定的网络地址,只能通过主动连接服务器,建立TCP长链接,来进行推送。
移动环境下,APP断网是非常普遍的,即推送任务会失败
在大并发的情况下,服务器宕机也是非常有可能的。
APP可能不会随时连上服务器,而服务器需要保存一些非常重要的消息,等待APP连接上,推送给APP
移动环境下,节省流量也是非常重要的,毕竟不是人人都有4G
网络安全问题,日益严重,我们需要安全的推送
协议的兼容性
由于GSM网关,所以需要发送心跳包,避免NET转换被剔除
如何解决
可以发现,一个好的推送协议设计,还是非常困难的。总结下来,我们遇到的问题大致如下:
节约流量:采用protobuf这种二进制协议进行推送,但是也可以考虑JSON + UTF-8的方式
推送失败处理:无论服务器还是APP,都采用日志先行的策略,如果发送失败,则进行RETRY,直到到达推送极限次数后,报告错误。
安全性:采用SSL/TLS连接,基本上能解决安全链接的问题,如果条件比较拮据,可以把证书放在APP端。
协议兼容:可以参考APP兼容性设计中的协议兼容部分。
心跳包:一般来说,因为移动网络和互联网之间存在一个NET网关,它的有效时间为6分钟左右,所以APP端需要定时的发送心跳包到服务器,一般间隔时间为3-5分钟。同时,通过心跳包,服务器可以发现已经死掉的链接,及时的释放资源。
RETRY机制
在上述问题中,其他问题都比较好解决,但是消息重发机制,还是比较难以实现的。 这几介绍一下RETRY机制的设计:
加入UUID作为该消息的唯一ID,且在服务器端保留最后的100条该用户的消息,当APP端把消息推送到服务器的时候,服务器先去重,避免接受到相同的消息进行推送。
当APP连接上网络后,首先检测待发送的消息列表中是否为空,如果不为空,则根据FIFO来提取待发送的消息。直到服务器返回OK后,才把本条消息从待发送队列中删除。
发送失败的延迟推送算法,建议采用TCP的重发时间机制,也就是指数退避算法。
协议具体设计
这里使用JSON描述
{
MODULE:PUSH
VERSION:1 //版本号
ID: UUID //消息UUID
TYPE: GROUP | PRIVATE | SYSTEM //消息会话类型
TARGET_ID: STRING //消息要发送到的目标ID,如果是GROUP,则是群的ID
TIME:LONG //消息发送时间
USER_ID : STRING //发送该该消息的用户ID
CONTENT:{
TYPE : STRING -> TEXT | IMAGE //消息的内容类型
[DATA]: STRING -> BASE64_IMAGE | TEXT //消息的具体内容,可选
}
}
一些实际问题
粘包
在使用TCP的过程中,会遇到粘包的问题,其根本原因是TCP是面向流链接的,所以,在传输一消息的时候,需要在开始处添加 该消息的长度 , 建议采用 [TCP长度,固定4byte + TCP报文]的格式。
消息有序性
在推送消息的时候,我们需要考虑消息的有序性。所以,在APP推送到服务器,或者服务器推送到APP端的时候,需要按序推送,只有前一条消息推送成功后,才能推送后一条。
总结
可以发现IM协议的设计和TCP协议是非常类似的,额外的添加了消息持久化的功能。对于消息有序性这个问题,引发的同一时间只有一条消息发送这个问题,可以考虑TCP的滑动窗口机制。