TCP 是一个可靠的传输协议,那它一定能保证数据不丢失吗?

这次,就跟大家探讨这个问题。

数据包的发送流程

首先,我们两个手机的绿皮聊天软件客户端,要通信,中间会通过它们家服务器。大概长这样。

聊天软件三端通信

但为了简化模型,我们把中间的服务器给省略掉,假设这是个端到端的通信。且为了保证消息的可靠性,我们盲猜它们之间用的是TCP协议进行通信。

聊天软件两端通信

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

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

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

网络发包收包全景图

画了那么大一张图,只水了200字做解释,我多少是有些心痛的。

到这里,抛开一些细节,大家大概知道了一个数据包从发送到接收的宏观过程。

可以看到,这上面全是密密麻麻的名词

整条链路下来,有不少地方可能会发生丢包。

但为了不让大家保持蹲姿太久影响身体健康,我这边只重点讲下几个常见容易发生丢包的场景

建立连接时丢包

TCP协议会通过三次握手建立连接。大概长下面这样。

TCP三次握手

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

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

半连接队列和全连接队列

是队列就有长度,有长度就有可能会满,如果它们满了,那新来的包就会被丢弃

可以通过下面的方式查看是否存在这种丢包行为。

# 全连接队列溢出次数
# netstat -s | grep overflowed4343 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(QueueingDisciplines,排队规则),这也是我们常说的流量控制机制。

排队,得先有个队列,而队列有个长度

我们可以通过下面的ifconfig命令查看到,里面涉及到的txqueuelen后面的数字1000,其实就是流控队列的长度。

当发送数据过快,流控队列长度txqueuelen又不够大时,就容易出现丢包现象。

qdisc丢包

可以通过下面的ifconfig命令,查看TX下的dropped字段,当它大于0时,则有可能是发生了流控丢包。

# ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500inet 172.21.66.69  netmask 255.255.240.0  broadcast 172.21.79.255inet6 fe80::216:3eff:fe25:269f  prefixlen 64  scopeid 0x20<link>ether 00:16:3e:25:26:9f  txqueuelen 1000  (Ethernet)RX packets 6962682  bytes 1119047079 (1.0 GiB)RX errors 0  dropped 0  overruns 0  frame 0TX packets 9688919  bytes 2072511384 (1.9 GiB)TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

当遇到这种情况时,我们可以尝试修改下流控队列的长度。比如像下面这样将eth0网卡的流控队列长度从1000提升为1500.

# ifconfig eth0 txqueuelen 1500

网卡丢包

网卡和它的驱动导致丢包的场景也比较常见,原因很多,比如网线质量差,接触不良。除此之外,我们来聊几个常见的场景。

RingBuffer过小导致丢包

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

RingBuffer满了导致丢包

我们可以通过下面的命令去查看是否发生过这样的事情。

# ifconfig
eth0:  RX errors 0  dropped 0  overruns 0  frame 0

查看上面的overruns指标,它记录了由于RingBuffer长度不足导致的溢出次数。

当然,用ethtool命令也能查看。

# ethtool -S eth0|grep rx_queue_0_drops

但这里需要注意的是,因为一个网卡里是可以有多个RingBuffer的,所以上面的rx_queue_0_drops里的0代表的是第0个RingBuffer的丢包数,对于多队列的网卡,这个0还可以改成其他数字。但我的家庭条件不允许我看其他队列的丢包数,所以上面的命令对我来说是够用了。。。

当发现有这类型丢包的时候,可以通过下面的命令查看当前网卡的配置。

#ethtool -g eth0
Ring parameters for eth0:
Pre-set maximums:
RX:        4096
RX Mini:    0
RX Jumbo:    0
TX:        4096
Current hardware settings:
RX:        1024
RX Mini:    0
RX Jumbo:    0
TX:        1024

上面的输出内容,含义是RingBuffer最大支持4096的长度,但现在实际只用了1024。

想要修改这个长度可以执行ethtool -G eth1 rx 4096 tx 4096将发送和接收RingBuffer的长度都改为4096。

RingBuffer增大之后,可以减少因为容量小而导致的丢包情况。

