670-聊天服务器和客户端如何保证消息的可靠传输

如何保证消息的可靠传输?

意思是:客户端把消息发送出去了,只要客户端这里显示他的消息发送成功,就要保证对端一定要收到,要么收不到,客户端就显示发送失败,用户后续选择重新发送消息。如果客户端显示消息发送成功,就一定保证对端一定收到这条消息。
我们可以在业务层实现消息的确认机制(结合心跳):在消息发送之后对端给予响应。
集群聊天服务器是基于TCP协议实现的,TCP协议本身是可靠的传输协议,在发送数据的时候,有超时重传机制,TCP发送每一个数据的时候,都会等待得到接收方返回的ACK消息确认,它如果得不到ACK,就会超时重传,选择重新发送这个数据,得到ACK确认之后,保证消息发送成功了。
为什么业务层上还要实现消息确认机制呢?
我们看看下面这张图:
670-聊天服务器和客户端如何保证消息的可靠传输_第1张图片
这个send的返回值大于0,发送成功了,返回了发送成功的字节数,但是并不代表着这条消息跑到对端,对端接收到了。
send怎么可能是调用的时候还包括客户端往服务器发送的过程和服务器接收到了,然后响应,响应成功了,send才返回?不可能的!如果是这样的话,send调用所花费的时间就非常长了,尤其是在网络环境非常复杂的情况下,这样做,耗费的时间是非常长的。

当我们在进程的用户空间的数据,就是上图中的buf,然后它要发送,调用系统的接口send,把用户空间的buf数据拷贝到内核空间的TCP发送缓冲区中,因为TCP是流式服务,有TCP的发送缓冲区和接收缓冲区, UDP数据报协议是没有发送缓冲区和接收缓冲区的,对于TCP发送来说,send发送成功,仅仅只是代表说把用户空间的buf里面的多少数据拷贝到内核空间的TCP发送缓冲区中。还没有把数据发送到对端。
670-聊天服务器和客户端如何保证消息的可靠传输_第2张图片
内核空间的TCP发送缓冲区的数据,是由内核的TCP协议栈(相当于是TCP协议的代码模块),专门把发送缓冲区的数据(根据滑动窗口机制,可以根据网络状况,调节发送缓冲区的数据的发送的一个快慢,调节流量,把数据发送出去)。
send只是把数据发送到TCP的发送缓冲区中,然后send就返回了,用户空间继续向下执行代码。所以,send成功返回,并不能说明消息到达对端了。
670-聊天服务器和客户端如何保证消息的可靠传输_第3张图片
内核空间处理TCP发送缓冲区的数据,开启TCP的传输,TCP传输的时候,传1个数据报文,在这里,有可能出现2种情况:
情况1、从C端发送到S端后,由于网络的情况比较复杂,网络比较拥塞,数据报文经常切换网络节点的路由,导致这个数据报文的TTL在没有到达S端,已经超过上限了,直接被路由器把当前的数据报文丢弃了,到达不了S端了。
情况
情况 2、这个报文到达S端了,基于TCP的可靠传输,对每一个发送的数据报文,都要进行响应,这个ACK可能就碰到了情况1了,导致ACK报文在网络中丢了,没有到达C端。
上面2种情况都是属于报文传输失败了。
670-聊天服务器和客户端如何保证消息的可靠传输_第4张图片
当C端第一次发送报文的时候,TCP协议栈里面就会起1个超时重传定时器,当我们发送的数据报文到达S端,S端返回的这个ACK也到达C端了,那我们就可以继续发送下一个报文了。
如果说,当我第一次发送该报文,超时重传定时器已经超时了,还没有得到S端的响应ACK,它就会重新去发送这个数据报文,力求这个S端可以返回这个报文的ACK,如果后面S端响应了这个ACK,说明这个报文传输成功,如果还是出现了数据报文没有发到S端,或者S端返回的ACK在中途丢了,C端没有收到,超过定时时间,它又要进行超时重传,超时重传是有上限次数的,是不可能无限次超时重传下去的,网络情况是很复杂的,如果网络情况复杂,搞了很多很多的TCP报文超时重传,一直不断重传,整个内核所占用的资源永远得不到释放,这是非常不好的。所以,超时重传超过一定次数,TCP会发一个RST(连接重置)的报文给到对端,整个超时重传流程就结束了。TCP的超时重传是在一定程度上保证了报文的可靠性传输,但是如果网络环境比较差,一直重传都没有得到响应,TCP发送一个RST连接重置报文就不管了,消息还是没有到达S端!!!所以,我们聊天消息的可靠传输肯定是不能靠协议来保证的。
**内核空间是不可能给用户提供一个回调函数提醒消息没有传输成功的。**尤其是在TCP协议栈,TCP协议栈是负责网络模块的,其本身就随着网络环境的复杂,TCP协议栈本身实现也很复杂,如果在这里给用户注册一个回调函数,万一回调函数里访问了一个空指针,直接把内核的TCP模块废掉了,怎么办?
Linux的信号signal,信号的回调操作内核发给你当前进程的信号,可以写代码让这个信号挂掉,因为信号是某个进程的信号,把进程搞挂掉,就是让这个进程不要运行了而已。但是TCP协议栈是给的用户空间的所有应用程序服务的,万一给你注册回调,把内核挂了,怎么办???

