668-怎么保证消息在客户端按序显示

怎么保证消息在客户端按序显示?

客户端以什么样的顺序发送给服务端消息,服务端就以什么顺序接收这些消息。
我实现的集群聊天服务器是跑在局域网上的,局域网的网络没有那么复杂,基本不会出现消息乱序到达,所以没有考虑到实现这个“保证”。
如果是在真实的网络环境,要做一个产品,线上的产品,给广大客户使用,这个“保证”是一定要实现的。

如果是给消息添加一个时间戳:

甲(客户端),消息是要通过服务端server进行转发。
所以,我们要设置成长连接。
如果是短连接,类似于http协议,是应用层的协议,底层是依赖TCP实现的,但是客户端(浏览器)和服务端(B/S结构)进行交互的时候,浏览器永远都是发送一个请求到服务端,服务端处理完这个请求,给response响应,然后服务端会主动把这个连接给关闭掉,然后给其他更多的客户端提供服务,这就是短连接,无状态的协议的体现,无状态,短连接的服务的模式只能是客户端主动请求,服务端被迫响应,服务端是没有办法给浏览器主动推送消息的。淘宝或者京东,有淘宝旺旺,京东也有推送消息,这其实都是通过websocket技术在浏览器和服务端创建一个TCP的长连接,才能让服务端给客户端主动推送消息。
在这里插入图片描述

但是集群聊天服务器是聊天业务,客户端甲和客户端乙进行聊天,客户端甲把聊天的消息发送到服务端,然后服务端找到客户端乙,然后主动推送消息给乙客户端。不建立长连接,服务器怎么知道和客户端乙通信的socket是哪个呢?连接都找不到,怎么找乙客户端?总不能让客户端乙自己每隔10毫秒去服务端拉一下,看看有没有乙的消息?如果有很多很多客户端呢?每个客户都每隔10毫秒去服务端拉消息(如果隔太久,会产生消息延迟),大部分拉都是白拉,对于服务端来说,处理的请求实在是太多太多了,承受不了这么多的请求。对于这种支持IM即时聊天消息功能的服务器来说,这个设计肯定是要和服务端保持长连接的,服务端有专门的长连接模块,专门保存所有客户端建立的长连接的连接消息(包含了连接建立的sockfd,连接的时长,通信的频率之类的),所以,这个项目要用长连接。

我们回到刚才最初的问题:怎么保证消息在客户端按序显示?

1、如果是给消息添加一个时间戳

甲客户端把消息从网络发送到服务端:
在这里插入图片描述
在真实的网络环境中,从甲客户端路由到服务端,发送的不同的消息,都有可能经过不同的网络节点的,如果在不做任何控制的情况下
甲先发送的“你在吗 23:10:01”,然后发送“你下午有什么打算?23:10:05”,到达服务端,不一定还是这样的到达顺序哦!因为有可能发送的“你在吗 23:10:01”这条消息选择的网络路由的节点(经过本地,运营商,服务端的路由节点),这条网络节点可能比较拥堵,停留的时间比较长,而“你下午有什么打算?23:10:05”这条消息根据网络的路由算法选择的路由节点可能比较通畅,更快的到达服务端了,所以,多条到达服务端的时候的顺序和发送出去的顺序是不一样的。然后服务端接分别接收到这些消息,然后转发给乙客户端,转发的时候同样遇到不同的路由节点,不同的拥塞情况,造成到达乙客户端的消息顺序也是不一样的。
在这里插入图片描述
如果是到达客户端乙的时候,对这些消息的时间戳进行排序显示,这个解决方法是有瑕疵的,首先,网络上随时有消息发送到客户端,客户端在进行排序的时候是有时间间断,比如说,客户端乙是以1秒为周期,对收到的消息进行时间排序,进行消息显示,把1秒之内接收的消息显示出来,有瑕疵:以1秒为周期进行消息排序显示,假如说,上图中甲发送的这2条消息是刚好跑在1秒的周期之内,那过了1秒之后,这2个消息的显示肯定是正确的。

但是,如果甲还发了一条消息:“下午走去打篮球,怎么样?23:10:15”,也就是说现在有3条消息从甲发送给乙了,
如果这3条消息通过网络发送出去,到达服务端,服务端转发到客户端,在客户端收到的消息的顺序是下图这样的:
在这里插入图片描述

也就是说,现在的情况是:甲发送的第2条和第3条消息先到达乙客户端,然后第1条消息才到达乙客户端,这是有可能的,第1条消息由于网络非常拥塞造成慢到达的。
如果是按照时间戳排序,进行显示,没有办法在全局对时间戳进行排序,只能规定一个周期,把周期内的消息按时间戳排序显示。如果是上图中的情况:第2条第3条消息在1个周期内,第1条消息在后1个周期内,不同周期的时间戳是不会进行排序的,这样就造成了第2条第3条消息排序后显示出来,然后把第3条消息和后面的消息排序显示出来,也是乱序的显示了!