网卡性能不足

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

我们可以通过ethtool加网卡名,获得当前网卡支持的最大速度。

# ethtool eth0
Settings for eth0:Speed: 10000Mb/s

可以看到,我这边用的网卡能支持的最大传输速度speed=1000Mb/s

也就是俗称的千兆网卡,但注意这里的单位是Mb,这里的b是指bit,而不是Byte。1Byte=8bit。所以10000Mb/s还要除以8,也就是理论上网卡最大传输速度是1000/8 = 125MB/s

我们可以通过sar命令从网络接口层面来分析数据包的收发情况。

# sar -n DEV 1
Linux 3.10.0-1127.19.1.el7.x86_64      2022年07月27日     _x86_64_    (1 CPU)08时35分39秒     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s    rxcmp/s   txcmp/s  rxmcst/s
08时35分40秒      eth0      6.06      4.04      0.35    121682.33   0.00    0.00     0.00

其中 txkB/s是指当前每秒发送的字节(byte)总数,rxkB/s是指每秒接收的字节(byte)总数

当两者加起来的值约等于12~13w字节的时候,也就对应大概125MB/s的传输速度。此时达到网卡性能极限,就会开始丢包。

遇到这个问题,优先看下你的服务是不是真有这么大的真实流量,如果是的话可以考虑下拆分服务,或者就忍痛充钱升级下配置吧。

接收缓冲区丢包

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

当我们想要发一个数据包,会在代码里执行send(msg),这时候数据包并不是一把梭直接就走网卡飞出去的。而是将数据拷贝到内核发送缓冲区就完事返回了,至于什么时候发数据,发多少数据,这个后续由内核自己做决定。

tcp_sendmsg逻辑

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

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

# 查看接收缓冲区
# sysctl net.ipv4.tcp_rmem
net.ipv4.tcp_rmem = 4096    87380   6291456# 查看发送缓冲区
# sysctl net.ipv4.tcp_wmem
net.ipv4.tcp_wmem = 4096    16384   4194304

不管是接收缓冲区还是发送缓冲区,都能看到三个数值,分别对应缓冲区的最小值,默认值和最大值 (min、default、max)。缓冲区会在min和max之间动态调整。

那么问题来了,如果缓冲区设置过小会怎么样?

对于发送缓冲区,执行send的时候,如果是阻塞调用,那就会等,等到缓冲区有空位可以发数据。

send阻塞

如果是非阻塞调用,就会立刻返回一个 EAGAIN 错误信息,意思是  Try again。让应用程序下次再重试。这种情况下一般不会发生丢包。

send非阻塞

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

recv_buffer丢包

我们可以通过下面的命令里的TCPRcvQDrop查看到有没有发生过这种丢包现象。

cat /proc/net/netstat
TcpExt: SyncookiesSent TCPRcvQDrop SyncookiesFailed
TcpExt: 0              157              60116

但是说个伤心的事情,我们一般也看不到这个TCPRcvQDrop,因为这个是5.9版本里引入的打点,而我们的服务器用的一般是2.x~3.x左右版本。你可以通过下面的命令查看下你用的是什么版本的linux内核。

# cat /proc/version
Linux version 3.10.0-1127.19.1.el7.x86_64

两端之间的网络丢包

前面提到的是两端机器内部的网络丢包,除此之外,两端之间那么长的一条链路都属于外部网络,这中间有各种路由器和交换机还有光缆啥的,丢包也是很经常发生的。

这些丢包行为发生在中间链路的某些个机器上,我们当然是没权限去登录这些机器。但我们可以通过一些命令观察整个链路的连通情况。

ping命令查看丢包

比如我们知道目的地的域名是 baidu.com。想知道你的机器到baidu服务器之间,有没有产生丢包行为。可以使用ping命令。

ping查看丢包

倒数第二行里有个100% packet loss,意思是丢包率100%。

但这样其实你只能知道你的机器和目的机器之间有没有丢包。

那如果你想知道你和目的机器之间的这条链路,哪个节点丢包了,有没有办法呢?

有。

mtr命令

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

像下面这样执行命令。

mtr_icmp

