移动端即时通讯系统实践

在信息高度发达的今天,IM基本上已经成为了一个社交应用的标配。本文将以一个移动开发者的视角,探讨移动端即时通讯系统的技术选型和关键要点。

1 即时通讯系统的需求
任何技术系统都来源于真实业务的需求,做架构设计之前应该先设定好目标。作为一个即时通讯应用,可以参考微信的使用体验,你需要保证以下特性:
1,实时。消息的接收端应该能够及时收到并处理消息。
2,不丢。需要保证所有的消息都顺利送达。
3,不重。重复的消息对用户来说是一种糟糕的体验。
4,保序。只要顺序一乱,消息根本没发看。
5,节能。流量可贵,电量可贵,能省则省。
6,安全。如果涉及敏感数据,安全必须重视。
7,流畅。卡顿的应用是不会被用户接受的。

2 关键技术点
为了保证消息的实时性,有两种思路:
1,长轮询方式,高频率地从服务端拉取新消息。这种方式其实就是传统的请求-响应模型,现在很多体育文字直播软件也采取这种方式。这种方法虽然简单,但有很多缺点。一是会产生很多请求,这对服务器的压力和用户的流量都是浪费。二是消息仍然不够及时,不考虑传输时间,最长的延迟就是轮询的间隔。
2,消息的生产者主动推送消息。这应该是更好的选择,可以解决长轮询的缺点。我们的即时通讯系统也会采用这种方式。使用长连接,而且连接必须是稳定可靠的,才能确保消息的实时性。

2.1 数据通信协议
服务端与客户端之间需要协商好数据格式,这是数据传输和数据处理的基础。协议的设计需要着重考虑第一节提到几点需求。
XMPP和MQTT是当前比较成熟的两种消息协议。如果能较好地处理你的业务需求,就没有必要重复造轮子。有很多企业的业务有特殊需求,可以考虑根据实际情况自定义协议,从头开始显然是不现实的,可以参考已经成熟的协议再做设计和开发。具体协议内容在此不详细展开,只做优劣的比较。

2.1.1 XMPP
XMPP是一种以XML为基础的开放式即时通讯协议。
XMPP的优点是安全,SASL及TL等技术的可靠安全性已内置于核心XMPP技术规格中。
XMPP 协议的最主要的一点就是开放,不管是协议、客户端,还是 Server 端,都有成熟的实现方案。
基于XML,它天生拥有很强的灵活性,可以在核心协议之上方便地进行定制化。Google Talk就是采用这种协议。
但是XMPP的缺点也很明显。首先,XMPP协议的方式被编码为一个单一的长的XML文件,因此无法提供修改二进制数据。其次,XML 有大量的标签冗余信息,网络流量的 70% 都消耗在 XMPP 协议层了,这在移动互联网时代,流量和电量是一个不可忽视的消耗。

2.1.2 MQTT
MQTT协议是由IBM提出的基于发布/订阅模型的消息传输协议,相比于XMPP,它显得非常轻量小巧,协议内容包括固定头部+可变头部+消息体,最下的情况下头部只需要两个字节,在传输开销上有着巨大的优势,可以节省流量和电量。
MQTT可以保证消息的可靠性,它包括三种不同的服务质量(最多只传一次、最少被传一次、一次且只传一次),如果客户端意外掉线,可以使用“遗愿”发布一条消息,同时支持持久订阅。
XMPP使用XML,是一个历史的选择,在现在移动应用的场景下,个人更加推荐MQTT。据了解,不少企业,包括做IM SDK的厂商,也是在MQTT的基础上进行自定义的扩展和修改。

2.2 连接的稳定性
移动互联网的场景下,网络环境经常变化,需要保证连接是稳定的。

2.2.1 心跳
最经典的做法就是使用心跳,实时地检测连接状态。通常是客户端每隔一小段时间向服务器发送一个数据包,通知服务器自己仍然在线,并传输一些可能必要的数据。如果在一定时间内服务器没有响应,则认为连接可能已经断开,重新尝试连接。
伪代码如下:

while(true) {
    if (now - last_pong_msg > keep_alive) {
        socket_close();
        reconnect();
    }
    send_heartbeat_ping();
    // 只是为了表示每keep_alive时间段发一次心跳
    sleep(keep_alive);
}

上述代码的心跳间隔是固定的。由于心跳包也是会消耗流量的,因此应该找到一个理想的心跳周期,在能敏锐地察觉连接变化的前提下,尽量大地增加周期间隔。因此可以做一个优化,是使心跳间隔动态增加。

2.2.2 多连接尝试
(1)多连接尝试
考虑到不同地区不同网络运营商的情况下,用户可能因为网络限制,连接不上我们的服务或者比较慢。我们在实践中就发现,某些网络运营商将某些端口封禁了,导致部分用户连接不上服务。为了解决这个问题,可以提供多个ip和多个端口,客户端在连接某个ip比较慢的情况下,可以进行轮询,切换到一个更快的ip。
(2)长连接与短连接结合
这只是一条退路,而不是常规武器。
在长连接实在连接不上的情况下,可以考虑做降级,使用短连接长轮询的方式进行替代。

2.3 服务质量
系统的设计往往存在着取舍和妥协。正如TCP比UDP更加可靠,但它的负载会更高。
在即时通讯系统中也存在着取舍的问题,是追求极速送达,还是在传输上做可靠性的保证,确保不丢不重?不同的业务类型可能需要不同的服务质量,MQTT协议提供了三种服务质量,可以作为参考:
QoS 0: 至多发送一次,发送即丢弃。没有确认消息,也不知道对方是否收到。针对的消息不重要,丢失也无所谓。
QoS 1: 至少发送一次。发送之后,会等待接收方ack确认。在一定时间之内,如果没有收到ack,则会再发一次,一直到接收方收到。重发的消息会在头部有dup标示。这种QoS可以保证消息不丢,但接收方可能会有重复消息,需要做去重。如下图所示:

移动端即时通讯系统实践_第1张图片

QoS 2:有且仅有一次。可以保证不丢不重,但是通信压力高,需要多次握手。如下图所示:


移动端即时通讯系统实践_第2张图片

3 客户端实现

3.1 消息处理
发送消息比较简单,只需要往某一个topic发布即可。
接收消息的流程如下:

移动端即时通讯系统实践_第3张图片

收消息:客户端需要保持一个长连接,并且确保连接稳定,如上章节所示。
消息过滤:如果发送端不能确保消息不重(如mqtt中QoS为0或1),客户端需要做去重,因此消息需要有一个唯一的id。
消息合并和分发:在实际使用场景中,往往有各种各样的消息类型(如聊天消息、系统通知等),对同一类型的消息可以做合并,以加快后续消息处理速度。而不同类型的消息则分发到各自的处理器当中,如存储到本地数据库,通知页面更新等)
UI更新:客户端一般是使用列表来展示消息(iOS中是UITableView,Android中是ListView),而列表的数据量可能很大,数据源更新频率也可能很频繁,因此需要对列表做性能优化,以确保用户体验。以iOS为例,使用Instruments监控性能瓶颈的地方,对内存占用和CPU占用大户进行优化。确定问题后,常用的技巧有:对cell高度做缓存;简化UI层次结构;避免大量的离屏渲染;减少混合图层;等等。对症下药,各个击破。

3.2 其他
IM应用中,还有很多常用的实现需求,例如表情键盘,图片语音等多媒体的存储和下载队列等。但这不在系统实现的范畴中,将来后有文章进行详细阐述。
参考:
https://en.wikipedia.org/wiki/XMPP
http://www.blogjava.net/yongboy/archive/2014/02/15/409893.html

你可能感兴趣的:(移动端即时通讯系统实践)