微信消息从发送到接收,经历了什么?如何防止丢包

先不考虑服务器,假设微信是端到端的连接,为了保证消息的可靠性,它们之间用的一定是TCP协议进行通信。

为了发送数据包,两端首先会通过三次握手,建立TCP连接。

一个数据包,从聊天框里发出,消息会从聊天软件所在的用户空间拷贝到内核空间的发送缓冲区(send buffer),数据包就这样顺着传输层、网络层,进入到数据链路层,在这里数据包会经过流控(qdisc),再通过RingBuffer发到物理层的网卡。数据就这样顺着网卡发到了纷繁复杂的网络世界里。这里头数据会经过n多个路由器和交换机之间的跳转,最后到达目的机器的网卡处。

此时目的机器的网卡会通知DMA将数据包信息放到RingBuffer中,再触发一个硬中断给CPU,CPU触发软中断让ksoftirqd去RingBuffer收包,于是一个数据包就这样顺着物理层,数据链路层,网络层,传输层,最后从内核空间拷贝到用户空间里的聊天软件里。

微信消息从发送到接收,经历了什么?如何防止丢包_第1张图片

以上就是数据包发送及接收的流程,那么在这个过程中,什么情况下会导致丢包呢?

一:建立tcp连接时丢包 

tcp在建立连接时会有三次握手

在服务端,第一次握手之后,会先建立个半连接,然后再发出第二次握手。这时候需要有个地方可以暂存这些半连接。这个地方就叫半连接队列。

如果之后第三次握手来了,半连接就会升级为全连接,然后暂存到另外一个叫全连接队列的地方,坐等程序执行accept()方法将其取走使用。

队列都是有长度的,当长度满了的时候,新来的包就会被丢弃,可以通过命令查看是否存在丢包情况

# 全连接队列溢出次数
# netstat -s | grep overflowed
    4343 times the listen queue of a socket overflowed

# 半连接队列溢出次数
# netstat -s | grep -i "SYNs to LISTEN sockets dropped"
    109 times the listen queue of a socket overflowed 

二:流量控制丢包

因为很多应用都需要收发消息,如果不经控制,一股脑发送到网卡,会导致网卡超载,此时会引入流量控制机制qdisc(Queueing Disciplines,排队规则),让数据按一定的规则排个队依次处理。

队伍的长度可以通过 ifconfig 查到,其中的txqueuelen 字段就是队列长度

查看是否丢包时,可以输入 ifconfig命令,看TX下的dropped字段,当大于0时,说明发生了丢包

三:网卡丢包

不考虑物理层面(接触不良,网线质量问题)等,常见的网卡丢包情况如下:

 RingBuffer过小导致丢包

之前提到过,在接收数据时,会把数据缓存在RingBuffer的缓冲区中,然后等着内核触发软中断慢慢收走。如果这个缓冲区过小,而这时候发送的数据又过快,就有可能发生溢出,此时也会产生丢包。

微信消息从发送到接收,经历了什么?如何防止丢包_第2张图片

可以通过 ifconfig命令查看是否发生,overruns指标,它记录了由于RingBuffer长度不足导致的溢出次数。

查看RingBuffer的大小,可以通过ethtool命令,想要修改这个长度可以执行ethtool -G eth1 rx 4096 tx 4096将发送和接收RingBuffer的长度都改为4096。

网卡性能不足

网卡作为硬件,传输速度是有上限的。当网络传输速度过大,达到网卡上限时,就会发生丢包。这种情况一般常见于压测场景。

四:接收缓冲区丢包

我们一般使用TCP socket进行网络编程的时候,内核都会分配一个发送缓冲区和一个接收缓冲区。

当我们想要发一个数据包,会在代码里执行send(msg),这时候数据包并不是直接通过网卡发送出去的。而是将数据拷贝到内核发送缓冲区就完事返回了。

而接收缓冲区作用也类似,从外部网络收到的数据包就暂存在这个地方,然后坐等用户空间的应用程序将数据包取走。

这两个缓冲区是有大小限制的,可以通过下面的命令去查看。

# 查看接收缓冲区
# sysctl net.ipv4.tcp_rmem
net.ipv4.tcp_rmem = 4096    87380   6291456

# 查看发送缓冲区
# sysctl net.ipv4.tcp_wmem
net.ipv4.tcp_wmem = 4096    16384   4194304

三个数值分别对应最小值,默认值,最大值