其中 -r 是指report,以报告的形式打印结果。

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

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

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

mtr-udp

ICMP包和UDP包的结果拼在一起看,就是比较完整的链路图了。

还有个小细节,Loss那一列,我们在icmp的场景下,关注最后一行,如果是0%,那不管前面loss是100%还是80%都无所谓,那些都是节点限制导致的虚报

但如果最后一行是20%,再往前几行都是20%左右,那说明丢包就是从最接近的那一行开始产生的,长时间是这样,那很可能这一跳出了点问题。如果是公司内网的话,你可以带着这条线索去找对应的网络同事。如果是外网的话,那耐心点等等吧,别人家的开发会比你更着急。

发生丢包了怎么办

说了这么多。只是想告诉大家,丢包是很常见的,几乎不可避免的一件事情

但问题来了,发生丢包了怎么办?

这个好办,用TCP协议去做传输。

TCP是什么

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

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

这时候问题又来了。

假设某绿皮聊天软件用的就是TCP协议。

那文章开头提到的女生,她男朋友回她的消息时为什么还会丢包?毕竟丢包了会重试,重试失败了还会出现红色感叹号。

于是乎,问题就变成了,用了TCP协议,就一定不会丢包吗?

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

我们知道TCP位于传输层,在它的上面还有各种应用层协议,比如常见的HTTP或者各类RPC协议。

四层网络协议

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

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

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

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

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

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

于是乎,消息就丢了。

使用TCP协议却发生丢包

虽然概率很小,但它就是发生了

合情合理,逻辑自洽。

这类丢包问题怎么解决?

故事到这里也到尾声了,感动之余,我们来聊点掏心窝子的话

其实前面说的都对,没有一句是假话

但某绿皮聊天软件这么成熟,怎么可能没考虑过这一点呢。

大家应该还记得我们文章开头提到过,为了简单,就将服务器那一方给省略了,从三端通信变成了两端通信,所以才有了这个丢包问题。

现在我们重新将服务器加回来。

聊天软件三端通信

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

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

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

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

那么问题叒来了,两端通信的时候也能对账,为什么还要引入第三端服务器?

主要有三个原因。

  • 第一,如果是两端通信,你聊天软件里有1000个好友,你就得建立1000个连接。但如果引入服务端,你只需要跟服务器建立1个连接就够了,聊天软件消耗的资源越少,手机就越省电

  • 第二,就是安全问题,如果还是两端通信,随便一个人找你对账一下,你就把聊天记录给同步过去了,这并不合适吧。如果对方别有用心,信息就泄露了。引入第三方服务端就可以很方便的做各种鉴权校验。

  • 第三,是软件版本问题。软件装到用户手机之后,软件更不更新就是由用户说了算了。如果还是两端通信,且两端的软件版本跨度太大,很容易产生各种兼容性问题,但引入第三端服务器,就可以强制部分过低版本升级,否则不能使用软件。但对于大部分兼容性问题,给服务端加兼容逻辑就好了,不需要强制用户更新软件。

所以看到这里大家应该明白了,我把服务端去掉,并不单纯是为了简单

总结

  • 数据从发送端到接收端,链路很长,任何一个地方都可能发生丢包,几乎可以说丢包不可避免。

  • 平时没事也不用关注丢包,大部分时候TCP的重传机制保证了消息可靠性。

  • 当你发现服务异常的时候,比如接口延时很高,总是失败的时候,可以用ping或者mtr命令看下是不是中间链路发生了丢包。

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

