来看一下客户端实现的细节,这里主要说和服务端消息服务通信部分的。
首先底层API暴露出的功能有:
- 发送消息
- 发送业务消息
- 发送Sync,Box,Auth这三种逻辑消息
- 接收消息
- 接收业务消息
- 接收Sync,Box,Auth这三种逻辑消息
- 发起连接
- 传递服务端地址并创建连接,API本身不会做IP:PORT获取
- 在发生断线时重新发起连接
- 关闭连接
- 主动退出关闭连接
- 崩溃时/异常时/无法处理的错误时关闭连接并退出
而底层API本身需要做的事除了暴露上述API,还包括:
- 心跳逻辑:自动发送Ping并处理Pong
- 断线自动重连:保存上一次连接地址并尝试连接
- 处理下线消息:通知上层并等待新的连接
- 转发ACK
- 处理错误消息:包括反馈给上层API
- 存储消息:持久化消息,方便查询
- 记录系统最新状态:用于Sync
这里有一个问题,就是怎么实现发送失败通知,假如设定超时时间为三秒,则三秒之后未收到ACK则认为发生失败,难点在于怎么实现三秒后的查询。
如果使用定时器,则对定时器性能要求比较高,如果不使用则需要使用循环查询+下次唤醒机制。
关于ACK未收到分为发送失败和ACK回复失败两种,前者简单重发即可(但是可能造成消息顺序错乱,QQ就存在这个问题,所以我们也不管了),后者重发会造成消息重复,这一点只能让服务端去判断(哈希消息保存+Set记录+过期时间设定)。
所以消息发送幂等由服务端实现。
这里选用定时器实现。性能什么的不管了,应该问题不大。其实如果选用轮询的话,可以使用定时处理,这样保证最多两个Ticker之内可以处理消息超时问题。
关于记录消息ACK选用下标数组实现,这里就有一个假设,即一毫秒内不会产生两条消息。
现在来讨论一些实际实现时遇到的细节。
- 消息列表:消息列表应该做本地化存储,同时在每一次客户端开启时,都应该询问Box信箱,并把新的用户追加到消息列表中,此外,如果用户点击朋友头像开启的聊天,也应该追加消息列表,并同步消息。
- 用户列表:通过后端API获取,需要在点击时添加到消息列表,且如果是第一次添加,需要拉取消息。
- 消息同步:在系统启动时针对所有消息列表拉取所有消息,在启动之后所有添加到消息列表的新的信道只拉取一次。同时因为客户端消息排序基于时间,服务端基于序列号,而且序列号只会在服务端生成,所以可能会出现不同步,解决措施就是退出客户端触发重同步。
关于消息拉取的处理,包括第一次拉取会拉取最新的五十条消息,然后判断是否拉取到已经存在的消息,这里需要注意,本地的消息列表一定是包含序列号的。如果拉取到存在的,说明同步完成。记录最老的消息序列号,作为下一次拉取依据,如果某次拉取响应大小为零,说明拉取到末尾,同步完成。
总结:针对任一信箱只会同步一次。消息列表持久化数据一定包含序列号。拉取完成需要写入本地存储。
补充一下多设备处理。假设用户A有三个设备,用户B有四个设备,则用户A的消息需要被推送到用户B的所有在线设备,同时推送到自己的其他在线设备。而对于当前设备则不进行推送,取而代之的是使用ACK处理。同样的是,无论设备数,客户端只在启动时进行一次同步,之后全程使用连接收发消息。