2、给每一个消息都添加1个序列号seq

从0开始,1,2,3,4,5…
在这里插入图片描述
当客户端甲给客户端乙发送消息的时候,消息包含消息内容和seq,甲先发送的是“你在吗?”所以,这个消息带的seq=0
然后发送“你下午有什么打算?”,这个消息带的seq=1
然后发送“下午去打篮球怎么样?”,这个消息带的seq=2
在这里插入图片描述
我们不需要管这些消息到达服务端的顺序是什么样的。
现在在乙客户端,维护着每一个好友的seq,比如说,现在维护着甲的seq是0,也就是说,乙下一次在接收甲的消息的时候,它首先需要接收seq是0的消息,现在乙收到甲发送的第2条消息了,查了一下它维护的甲的seq序列号是0,但是现在收到这条甲发送的消息的seq序列号确是1,证明,不能先显示这个,乙需要把这个消息先缓存到乙的客户端本地,现在乙客户端收到甲发来的第3条消息,同样的,乙客户端查本地维护的甲的seq是0,然后看着条消息的seq是2,所以,也把这条消息缓存在本地中,在这里插入图片描述
然后乙客户端收到甲发送的第1条消息,查了一下本地维护甲的seq是0,然后此条消息的seq是0,匹配上了,然后就把这条消息显示出来。然后本地维护甲的seq+1
在这里插入图片描述
然后乙客户端先看有没有本地缓存,如果有本地缓存,就从本地缓存中找:有没有seq=1的甲的消息,如果有,就显示出来,然后本地维护甲的seq+1,
然后继续在本地中查有没有seq=2的甲的消息,如果有,就显示出来,然后本地维护甲的seq+1,seq就变成3了,发现本地缓存也没有seq=3的甲的消息了,目前为止,消息处理完了,等下次有消息发来继续处理。
不管是一对一聊,还是群聊,都要针对某一个人,或者某一个群维护1个seq。这个seq不是全局共享的哦,都是针对人或者群进行特别定制的!
这样一来,消息的显示肯定是不会乱的!而且每一个消息有了序列号,不仅仅可以保证消息的按序到达,更重要的是还可以实现其他更多的功能:
比如说,消息的撤回功能!如果每一条消息没有任何标记的话,那么消息如何撤回?乙客户端就无法去识别消息的身份了。
在乙客户端中,每一条消息都有序列号,当甲客户端撤回某一条消息的时候,相当于是发送了一个撤回消息:“发送人甲接收人乙撤回的消息序列号是2”,这个消息发送到服务端,然后服务端把这个消息转发给乙客户端,乙在这里就知道甲要撤回seq=2的消息,它就在找见甲发来的seq=2的消息,然后把这个消息从乙客户端的显示界面剔除掉。

增加时间戳,时间戳这个东西变数是很大的,对于服务器来说,集群的话,每一台服务器的时间可能是不一样的,客户端也有可能修改成不是准确的时间,现在,不管是客户端还是服务器,都是连网,不断的请求时间服务器来保持时间的统一,但是时间戳来保证消息的顺序显示,还是不可靠的。
用序列号才是真真正正,在各种网络环境下,实现消息顺序显示的根本。
当然,也会显示一些问题:每一条消息最终通过TCP报文封装,打包成IP报文,IP报文里的字段有一个:TTL,最大的跳数,因为我们这个消息在网络节点上,经过一个路由器,就跳1次,TTL通常是64,也就是说,如果服务端转发消息到乙客户端的过程中,如果这个消息经过路由器的个数超过64了,这个消息就跳到某个路由器上时,TTL+1到达65了,就认为这个消息不应该在网络环境中存活了,这个路由器直接把这个网络消息直接丢弃掉了,导致乙客户端在本地缓存的甲的1号和2号消息,永远拿不到0号消息,此时,处理机制:乙可以主动的向服务端发送消息,然后服务端请求甲把seq=0的消息重发一下,如果乙请求服务器3次,都没有得到甲发送过来的0号消息,就判定这个消息是没有办法发送成功的,就直接显示1号消息和2号消息了,并更新seq=3

可以默认发起方的第一条消息的序列化是0,然后接收方维护每一个客户端的初始化seq=0,然后以此类推。
seq用unsigned就可以了,取值范围是0-40亿
或者使用unsigned long long 就非常非常的大了,聊了十几年到达上限了,因为是无符号的类型,再加1就又回到0了,这是不影响的,两端都是一起匹配的。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值