用了 TCP 协议,数据一定不会丢吗?相关推荐

  1. 如何使用python读取modbus/TCP协议数据

    文章目录 前言 一.modbus_tk是什么? 二.modbus_tk的使用步骤 三.使用modscan测试 四.32位无符号短整型数据转为64位float数据 五.总结 前言 在做项目的时候,需要使 ...

  2. 以太网 TCP协议交互过程中出现丢包时的解决机制,超时重传、快速重传、SACK与DSACK

    2.7.3 以太网 TCP协议(TCP交互过程中出现丢包时的解决机制-列举部分) 参考:CSDN_TCP的重传机制_博主.Pr Young,对描述进行了整理与结合个人的理解进行编写. 一.超时重传机制 ...

  3. 简述tcp协议三报文握手过程_华为原理 | 传输层协议amp;交换转发原理

    Interface GigabitEthernet0/0/0 ip address 12.1.1.2 255.255.255.0 arp-proxy enable \\华为接口下默认没有开启代理ARP ...

  4. 用了TCP协议,就一定不会丢包嘛?

    表面上我是个技术博主. 但没想到今天成了个情感博主. 我是没想到有一天,我会通过技术知识,来挽救粉丝即将破碎的感情. 掏心窝子的说.这件事情多少是沾点功德无量了. 事情是这样的. 最近就有个读者加了我 ...

  5. 用了TCP协议,就一定不会丢包吗?

    表面上我是个技术博主. 但没想到今天成了个情感博主. 我是没想到有一天,我会通过技术知识,来挽救粉丝即将破碎的感情. 掏心窝子的说.这件事情多少是沾点功德无量了. 事情是这样的. 最近就有个读者加了我 ...

  6. 用了 TCP 协议,就一定不会丢包吗?

    她说她男朋友也是个程序员,异地恋,也关注了我,天天研究什么 TCP,UDP 网络.一研究就是一晚上,一晚上都不回她消息的那种. 话里有话,懂. 不出意外的出了意外,她发出了灵魂拷问 "你们程 ...

  7. Wireshark抓包——TCP协议分析

    一. 实验目的 通过本次实验,掌握使用Wireshark抓取TCP/IP协议数据包的技能,能够深入分析TCP帧格式及"TCP三次握手".通过抓包和分析数据包来理解TCP/IP协议, ...

  8. c/c++基于liunx平台tcp协议通信分数据段发送小demo

    引言*:tcp 是网络通信的一种协议,协议的话对于程序员来说就是按照约定的协议编写程序即可,具体编码的话相对于 之前做过的 socket 通信的 demo 来说 socket 通信来说并不会使用全新的 ...

  9. UDP的Socket发送数据,出现连续丢包现象(一)

    UDP丢包原因 一.主要丢包原因 1.接收端处理时间过长导致丢包:调用recv方法接收端收到数据后,处理数据花了一些时间,处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失.对于这种 ...

最新文章

  1. centos ruby通过rvm更新版本
  2. 计算机软件集成项目工程师上海,2021年上海系统集成项目管理工程师报名时间和报名入口...
  3. 判断字符串是否以指定字符开头
  4. 2020-05-02 自动控制原理常用术语
  5. ThinkPHP笔记——完全配置参考手册
  6. Symbian S60 3rd中的能力说明
  7. 如何防止插入删除表造成的数据库死锁
  8. CSS 特殊性、继承与层叠
  9. 多路转接select1
  10. React脚手架学习笔记
  11. LeetCode 1105. 填充书架(DP)
  12. python生成范围内随机数_如何使用Python中的pareto分布在specyfic范围内生成随机数...
  13. STM32 IWDG时间计算方法
  14. bzoj 2959: 长跑【LCT+并查集】
  15. 查找算法之三 插值查找(C++版本)
  16. hadoop中4种压缩格式的特征的比较
  17. 鸟哥的Linux私房菜PDF在线阅读
  18. 数据的提取方法 - 1
  19. 从科技创新到产业落地
  20. 使用python进行数据清洗常用的库_用于格式化和数据清理的便捷Python库

热门文章

  1. 跑步用什么蓝牙耳机比较好、跑步蓝牙耳机排名
  2. 疯狂的VR,热闹的内容,Cocos推动VR内容生态发展
  3. 联通iPhone无WiFi芯片是个不明智的选择
  4. Glew 配置 win7 64位 注意
  5. 传承——用双手打工创造的未来依旧可行
  6. MBA考研英语很重要青岛太奇亲情解读
  7. 风电场视频监控:如何实现风电场可视化、智慧化管理模式?
  8. <秒懂男女关系秘密的第一本书>
  9. RabbitMQ集群搭建、镜像队列、实现高可用负载均衡、Federation Exchange、Federation Queue、Shovel
  10. vscode 快速生成html模板