如果缓冲区设置过小,对于发送缓冲区,执行send的时候,如果是阻塞调用,那就会等,等到缓冲区有空位可以发数据。如果是非阻塞调用,就会立刻返回一个 EAGAIN 错误信息,意思是  Try again 。让应用程序下次再重试。这种情况下一般不会发生丢包。

当接受缓冲区满了,事情就不一样了,它的TCP接收窗口会变为0,也就是所谓的零窗口,并且会通过数据包里的win=0,告诉发送端别发了。一般这种情况下,发送端就该停止发消息了,但如果这时候确实还有数据发来,就会发生丢包。

微信消息从发送到接收,经历了什么?如何防止丢包_第3张图片

五: 两端之间的网络丢包

  ping命令查看丢包:常用命令,不多介绍。

但是ping命令只能知道你的机器和目的机器之间有没有丢包。

那如果你想知道你和目的机器之间的这条链路,哪个节点丢包了,可以用 mtr 命令

mtr命令可以查看到你的机器和目的机器之间的每个节点的丢包情况

微信消息从发送到接收,经历了什么?如何防止丢包_第4张图片

可以看到Host那一列,出现的都是链路中间每一跳的机器,Loss的那一列就是指这一跳对应的丢包率。

需要注意的是,中间有一些是host是???,那个是因为mtr默认用的是ICMP包,有些节点限制了ICMP包,导致不能正常展示。

我们可以在mtr命令里加个-u,也就是使用udp包,就能看到部分???对应的IP。

微信消息从发送到接收,经历了什么?如何防止丢包_第5张图片

 

以上介绍了丢包的场景,那么如何防止丢包呢

 丢包是很常见的,几乎不可避免的一件事情。解决办法之一就是通过tcp传输。

建立了TCP连接的两端,发送端在发出数据后会等待接收端回复ack包ack包的目的是为了告诉对方自己确实收到了数据,但如果中间链路发生了丢包,那发送端会迟迟收不到确认ack,于是就会进行重传。以此来保证每个数据包都确确实实到达了接收端。

假设现在网断了,我们还用聊天软件发消息,聊天软件会使用TCP不断尝试重传数据,如果重传期间网络恢复了,那数据就能正常发过去。但如果多次重试直到超时都还是失败,这时候你将收获一个红色感叹号。

用了TCP协议就一定不会丢包吗

TCP保证的可靠性,是传输层的可靠性。也就是说,TCP只保证数据从A机器的传输层可靠地发到B机器的传输层。

至于数据到了接收端的传输层之后,能不能保证到应用层,TCP并不管。

假设现在,我们输入一条消息,从聊天框发出,走到传输层TCP协议的发送缓冲区,不管中间有没有丢包,最后通过重传都保证发到了对方的传输层TCP接收缓冲区,此时接收端回复了一个ack,发送端收到这个ack后就会将自己发送缓冲区里的消息给扔掉。到这里TCP的任务就结束了。

TCP任务是结束了,但聊天软件的任务没结束。

聊天软件还需要将数据从TCP的接收缓冲区里读出来,如果在读出来这一刻,手机由于内存不足或其他各种原因,导致软件崩溃闪退了。

发送端以为自己发的消息已经发给对方了,但接收端却并没有收到这条消息。

于是乎,消息就丢了。

说了这么多,还是会丢包,难道真就没办法解决吗?

开头说了不考虑服务端的情况,端与端之间没办法解决,那把服务器做个处理不就好了吗

大家有没有发现,有时候我们在手机里聊了一大堆内容,然后登录电脑版,它能将最近的聊天记录都同步到电脑版上。也就是说服务器可能记录了我们最近发过什么数据,假设每条消息都有个id,服务器和聊天软件每次都拿最新消息的id进行对比,就能知道两端消息是否一致,就像对账一样。

对于发送方,只要定时跟服务端的内容对账一下,就知道哪条消息没发送成功,直接重发就好了。

如果接收方的聊天软件崩溃了,重启后跟服务器稍微通信一下就知道少了哪条数据,同步上来就是了,所以也不存在上面提到的丢包情况。

可以看出,TCP只保证传输层的消息可靠性,并不保证应用层的消息可靠性。如果我们还想保证应用层的消息可靠性,就需要应用层自己去实现逻辑做保证。

 

参考文章:用了TCP协议,就一定不会丢包吗?

你可能感兴趣的:(网络,网络,tcp/ip,服务器)