之前说过信道的实现是sender+receiver+seq_num确定唯一的消息。
使用Redis ZSet实现,score为seqnum,同时使用incr指令获取自增seqnum。
使用seqnum模块获取senum,参考微信seqnum实现。
收件箱只保存发送者,而不保存具体的消息。但是这里涉及到什么时候使用收件箱,一般而言是在用户连接断开时使用,但是如何准确快速的判断呢?
如果使用心跳机制,则最快判断时间取决于心跳间隔和超时次数,而盲目的缩小这二者会造成资源浪费。
所以通过向连接写数据失败来判断。比方说,一旦连接写入失败,便追加收件箱,因为所有的消息都会先写入到信道,所以单纯处理收件箱即可。
但是思考一个极端情况,如果客户端突然断网一秒,然后再重新上线,此时是感知不到断网这一操作的,毕竟连接只是虚拟的,TCP连接仅仅创建了一个可靠的虚拟链路。那么断网期间发消息,断网重连之后发消息会怎样呢?又或者考虑重启和崩溃这种情况,有需要怎么处理呢?
- 断网期间:
- 有数据传输:触发TCP的重传,并在超时*超次数后断开连接
- 无数据传输:无法感知
- 断网重连:
- 已超时(超时时间*重试次数):断开连接
- 未超时:无法感知
- 客户端崩溃:
- 断开连接
- 客户端主机重启:
- 服务端主动发送消息:触发客户端RST,断开连接。
其实断网的问题还要细致一点,就是分为直连/非直连链路的处理,直连链路断开会被感知,非直连不会。但是现在互联网基本都是非直连链路,所以这个当成断网有无数据传输处理。
看到这,可以发现仅仅通过发送失败是可以的。因为异常情况会出发断连,这正是我们需要的。而无法感知的两种情况都是我们可以接受的。
但是因为系统的重试次数*超时时间实在是太长了,所以我们还是最好拿应用层心跳处理;而且系统超时这个不是TCP规范,万一憨憨系统没有呢?
使用Redis Set实现。
心跳设置为每指定时间发送一次,同时在内存保存指定用户上一次在线时间,如果超过设定间隔三次没有心跳则认为发生了下线。
消息同步分为前向同步和后向同步,前向同步拉取指定消息前N条消息,后向同步拉取指定消息后N条消息。如果没有指定则默认前向同步,且起始位置为最新的消息。这里是针对某一信道而言。
所以这里需要客户端保存最新消息序号,并且作为拉取依据,如果是第一次登录则拉取最近七天的消息,当然了,一开始只拉取最近三天的即可。服务端仅保存最近七天的消息,并且可全平台同步。
所以拉取消息的参数应该有三个:方向,序号,数量(类似分页)。
在回复消息时,回复的第一条消息是本次同步请求返回的消息总数,然后依次跟着N条消息,按照seq_num排序。
这里的回执指的是服务端对客户端的回执,而不是被发送客户端对服务端的回执,那就是企业微信和钉钉这种PUA玩意了,仅仅用于确定消息送达至服务端。回执与其对应的消息存在严格的顺序关系,所以不需要担心回执错乱的问题。
这里说一下群消息,对于群消息前面说了是把群当成用户处理,且所有群成员共享一个信道,但是如果群成员想要了解消息发送方,还需要额外的参数来记录。
这里对群ID做限制,即所有的群ID都 >= (1 << 36),所以一旦消息接收方满足这个条件,就知道这是一个群消息,那么extension部分会被追加一个发送者信息,即包含的8字节sender_id。
收件箱为了保证绝对不遗漏(因为用户每次上线一定会检查一下的,所以如果这里遗漏了就会造成消息永远不被感知),需要对每一条消息进行记录,同时使用时间顺序写入ZSet,可以做到同一个用户永远保存的都是他的最新消息记录。
所以这里需要客户端自己去做消息本地化存储,并在下次上线时请求这个记录之后的收件箱。
服务端心跳指出所有设备里最新的未读消息,而每一个设备上的客户端都需要保存自己的本地列表,用来减少网络请求。
客户端 <=> 服务端的加密处理。