话不多说,直接上图看内容 显形不可见的网络包 网络世界中的数据包交互我们肉眼是看不见的,它们就好像隐形了一样,我们对着课本学习计算机网络的时候就会觉得非常的抽象,加大了学习的难度。 但莫慌,自从有了两大分析网络包的利器:tcpdump和Wireshark,我们看不见的数据包,再也没有那么神秘了。 唉,当初大学学习计网的时候,要是能知道这两个工具,就不会学的一脸懵逼。 tcpdump和Wireshark有什么区别? tcpdump和Wireshark就是最常用的网络抓包和分析工具,更是分析网络性能必不可少的利器。tcpdump仅支持命令行格式使用,常用在Linux服务器中抓取和分析网络包。Wireshark除了可以抓包外,还提供了可视化分析网络包的图形页面。 所以,这两者实际上是搭配使用的,先用tcpdump命令在Linux服务器上抓包,接着把抓包的文件拖出到Windows电脑后,用Wireshark可视化分析。 当然,如果你是在Windows上抓包,只需要用Wireshark工具就可以。 tcpdump在Linux下如何抓包? tcpdump提供了大量的选项以及各式各样的过滤表达式,来帮助你抓取指定的数据包,不过不要担心,只需要掌握一些常用选项和过滤表达式,就可以满足大部分场景的需要了。 假设我们要抓取下面的ping的数据包: 要抓取上面的ping命令数据包,首先我们要知道ping的数据包是icmp协议,接着在使用tcpdump抓包的时候,就可以指定只抓icmp协议的数据包: 那么当tcpdump抓取到icmp数据包后,输出格式如下: 从tcpdump抓取的icmp数据包,我们很清楚的看到icmpecho的交互过程了,首先发送方发起了ICMPechorequest请求报文,接收方收到后回了一个ICMPechoreply响应报文,之后seq是递增的。 我在这里也帮你整理了一些最常见的用法,并且绘制成了表格,你可以参考使用。 首先,先来看看常用的选项类,在上面的ping例子中,我们用过i选项指定网口,用过nn选项不对IP地址和端口名称解析。其他常用的选项,如下表格: 接下来,我们再来看看常用的过滤表用法,在上面的ping例子中,我们用过的是icmpandhost183。232。231。174,表示抓取icmp协议的数据包,以及源地址或目标地址为183。232。231。174的包。其他常用的过滤选项,我也整理成了下面这个表格。 说了这么多,你应该也发现了,tcpdump虽然功能强大,但是输出的格式并不直观。 所以,在工作中tcpdump只是用来抓取数据包,不用来分析数据包,而是把tcpdump抓取的数据包保存成pcap后缀的文件,接着用Wireshark工具进行数据包分析。 Wireshark工具如何分析数据包? Wireshark除了可以抓包外,还提供了可视化分析网络包的图形页面,同时,还内置了一系列的汇总分析工具。 比如,拿上面的ping例子来说,我们可以使用下面的命令,把抓取的数据包保存到ping。pcap文件 接着把ping。pcap文件拖到电脑,再用Wireshark打开它。打开后,你就可以看到下面这个界面: 是吧?在Wireshark的页面里,可以更加直观的分析数据包,不仅展示各个网络包的头部信息,还会用不同的颜色来区分不同的协议,由于这次抓包只有ICMP协议,所以只有紫色的条目。 接着,在网络包列表中选择某一个网络包后,在其下面的网络包详情中,可以更清楚的看到,这个网络包在协议栈各层的详细信息。比如,以编号1的网络包为例子: 可以在数据链路层,看到MAC包头信息,如源MAC地址和目标MAC地址等字段;可以在IP层,看到IP包头信息,如源IP地址和目标IP地址、TTL、IP包长度、协议等IP协议各个字段的数值和含义;可以在ICMP层,看到ICMP包头信息,比如Type、Code等ICMP协议各个字段的数值和含义; Wireshark用了分层的方式,展示了各个层的包头信息,把不可见的数据包,清清楚楚的展示了给我们,还有理由学不好计算机网络吗?是不是相见恨晚? 从ping的例子中,我们可以看到网络分层就像有序的分工,每一层都有自己的责任范围和信息,上层协议完成工作后就交给下一层,最终形成一个完整的网络包。 解密TCP三次握手和四次挥手 既然学会了tcpdump和Wireshark两大网络分析利器,那我们快马加鞭,接下来用它俩抓取和分析HTTP协议网络包,并理解TCP三次握手和四次挥手的工作原理。 本次例子,我们将要访问的http:192。168。3。200服务端。在终端一用tcpdump命令抓取数据包: 接着,在终端二执行下面的curl命令: 最后,回到终端一,按下CtrlC停止tcpdump,并把得到的http。pcap取出到电脑。 使用Wireshark打开http。pcap后,你就可以在Wireshark中,看到如下的界面: 我们都知道HTTP是基于TCP协议进行传输的,那么:最开始的3个包就是TCP三次握手建立连接的包中间是HTTP请求和响应的包而最后的3个包则是TCP断开连接的挥手包 Wireshark可以用时序图的方式显示数据包交互的过程,从菜单栏中,点击统计(Statistics)流量图(FlowGraph),然后,在弹出的界面中的流量类型选择TCPFlows,你可以更清晰的看到,整个过程中TCP流的执行过程: 你可能会好奇,为什么三次握手连接过程的Seq是0? 实际上是因为Wireshark工具帮我们做了优化,它默认显示的是序列号seq是相对值,而不是真实值。 如果你想看到实际的序列号的值,可以右键菜单,然后找到协议首选项,接着找到RelativeSeq后,把它给取消,操作如下: 取消后,Seq显示的就是真实值了: 可见,客户端和服务端的序列号实际上是不同的,序列号是一个随机值。 这其实跟我们书上看到的TCP三次握手和四次挥手很类似,作为对比,你通常看到的TCP三次握手和四次挥手的流程,基本是这样的: 为什么抓到的TCP挥手是三次,而不是书上说的四次? 当被动关闭方(上图的服务端)在TCP挥手过程中,没有数据要发送并且开启了TCP延迟确认机制,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。 而通常情况下,服务器端收到客户端的FIN后,很可能还没发送完数据,所以就会先回复客户端一个ACK包,稍等一会儿,完成所有数据包的发送后,才会发送FIN包,这也就是四次挥手了。 TCP三次握手异常情况实战分析 TCP三次握手的过程相信大家都背的滚瓜烂熟,那么你有没有想过这三个异常情况:TCP第一次握手的SYN丢包了,会发生了什么?TCP第二次握手的SYN、ACK丢包了,会发生什么?TCP第三次握手的ACK包丢了,会发生什么? 有的小伙伴可能说:很简单呀,包丢了就会重传嘛。 那我在继续问你:那会重传几次?超时重传的时间RTO会如何变化?在Linux下如何设置重传次数?。。。。 是不是哑口无言,无法回答? 不知道没关系,接下里我用三个实验案例,带大家一起探究探究这三种异常。 实验场景 本次实验用了两台虚拟机,一台作为服务端,一台作为客户端,它们的关系如下: 客户端和服务端都是CentOs6。5Linux,Linux内核版本2。6。32服务端192。168。12。36,apacheweb服务客户端192。168。12。37 实验一:TCP第一次握手SYN丢包 为了模拟TCP第一次握手SYN丢包的情况,我是在拔掉服务器的网线后,立刻在客户端执行curl命令: 其间tcpdump抓包的命令如下: 过了一会,curl返回了超时连接的错误: 从date返回的时间,可以发现在超时接近1分钟的时间后,curl返回了错误。 接着,把tcpsystimeout。pcap文件用Wireshark打开分析,显示如下图: 从上图可以发现,客户端发起了SYN包后,一直没有收到服务端的ACK,所以一直超时重传了5次,并且每次RTO超时时间是不同的:第一次是在1秒超时重传第二次是在3秒超时重传第三次是在7秒超时重传第四次是在15秒超时重传第五次是在31秒超时重传 可以发现,每次超时时间RTO是指数(翻倍)上涨的,当超过最大重传次数后,客户端不再发送SYN包。 在Linux中,第一次握手的SYN超时重传次数,是如下内核参数指定的: tcpsynretries默认值为5,也就是SYN最大重传次数是5次。 接下来,我们继续做实验,把tcpsynretries设置为2次:echo2procsysnetipv4tcpsynretries 重传抓包后,用Wireshark打开分析,显示如下图: 实验一的实验小结 通过实验一的实验结果,我们可以得知,当客户端发起的TCP第一次握手SYN包,在超时时间内没收到服务端的ACK,就会在超时重传SYN数据包,每次超时重传的RTO是翻倍上涨的,直到SYN包的重传次数到达tcpsynretries值后,客户端不再发送SYN包。 实验二:TCP第二次握手SYN、ACK丢包 为了模拟客户端收不到服务端第二次握手SYN、ACK包,我的做法是在客户端加上防火墙限制,直接粗暴的把来自服务端的数据都丢弃,防火墙的配置如下: 接着,在客户端执行curl命令: 从date返回的时间前后,可以算出大概1分钟后,curl报错退出了。 客户端在这其间抓取的数据包,用Wireshark打开分析,显示的时序图如下: 从图中可以发现:客户端发起SYN后,由于防火墙屏蔽了服务端的所有数据包,所以curl是无法收到服务端的SYN、ACK包,当发生超时后,就会重传SYN包服务端收到客户的SYN包后,就会回SYN、ACK包,但是客户端一直没有回ACK,服务端在超时后,重传了SYN、ACK包,接着一会,客户端超时重传的SYN包又抵达了服务端,服务端收到后,然后回了SYN、ACK包,但是SYN、ACK包的重传定时器并没有重置,还持续在重传,因为第二次握手在没收到第三次握手的ACK确认报文时,就会重传到最大次数。最后,客户端SYN超时重传次数达到了5次(tcpsynretries默认值5次),就不再继续发送SYN包了。 所以,我们可以发现,当第二次握手的SYN、ACK丢包时,客户端会超时重发SYN包,服务端也会超时重传SYN、ACK包。 咦?客户端设置了防火墙,屏蔽了服务端的网络包,为什么tcpdump还能抓到服务端的网络包? 添加iptables限制后,tcpdump是否能抓到包,这要看添加的iptables限制条件:如果添加的是INPUT规则,则可以抓得到包如果添加的是OUTPUT规则,则抓不到包 网络包进入主机后的顺序如下:进来的顺序WireNICtcpdumpnetfilteriptables出去的顺序iptablestcpdumpNICWire tcpsynretries是限制SYN重传次数,那第二次握手SYN、ACK限制最大重传次数是多少? TCP第二次握手SYN、ACK包的最大重传次数是通过tcpsynackretries内核参数限制的,其默认值如下:catprocsysnetipv4tcpsynackretries5 是的,TCP第二次握手SYN、ACK包的最大重传次数默认值是5次。 为了验证SYN、ACK包最大重传次数是5次,我们继续做下实验,我们先把客户端的tcpsynretries设置为1,表示客户端SYN最大超时次数是1次,目的是为了防止多次重传SYN,把服务端SYN、ACK超时定时器重置。 接着,还是如上面的步骤:客户端配置防火墙屏蔽服务端的数据包客户端tcpdump抓取curl执行时的数据包 把抓取的数据包,用Wireshark打开分析,显示的时序图如下: 从上图,我们可以分析出:客户端的SYN只超时重传了1次,因为tcpsynretries值为1服务端应答了客户端超时重传的SYN包后,由于一直收不到客户端的ACK包,所以服务端一直在超时重传SYN、ACK包,每次的RTO也是指数上涨的,一共超时重传了5次,因为tcpsynackretries值为5 接着,我把tcpsynackretries设置为2,tcpsynretries依然设置为1:echo2procsysnetipv4tcpsynackretriesecho1procsysnetipv4tcpsynretries 依然保持一样的实验步骤进行操作,接着把抓取的数据包,用Wireshark打开分析,显示的时序图如下: 可见:客户端的SYN包只超时重传了1次,符合tcpsynretries设置的值;服务端的SYN、ACK超时重传了2次,符合tcpsynackretries设置的值 实验二的实验小结 通过实验二的实验结果,我们可以得知,当TCP第二次握手SYN、ACK包丢了后,客户端SYN包会发生超时重传,服务端SYN、ACK也会发生超时重传。 客户端SYN包超时重传的最大次数,是由tcpsynretries决定的,默认值是5次;服务端SYN、ACK包时重传的最大次数,是由tcpsynackretries决定的,默认值是5次。 实验三:TCP第三次握手ACK丢包 为了模拟TCP第三次握手ACK包丢,我的实验方法是在服务端配置防火墙,屏蔽客户端TCP报文中标志位是ACK的包,也就是当服务端收到客户端的TCPACK的报文时就会丢弃。 iptables配置命令如下: 接着,在客户端执行如下tcpdump命令: 然后,客户端向服务端发起telnet,因为telnet命令是会发起TCP连接,所以用此命令做测试: 此时,由于服务端收不到第三次握手的ACK包,所以一直处于SYNRECV状态: 而客户端是已完成TCP连接建立,处于ESTABLISHED状态: 过了1分钟后,观察发现服务端的TCP连接不见了: 过了30分钟,客户端依然还是处于ESTABLISHED状态: 接着,在刚才客户端建立的telnet会话,输入123456字符,进行发送: 持续好长一段时间,客户端的telnet才断开连接: 以上就是本次的实现三的现象,这里存在两个疑点:为什么服务端原本处于SYNRECV状态的连接,过1分钟后就消失了?为什么客户端telnet输入123456字符后,过了好长一段时间,telnet才断开连接? 不着急,我们把刚抓的数据包,用Wireshark打开分析,显示的时序图如下: 上图的流程:客户端发送SYN包给服务端,服务端收到后,回了个SYN、ACK包给客户端,此时服务端的TCP连接处于SYNRECV状态;客户端收到服务端的SYN、ACK包后,给服务端回了个ACK包,此时客户端的TCP连接处于ESTABLISHED状态;由于服务端配置了防火墙,屏蔽了客户端的ACK包,所以服务端一直处于SYNRECV状态,没有进入ESTABLISHED状态,tcpdump之所以能抓到客户端的ACK包,是因为数据包进入系统的顺序是先进入tcpudmp,后经过iptables;接着,服务端超时重传了SYN、ACK包,重传了5次后,也就是超过tcpsynackretries的值(默认值是5),然后就没有继续重传了,此时服务端的TCP连接主动中止了,所以刚才处于SYNRECV状态的TCP连接断开了,而客户端依然处于ESTABLISHED状态;虽然服务端TCP断开了,但过了一段时间,发现客户端依然处于ESTABLISHED状态,于是就在客户端的telnet会话输入了123456字符;由于服务端的防火墙配置了屏蔽所有携带ACK标志位的TCP报文,客户端发送的数据报文,服务端并不会接收,而是丢弃(如果服务端没有设置防火墙,由于服务端已经断开连接,此时收到客户的发来的数据报文后,会回RST报文)。客户端由于一直收不到数据报文的确认报文,所以触发超时重传,在超时重传过程中,每一次重传,RTO的值是指数增长的,所以持续了好长一段时间,客户端的telnet才报错退出了,此时共重传了15次,然后客户端的也断开了连接。 通过这一波分析,刚才的两个疑点已经解除了:服务端在重传SYN、ACK包时,超过了最大重传次数tcpsynackretries,于是服务端的TCP连接主动断开了。客户端向服务端发送数据报文时,如果迟迟没有收到数据包的确认报文,也会触发超时重传,一共重传了15次数据报文,最后telnet就断开了连接。 TCP第一次握手的SYN包超时重传最大次数是由tcpsynretries指定,TCP第二次握手的SYN、ACK包超时重传最大次数是由tcpsynackretries指定,那TCP建立连接后的数据包最大超时重传次数是由什么参数指定呢? TCP建立连接后的数据包传输,最大超时重传次数是由tcpretries2指定,默认值是15次,如下:catprocsysnetipv4tcpretries215 如果15次重传都做完了,TCP就会告诉应用层说:搞不定了,包怎么都传不过去! 那如果客户端不发送数据,什么时候才会断开处于ESTABLISHED状态的连接? 这里就需要提到TCP的保活机制。这个机制的原理是这样的: 定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的TCP连接已经死亡,系统内核将错误信息通知给上层应用程序。 在Linux内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:net。ipv4。tcpkeepalivetime7200net。ipv4。tcpkeepaliveintvl75net。ipv4。tcpkeepaliveprobes9tcpkeepalivetime7200:表示保活时间是7200秒(2小时),也就2小时内如果没有任何连接相关的活动,则会启动保活机制tcpkeepaliveintvl75:表示每次检测间隔75秒;tcpkeepaliveprobes9:表示检测9次无响应,认为对方是不可达的,从而中断本次的连接。 也就是说在Linux系统中,最少需要经过2小时11分15秒才可以发现一个死亡连接。 这个时间是有点长的,所以如果我抓包足够久,或许能抓到探测报文。 实验三的实验小结 在建立TCP连接时,如果第三次握手的ACK,服务端无法收到,则服务端就会短暂处于SYNRECV状态,而客户端会处于ESTABLISHED状态。 由于服务端一直收不到TCP第三次握手的ACK,则会一直重传SYN、ACK包,直到重传次数超过tcpsynackretries值(默认值5次)后,服务端就会断开TCP连接。 而客户端则会有两种情况:如果客户端没发送数据包,一直处于ESTABLISHED状态,然后经过2小时11分15秒才可以发现一个死亡连接,于是客户端连接就会断开连接。如果客户端发送了数据包,一直没有收到服务端对该数据包的确认报文,则会一直重传该数据包,直到重传次数超过tcpretries2值(默认值15次)后,客户端就会断开TCP连接。 TCP快速建立连接 客户端在向服务端发起HTTPGET请求时,一个完整的交互过程,需要2。5个RTT的时延。 由于第三次握手是可以携带数据的,这时如果在第三次握手发起HTTPGET请求,需要2个RTT的时延。 但是在下一次(不是同个TCP连接的下一次)发起HTTPGET请求时,经历的RTT也是一样,如下图: 在Linux3。7内核版本中,提供了TCPFastOpen功能,这个功能可以减少TCP连接建立的时延。 在第一次建立连接的时候,服务端在第二次握手产生一个Cookie(已加密)并通过SYN、ACK包一起发给客户端,于是客户端就会缓存这个Cookie,所以第一次发起HTTPGet请求的时候,还是需要2个RTT的时延;在下次请求的时候,客户端在SYN包带上Cookie发给服务端,就提前可以跳过三次握手的过程,因为Cookie中维护了一些信息,服务端可以从Cookie获取TCP相关的信息,这时发起的HTTPGET请求就只需要1个RTT的时延; 注:客户端在请求并存储了FastOpenCookie之后,可以不断重复TCPFastOpen直至服务器认为Cookie无效(通常为过期) 在Linux上如何打开FastOpen功能? 可以通过设置net。ipv4。tcpfastopn内核参数,来打开FastOpen功能。 net。ipv4。tcpfastopn各个值的意义:0关闭1作为客户端使用FastOpen功能2作为服务端使用FastOpen功能3无论作为客户端还是服务器,都可以使用FastOpen功能 TCPFastOpen抓包分析 在下图,数据包7号,客户端发起了第二次TCP连接时,SYN包会携带Cooike,并且长度为5的数据。 服务端收到后,校验Cooike合法,于是就回了SYN、ACK包,并且确认应答收到了客户端的数据包,ACK516 TCP重复确认和快速重传 当接收方收到乱序数据包时,会发送重复的ACK,以便告知发送方要重发该数据包,当发送方收到3个重复ACK时,就会触发快速重传,立刻重发丢失数据包。 TCP重复确认和快速重传的一个案例,用Wireshark分析,显示如下: 数据包1期望的下一个数据包Seq是1,但是数据包2发送的Seq却是10945,说明收到的是乱序数据包,于是回了数据包3,还是同样的Seq1,Ack1,这表明是重复的ACK;数据包4和6依然是乱序的数据包,于是依然回了重复的ACK;当对方收到三次重复的ACK后,于是就快速重传了Seq1、Len1368的数据包8;当收到重传的数据包后,发现Seq1是期望的数据包,于是就发送了个确认收到快速重传的ACK 注意:快速重传和重复ACK标记信息是Wireshark的功能,非数据包本身的信息。 以上案例在TCP三次握手时协商开启了选择性确认SACK,因此一旦数据包丢失并收到重复ACK,即使在丢失数据包之后还成功接收了其他数据包,也只需要重传丢失的数据包。如果不启用SACK,就必须重传丢失包之后的每个数据包。 如果要支持SACK,必须双方都要支持。在Linux下,可以通过net。ipv4。tcpsack参数打开这个功能(Linux2。4后默认打开)。 TCP流量控制 TCP为了防止发送方无脑的发送数据,导致接收方缓冲区被填满,所以就有了滑动窗口的机制,它可利用接收方的接收窗口来控制发送方要发送的数据量,也就是流量控制。 接收窗口是由接收方指定的值,存储在TCP头部中,它可以告诉发送方自己的TCP缓冲空间区大小,这个缓冲区是给应用程序读取数据的空间:如果应用程序读取了缓冲区的数据,那么缓冲空间区就会把被读取的数据移除如果应用程序没有读取数据,则数据会一直滞留在缓冲区。 接收窗口的大小,是在TCP三次握手中协商好的,后续数据传输时,接收方发送确认应答ACK报文时,会携带当前的接收窗口的大小,以此来告知发送方。 假设接收方接收到数据后,应用层能很快的从缓冲区里读取数据,那么窗口大小会一直保持不变,过程如下: 但是现实中服务器会出现繁忙的情况,当应用程序读取速度慢,那么缓存空间会慢慢被占满,于是为了保证发送方发送的数据不会超过缓冲区大小,服务器则会调整窗口大小的值,接着通过ACK报文通知给对方,告知现在的接收窗口大小,从而控制发送方发送的数据大小。 零窗口通知与窗口探测 假设接收方处理数据的速度跟不上接收数据的速度,缓存就会被占满,从而导致接收窗口为0,当发送方接收到零窗口通知时,就会停止发送数据。 如下图,可以看到接收方的窗口大小在不断的收缩至0: 接着,发送方会定时发送窗口大小探测报文,以便及时知道接收方窗口大小的变化。 以下图Wireshark分析图作为例子说明: 发送方发送了数据包1给接收方,接收方收到后,由于缓冲区被占满,回了个零窗口通知;发送方收到零窗口通知后,就不再发送数据了,直到过了3。4秒后,发送了一个TCPKeepAlive报文,也就是窗口大小探测报文;当接收方收到窗口探测报文后,就立马回一个窗口通知,但是窗口大小还是0;发送方发现窗口还是0,于是继续等待了6。8(翻倍)秒后,又发送了窗口探测报文,接收方依然还是回了窗口为0的通知;发送方发现窗口还是0,于是继续等待了13。5(翻倍)秒后,又发送了窗口探测报文,接收方依然还是回了窗口为0的通知; 可以发现,这些窗口探测报文以3。4s、6。5s、13。5s的间隔出现,说明超时时间会翻倍递增。 这连接暂停了25s,想象一下你在打王者的时候,25s的延迟你还能上王者吗? 发送窗口的分析 在Wireshark看到的Windowssize也就是win,这个值表示发送窗口吗? 这不是发送窗口,而是在向对方声明自己的接收窗口。 你可能会好奇,抓包文件里有Windowsizescalingfactor,它其实是算出实际窗口大小的乘法因子,Windowsizevalue实际上并不是真实的窗口大小,真实窗口大小的计算公式如下: WindowsizevalueWindowsizescalingfactorCaculatedwindowsize 对应的下图案例,也就是32204865536。 实际上是Caculatedwindowsize的值是Wireshark工具帮我们算好的,Windowsizescalingfactor和Windossizevalue的值是在TCP头部中,其中Windowsizescalingfactor是在三次握手过程中确定的,如果你抓包的数据没有TCP三次握手,那可能就无法算出真实的窗口大小的值,如下图: 如何在包里看出发送窗口的大小? 很遗憾,没有简单的办法,发送窗口虽然是由接收窗口决定,但是它又可以被网络因素影响,也就是拥塞窗口,实际上发送窗口是值是min(拥塞窗口,接收窗口)。 发送窗口和MSS有什么关系? 发送窗口决定了一口气能发多少字节,而MSS决定了这些字节要分多少包才能发完。 举个例子,如果发送窗口为16000字节的情况下,如果MSS是1000字节,那就需要发送1600100016个包。 发送方在一个窗口发出n个包,是不是需要n个ACK确认报文? 不一定,因为TCP有累计确认机制,所以当收到多个数据包时,只需要应答最后一个数据包的ACK报文就可以了。 TCP延迟确认与Nagle算法 当我们TCP报文的承载的数据非常小的时候,例如几个字节,那么整个网络的效率是很低的,因为每个TCP报文中都会有20个字节的TCP头部,也会有20个字节的IP头部,而数据只有几个字节,所以在整个报文中有效数据占有的比重就会非常低。 这就好像快递员开着大货车送一个小包裹一样浪费。 那么就出现了常见的两种策略,来减少小报文的传输,分别是:Nagle算法延迟确认 Nagle算法是如何避免大量TCP小数据报文的传输? Nagle算法做了一些策略来避免过多的小数据报文发送,这可提高传输效率。 Nagle伪代码如下:if有数据要发送{if可用窗口大小MSSand可发送的数据MSS{立刻发送MSS大小的数据}else{if有未确认的数据{将数据放入缓存等待接收ACK}else{立刻发送数据}}} 使用Nagle算法,该算法的思路是延时处理,只有满足下面两个条件中的任意一个条件,才能可以发送数据:条件一:要等到窗口大小MSS并且数据大小MSS;条件二:收到之前发送数据的ack回包; 只要上面两个条件都不满足,发送方一直在囤积数据,直到满足上面的发送条件。 上图右侧启用了Nagle算法,它的发送数据的过程:一开始由于没有已发送未确认的报文,所以就立刻发了H字符;接着,在还没收到对H字符的确认报文时,发送方就一直在囤积数据,直到收到了确认报文后,此时没有已发送未确认的报文,于是就把囤积后的ELL字符一起发给了接收方;待收到对ELL字符的确认报文后,于是把最后一个O字符发送了出去 可以看出,Nagle算法一定会有一个小报文,也就是在最开始的时候。 另外,Nagle算法默认是打开的,如果对于一些需要小数据包交互的场景的程序,比如,telnet或ssh这样的交互性比较强的程序,则需要关闭Nagle算法。 可以在Socket设置TCPNODELAY选项来关闭这个算法(关闭Nagle算法没有全局参数,需要根据每个应用自己的特点来关闭)。 那延迟确认又是什么? 事实上当没有携带数据的ACK,它的网络效率也是很低的,因为它也有40个字节的IP头和TCP头,但却没有携带数据报文。 为了解决ACK传输效率低问题,所以就衍生出了TCP延迟确认。 TCP延迟确认的策略:当有响应数据要发送时,ACK会随着响应数据一起立刻发送给对方当没有响应数据要发送时,ACK将会延迟一段时间,以等待是否有响应数据可以一起发送如果在延迟等待发送ACK期间,对方的第二个数据报文又到达了,这时就会立刻发送ACK 延迟等待的时间是在Linux内核中定义的,如下图: 关键就需要HZ这个数值大小,HZ是跟系统的时钟频率有关,每个操作系统都不一样,在我的Linux系统中HZ大小是1000,如下图: 知道了HZ的大小,那么就可以算出:最大延迟确认时间是200ms(10005)最短延迟确认时间是40ms(100025) TCP延迟确认可以在Socket设置TCPQUICKACK选项来关闭这个算法。 延迟确认和Nagle算法混合使用时,会产生新的问题 当TCP延迟确认和Nagle算法混合使用时,会导致时耗增长,如下图: 发送方使用了Nagle算法,接收方使用了TCP延迟确认会发生如下的过程:发送方先发出一个小报文,接收方收到后,由于延迟确认机制,自己又没有要发送的数据,只能干等着发送方的下一个报文到达;而发送方由于Nagle算法机制,在未收到第一个报文的确认前,是不会发送后续的数据;所以接收方只能等待最大时间200ms后,才回ACK报文,发送方收到第一个报文的确认报文后,也才可以发送后续的数据。 很明显,这两个同时使用会造成额外的时延,这就会使得网络很慢的感觉。 要解决这个问题,只有两个办法:要不发送方关闭Nagle算法要不接收方关闭TCP延迟确认 参考资料: 〔1〕Wireshark网络分析的艺术。林沛满。人民邮电出版社。 〔2〕Wireshark网络分析就这么简单。林沛满。人民邮电出版社。 〔3〕Wireshark数据包分析实战。ChrisSanders。人民邮电出版社。读者问答