数据传输的过程:传输层(TCP)-》网络层(IP)-》数据链路层(MAC)
网络层以上是通过IP地址定位主机的,网络层以下(从链路层开始)是通过Mac2地址定位主机的。Mac是真真正正,把数据一帧一帧的发送到网络上了。Mac发送一帧的大小:上限是MTU 1500字节,TCP的报头是20字节,IP的报头是20字节,一般来说,一个网卡的一帧最多携带1500-40=1460字节数据。如果我们发送的数据的大小是超过1460字节的,我们在IP层可以进行分包,分片传输,分成1小片1小片,发送到对后,可以把这些小的分片组成原始的数据。

正确方法

我们还是得在业务层上实现消息的可靠传输!!!
我们把客户端要发送的消息都缓存起来,因为客户端要实现消息的可靠传输,而且,每一条消息都有seq序列号,不管是一对一聊天,还是群聊,每一个对象在每一个聊天的会话都有消息的序列号seq。
客户端A连续发送3条消息:
message1 seq:0
message2 seq:1
message3 seq:2
0号消息先发送出去,正常情况下,这条消息发送出去以后,客户端要等待服务端对这个消息的响应ACK,然后从正常流程来说,客户端发送1号消息。
以此类推。
或者说,在客户端中,也没有必要按照顺序发送消息,为了性能来说,可以把消息全部发送出去,因为每条消息都有seq序列号,在对端收到消息以后,会对消息进行缓存,按顺序显示消息,所以发送方可以一次性全部发送出去,但是发送方一定要等待每个消息回应的ACK,哪个消息回应了ACK,就说明哪个消息是可靠传输到了对端(因为消息都有序列号,可以区分出来),客户端发送到服务器,服务器转发到另一个客户端上,都是根据这个方法。如果客户端收到某条消息的ACK了,就可以把这条消息从本地缓存删除了,说明这个消息后面不用进行重传了。如果客户端得不到对1号消息的ACK的话,我们可以在客户端设置超时重传机制,如果3毫秒没有收到这条消息的ACK,再重新发这条消息,以此类推,如果重复传3次还没有收到这条消息的ACK,我们就给客户端显示:该消息发送失败。等下一次网络恢复,由用户来选择是否重传。
670-聊天服务器和客户端如何保证消息的可靠传输_第5张图片
客户端也可以通过实现心跳机制来检查服务端的连接能不能连通,心跳正常,发送消息肯定没问题。比如说,客户端隔1秒发送一次心跳,如果服务端没有响应,客户端的心跳计数就从0加到1,如果收到服务端的响应,就减1,以此类推,如果心跳计数超过3,客户端就判定和服务端的连接断开了,客户端就是红色状态了。如果客户端没有退出APP的话,就一直发送心跳,如果发着发着,网络恢复了,客户端发了一个心跳,服务端响应了,客户端把心跳计数的3改为0,继续发送心跳,如果刚恢复了,现在又出现问题了,心跳计数又超过3了,又判定和服务端连接有问题了
也就是说,心跳给了和服务端的连接状态,我们发送消息的时候可以先看连接的状态,如果是OK的话,心跳消息都能交互成功,我们发送普通的消息肯定也可以收到ACK响应的。如果心跳已经是失败的,我们在全局有一个状态(和服务器的状态)已经不可用的状态(inactive),我们客户端在发送消息就直接是不能发送,直接给用户显示发送失败,不用尝试发送了,心跳都失败,发送普通消息还能成功???等下一次心跳恢复正常的话,我们再去发送消息。

我们在TCP,UDP都会设置心跳机制来复杂的网络环境中,客户端检测服务端链路是否正常,服务端检测客户端是否在线。

你可能感兴趣的