网络世界中的数据表交互咱们肉眼是看不见的,它们就好像隐形了同样,咱们对着课本学习计算机网络的时候就会以为很是的抽象,加大了学习的难度。web
还别说,我本身在大学的时候,也是如此。算法
直到工做后,认识了两大分析网络的利器:tcpdump 和 Wireshark,这两大利器把咱们“看不见”的数据表,呈如今咱们眼前,一目了然。apache
唉,当初大学学习计网的时候,要是能知道这两个工具,就不会学的一脸懵逼。缓存
tcpdump 和 Wireshark 有什么区别?
tcpdump 和 Wireshark 就是最经常使用的网络抓包和分析工具,更是分析网络性能必不可少的利器。服务器
因此,这二者其实是搭配使用的,先用 tcpdump 命令在 Linux 服务器上抓包,接着把抓包的文件拖出到 Windows 电脑后,用 Wireshark 可视化分析。网络
固然,若是你是在 Windows 上抓包,只须要用 Wireshark 工具就能够。ssh
tcpdump 在 Linux 下如何抓包?
tcpdump 提供了大量的选项以及各式各样的过滤表达式,来帮助你抓取指定的数据包,不过不要担忧,只须要掌握一些经常使用选项和过滤表达式,就能够知足大部分场景的须要了。curl
假设咱们要抓取下面的 ping 的数据包:tcp
要抓取上面的 ping 命令数据包,首先咱们要知道 ping 的数据包是 icmp协议,接着在使用 tcpdump 抓包的时候,就能够指定只抓 icmp 协议的数据表:工具
那么当 tcpdump 抓取到 icmp 数据包后, 输出格式以下:
从 tcpdump 抓取的 icmp 数据包,咱们很清楚的看到 icmp echo 的交互过程了,首先发送方发起了 ICMP echo request 请求报文,接收方收到后回了一个 ICMP echo reply 相应报文,以后 seq 是递增的。
我在这里也帮你整理了一些最多见的用法,而且绘制成了表格,你能够参考使用。
首先,先来看看经常使用的选项类,在上面的 ping 例子中,咱们用过 -i 选项指定网口,用过 -nn 选项不对 IP 地址和端口名称解析。其它经常使用的选项,以下表格:
tcpdump 经常使用选项类
接下来,咱们再来看看经常使用的过滤表用法,在上面的 ping 例子中,咱们用过的是 icmp and host 183.232.231.174,表示抓取 icmp 协议的数据包,以及源地址或目标地址为 183.232.231.174 的包。其余经常使用的过滤选项,我也整理成了下面这个表格。
tcpdump 经常使用过滤表达式类
说了这么多,你应该也发现了,tcpdump 虽然功能强大,可是输出的格式并不直观。
因此,在工做中 tcpdump 只是用来抓取数据包,不用来分析数据包,而是把 tcpdump 抓取的数据包保存成 pcap 后缀的文件,接着用 Wireshark 工具进行数据包分析。
Wireshark 工具如何分析数据包?
Wireshark 除了能够抓包外,还提供了可视化分析网络包的图形页面,同时,还内置了一系列的汇总分析工具。
好比,拿上面的 ping 例子来讲,咱们可使用下面的命令,把抓取的数据包保存到 ping.pcap 文件
接着把 ping.pcap 文件拖到电脑,再用 Wireshark 打开它。打开后,你就能够看到下面这个界面:
是吧?在 Wireshark 的页面里,能够更加直观的分析数据包,不只展现各个网络包的头部信息,还会用不一样的颜色来区分不一样的协议,因为此次抓包只有 ICMP 协议,因此只有紫色的条目。
接着,在网络包列表中选择某一个网络包后,在其下面的网络包详情中,能够更清楚的看到,这个网络包在协议栈各层的详细信息。好比,以编号 1 的网络包为例子:
ping 网络包
Wireshark 用了分层的方式,展现了各个层的包头信息,把“不可见”的数据包,清清楚楚的展现了给咱们,还有理由学很差计算机网络吗?是不是相见恨晚?
从 ping 的例子中,咱们能够看到网络分层就像有序的分工,每一层都有本身的责任范围和信息,上层协议完成工做后就交给下一层,最终造成一个完整的网络包。
既然学会了 tcpdump 和 Wireshark 两大网络分析利器,那咱们马不停蹄,接下用它俩抓取和分析 HTTP 协议网络包,并理解 TCP 三次握手和四次挥手的工做原理。
本次例子,咱们将要访问的 http://192.168.3.200 服务端。在终端一用 tcpdump 命令抓取数据包:
接着,在终端二执行下面的 curl 命令:
最后,回到终端一,按下 Ctrl+C 中止 tcpdump,并把获得的 http.pcap 取出到电脑。
使用 Wireshark 打开 http.pcap 后,你就能够在 Wireshark 中,看到以下的界面:
HTTP 网络包
咱们都知道 HTTP 是基于 TCP 协议进行传输的,那么:
Wireshark 能够用时序图的方式显示数据包交互的过程,从菜单栏中,点击 统计 (Statistics) -> 流量图 (Flow Graph),而后,在弹出的界面中的「流量类型」选择 「TCP Flows」,你能够更清晰的看到,整个过程当中 TCP 流的执行过程:
TCP 流量图
你可能会好奇,为何三次握手链接过程的 Seq 是 0 ?
其实是由于 Wireshark 工具帮咱们作了优化,它默认显示的是序列号 seq 是相对值,而不是真实值。
若是你想看到实际的序列号的值,能够右键菜单, 而后找到「协议首选项」,接着找到「Relative Seq」后,把它给取消,操做以下:
取消序列号相对值显示
取消后,Seq 显示的就是真实值了:
TCP 流量图
可见,客户端和服务端的序列号其实是不一样的,序列号是一个随机值。
这其实跟咱们书上看到的 TCP 三次握手和四次挥手很相似,做为对比,你一般看到的 TCP 三次握手和四次挥手的流程,基本是这样的:
TCP 三次握手和四次挥手的流程
为何抓到的 TCP 挥手是三次,而不是书上说的四次?
由于服务器端收到客户端的 FIN 后,服务器端同时也要关闭链接,这样就能够把 ACK 和 FIN 合并到一块儿发送,节省了一个包,变成了“三次挥手”。
而一般状况下,服务器端收到客户端的 FIN 后,极可能还没发送完数据,因此就会先回复客户端一个 ACK 包,稍等一下子,完成全部数据包的发送后,才会发送 FIN 包,这也就是四次挥手了。
以下图,就是四次挥手的过程:
四次挥手
TCP 三次握手的过程相信你们都背的倒背如流,那么你有没有想过这三个异常状况:
有的小伙伴可能说:“很简单呀,包丢了就会重传嘛。”
那我在继续问你:
是否是哑口无言,没法回答?
不知道不要紧,接下里我用三个实验案例,带你们一块儿探究探究这三种异常。
本次实验用了两台虚拟机,一台做为服务端,一台做为客户端,它们的关系以下:
实验环境
为了模拟 TCP 第一次握手 SYN 丢包的状况,我是在拔掉服务器的网线后,马上在客户端执行 curl 命令:
其间 tcpdump 抓包的命令以下:
过了一会, curl 返回了超时链接的错误:
从 date 返回的时间,能够发如今超时接近 1 分钟的时间后,curl 返回了错误。
接着,把 tcp_sys_timeout.pcap 文件用 Wireshark 打开分析,显示以下图:
SYN 超时重传五次
从上图能够发现, 客户端发起了 SYN 包后,一直没有收到服务端的 ACK ,因此一直超时重传了 5 次,而且每次 RTO 超时时间是不一样的:
能够发现,每次超时时间 RTO 是指数(翻倍)上涨的,当超过最大重传次数后,客户端再也不发送 SYN 包。
在 Linux 中,第一次握手的 SYN 超时重传次数,是以下内核参数指定的:
$ cat /proc/sys/net/ipv4/tcp_syn_retries
5
tcp_syn_retries 默认值为 5,也就是 SYN 最大重传次数是 5 次。
接下来,咱们继续作实验,把 tcp_syn_retries 设置为 2 次:
$ echo 2 > /proc/sys/net/ipv4/tcp_syn_retries
重传抓包后,用 Wireshark 打开分析,显示以下图:
SYN 超时重传两次
实验一的实验小结
经过实验一的实验结果,咱们能够得知,当客户端发起的 TCP 第一次握手 SYN 包,在超时时间内没收到服务端的 ACK,就会在超时重传 SYN 数据包,每次超时重传的 RTO 是翻倍上涨的,直到 SYN 包的重传次数到达tcp_syn_retries 值后,客户端再也不发送 SYN 包。
SYN 超时重传
为了模拟客户端收不到服务端第二次握手 SYN、ACK 包,个人作法是在客户端加上防火墙限制,直接粗暴的把来自服务端的数据都丢弃,防火墙的配置以下:
接着,在客户端执行 curl 命令:
从 date 返回的时间先后,能够算出大概 1 分钟后,curl 报错退出了。
客户端在这其间抓取的数据包,用 Wireshark 打开分析,显示的时序图以下:
从图中能够发现:
因此,咱们能够发现,当第二次握手的 SYN、ACK 丢包时,客户端会超时重发 SYN 包,服务端也会超时重传 SYN、ACK 包。
咦?客户端设置了防火墙,屏蔽了服务端的网络包,为何 tcpdump 还能抓到服务端的网络包?
添加 iptables 限制后, tcpdump 是否能抓到包 ,这要看添加的 iptables 限制条件:
网络包进入主机后的顺序以下:
tcp_syn_retries 是限制 SYN 重传次数,那第二次握手 SYN、ACK 限制最大重传次数是多少?
TCP 第二次握手 SYN、ACK 包的最大重传次数是经过tcp_synack_retries 内核参数限制的,其默认值以下:
$ cat /proc/sys/net/ipv4/tcp_synack_retries
5
是的,TCP 第二次握手 SYN、ACK 包的最大重传次数默认值是 5 次。
为了验证 SYN、ACK 包最大重传次数是 5 次,咱们继续作下实验,咱们先把客户端的 tcp_syn_retries 设置为 1,表示客户端 SYN 最大超时次数是 1 次,目的是为了防止屡次重传 SYN,把服务端 SYN、ACK 超时定时器重置。
接着,仍是如上面的步骤:
把抓取的数据包,用 Wireshark 打开分析,显示的时序图以下:
从上图,咱们能够分析出:
接着,我把 tcp_synack_retries 设置为 2,tcp_syn_retries 依然设置为 1:
$ echo 2 > /proc/sys/net/ipv4/tcp_synack_retries
$ echo 1 > /proc/sys/net/ipv4/tcp_syn_retries
依然保持同样的实验步骤进行操做,接着把抓取的数据包,用 Wireshark 打开分析,显示的时序图以下:
可见:
实验二的实验小结
经过实验二的实验结果,咱们能够得知,当 TCP 第二次握手 SYN、ACK 包丢了后,客户端 SYN 包会发生超时重传,服务端 SYN、ACK 也会发生超时重传。
客户端 SYN 包超时重传的最大次数,是由 tcp_syn_retries 决定的,默认值是 5 次;服务端 SYN、ACK 包时重传的最大次数,是由 tcp_synack_retries 决定的,默认值是 5 次。
为了模拟 TCP 第三次握手 ACK 包丢,个人实验方法是在服务端配置防火墙,屏蔽客户端 TCP 报文中标志位是 ACK 的包,也就是当服务端收到客户端的 TCP ACK 的报文时就会丢弃,iptables 配置命令以下:
接着,在客户端执行以下 tcpdump 命令:
而后,客户端向服务端发起 telnet,由于 telnet 命令是会发起 TCP 链接,因此用此命令作测试:
此时,因为服务端收不到第三次握手的 ACK 包,因此一直处于SYN_RECV 状态:
而客户端是已完成 TCP 链接创建,处于 ESTABLISHED 状态:
过了 1 分钟后,观察发现服务端的 TCP 链接不见了:
过了 30 分别,客户端依然仍是处于 ESTABLISHED 状态:
接着,在刚才客户端创建的 telnet 会话,输入 123456 字符,进行发送:
持续「好长」一段时间,客户端的 telnet 才断开链接:
以上就是本次的实现三的现象,这里存在两个疑点:
不着急,咱们把刚抓的数据包,用 Wireshark 打开分析,显示的时序图以下:
上图的流程:
经过这一波分析,刚才的两个疑点已经解除了:
TCP 第一次握手的 SYN 包超时重传最大次数是由 tcp_syn_retries 指定,TCP 第二次握手的 SYN、ACK 包超时重传最大次数是由 tcp_synack_retries 指定,那 TCP 创建链接后的数据包最大超时重传次数是由什么参数指定呢?
TCP 创建链接后的数据包传输,最大超时重传次数是由 tcp_retries2 指定,默认值是 15 次,以下:
$ cat /proc/sys/net/ipv4/tcp_retries2
15
若是 15 次重传都作完了,TCP 就会告诉应用层说:“搞不定了,包怎么都传不过去!”
那若是客户端不发送数据,何时才会断开处于 ESTABLISHED 状态的链接?
这里就须要提到 TCP 的 保护机制。这个机制的原理是这样的:
定义一个时间段,在这个时间段内,若是没有任何链接相关的活动,TCP 保活机制会开始做用,每隔一个时间间隔,发送一个「探测报文」,该探测报文包含的数据很是少,若是连续几个探测报文都没有获得响应,则认为当前的 TCP 链接已经死亡,系统内核将错误信息通知给上层应用程序。
在 Linux 内核能够有对应的参数能够设置保活时间、保活探测的次数、保活探测的时间间隔,如下都为默认值:
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
也就是说在 Linux 系统中,最少须要通过 2 小时 11 分 15 秒才能够发现一个「死亡」链接。
这个时间是有点长的,因此若是我抓包足够久,或许能抓到探测报文。
实验三的实验小结
在创建 TCP 链接时,若是第三次握手的 ACK,服务端没法收到,则服务端就会短暂处于 SYN_RECV 状态,而客户端会处于 ESTABLISHED 状态。
因为服务端一直收不到 TCP 第三次握手的 ACK,则会一直重传 SYN、ACK 包,直到重传次数超过 tcp_synack_retries 值(默认值 5 次)后,服务端就会断开 TCP 链接。
而客户端则会有两种状况:
客户端在向服务端发起 HTTP GET 请求时,一个完整的交互过程,须要 2.5 个 RTT 的时延。
因为第三次握手是能够携带数据的,这时若是在第三次握手发起 HTTP GET 请求,须要 2 个 RTT 的时延。
可是在下一次(不是同个 TCP 链接的下一次)发起 HTTP GET 请求时,经历的 RTT 也是同样,以下图:
常规 HTTP 请求
在 Linux 3.7 内核版本中,提供了 TCP Fast Open 功能,这个功能能够减小 TCP 链接创建的时延。
常规 HTTP 请求 与 Fast Open HTTP 请求
注:客户端在请求并存储了 Fast Open Cookie 以后,能够不断重复 TCP Fast Open 直至服务器认为 Cookie 无效(一般为过时)
在 Linux 上如何打开 Fast Open 功能?
能够经过设置 net.ipv4.tcp_fastopn 内核参数,来打开 Fast Open 功能。
net.ipv4.tcp_fastopn 各个值的意义:
TCP Fast Open 抓包分析
在下图,数据包 7 号,客户端发起了第二次 TCP 链接时,SYN 包会携带 Cooike,而且有长度为 5 的数据。
服务端收到后,校验 Cooike 合法,因而就回了 SYN、ACK 包,而且确认应答收到了客户端的数据包,ACK = 5 + 1 = 6
TCP Fast Open 抓包分析
当接收方收到乱序数据包时,会发送重复的 ACK,以使告知发送方要重发该数据包,当发送方收到 3 个重复 ACK 时,就会触发快速重传,立该重发丢失数据包。
快速重传机制
TCP 重复确认和快速重传的一个案例,用 Wireshark 分析,显示以下:
注意:快速重传和重复 ACK 标记信息是 Wireshark 的功能,非数据包自己的信息。
以上案例在 TCP 三次握手时协商开启了选择性确认 SACK,所以一旦数据包丢失并收到重复 ACK ,即便在丢失数据包以后还成功接收了其余数据包,也只须要重传丢失的数据包。若是不启用 SACK,就必须重传丢失包以后的每一个数据包。
若是要支持 SACK,必须双方都要支持。在 Linux 下,能够经过net.ipv4.tcp_sack 参数打开这个功能(Linux 2.4 后默认打开)。
TCP 为了防止发送方无脑的发送数据,致使接收方缓冲区被填满,因此就有了滑动窗口的机制,它可利用接收方的接收窗口来控制发送方要发送的数据量,也就是流量控制。
接收窗口是由接收方指定的值,存储在 TCP 头部中,它能够告诉发送方本身的 TCP 缓冲空间区大小,这个缓冲区是给应用程序读取数据的空间:
接收窗口的大小,是在 TCP 三次握手中协商好的,后续数据传输时,接收方发送确认应答 ACK 报文时,会携带当前的接收窗口的大小,以此来告知发送方。
假设接收方接收到数据后,应用层能很快的从缓冲区里读取数据,那么窗口大小会一直保持不变,过程以下:
理想状态下的窗口变化
可是现实中服务器会出现繁忙的状况,当应用程序读取速度慢,那么缓存空间会慢慢被占满,因而为了保证发送方发送的数据不会超过缓冲区大小,则服务器会调整窗口大小的值,接着经过 ACK 报文通知给对方,告知如今的接收窗口大小,从而控制发送方发送的数据大小。
服务端繁忙状态下的窗口变化
假设接收方处理数据的速度跟不上接收数据的速度,缓存就会被占满,从而致使接收窗口为 0,当发送方接收到零窗口通知时,就会中止发送数据。
以下图,能够接收方的窗口大小在不断的收缩至 0:
窗口大小在收缩
接着,发送方会定时发送窗口大小探测报文,以便及时知道接收方窗口大小的变化。
如下图 Wireshark 分析图做为例子说明:
零窗口 与 窗口探测
能够发现,这些窗口探测报文以 3.4s、6.5s、13.5s 的间隔出现,说明超时时间会翻倍递增。
这链接暂停了 25s,想象一下你在打王者的时候,25s 的延迟你还能上王者吗?
在 Wireshark 看到的 Windows size 也就是 " win = ",这个值表示发送窗口吗?
这不是发送窗口,而是在向对方声明本身的接收窗口。
你可能会好奇,抓包文件里有「Window size scaling factor」,它实际上是算出实际窗口大小的乘法因子,「Windos size value」实际上并非真实的窗口大小,真实窗口大小的计算公式以下:
「Windos size value」 * 「Window size scaling factor」 = 「Caculated window size 」
对应的下图案例,也就是 32 * 2048 = 65536。
其实是 Caculated window size 的值是 Wireshark 工具帮咱们算好的,Window size scaling factor 和 Windos size value 的值是在 TCP 头部中,其中 Window size scaling factor 是在三次握手过程当中肯定的,若是你抓包的数据没有 TCP 三次握手,那可能就没法算出真实的窗口大小的值,以下图:
如何在包里看出发送窗口的大小?
很遗憾,没有简单的办法,发送窗口虽然是由接收窗口决定,可是它又能够被网络因素影响,也就是拥塞窗口,实际上发送窗口是值是 min(拥塞窗口,接收窗口)。
发送窗口和 MSS 有什么关系?
发送窗口决定了一口气能发多少字节,而 MSS 决定了这些字节要分多少包才能发完。
举个例子,若是发送窗口为 16000 字节的状况下,若是 MSS 是 1000 字节,那就须要发送 1600/1000 = 16 个包。
发送方在一个窗口发出 n 个包,是否是须要 n 个 ACK 确认报文?
不必定,由于 TCP 有累计确认机制,因此当收到多个数据包时,只须要应答最后一个数据包的 ACK 报文就能够了。
当咱们 TCP 报文的承载的数据很是小的时候,例如几个字节,那么整个网络的效率是很低的,由于每一个 TCP 报文中都有会 20 个字节的 TCP 头部,也会有 20 个字节的 IP 头部,而数据只有几个字节,因此在整个报文中有效数据占有的比重就会很是低。
这就好像快递员开着大货车送一个小包裹同样浪费。
那么就出现了常见的两种策略,来减小小报文的传输,分别是:
Nagle 算法是如何避免大量 TCP 小数据报文的传输?
Nagle 算法作了一些策略来避免过多的小数据报文发送,这可提升传输效率。
Nagle 算法的策略:
只要没知足上面条件中的一条,发送方一直在囤积数据,直到知足上面的发送条件。
禁用 Nagle 算法 与 启用 Nagle 算法
上图右侧启用了 Nagle 算法,它的发送数据的过程:
能够看出,Nagle 算法必定会有一个小报文,也就是在最开始的时候。
另外,Nagle 算法默认是打开的,若是对于一些须要小数据包交互的场景的程序,好比,telnet 或 ssh 这样的交互性比较强的程序,则须要关闭 Nagle 算法。
能够在 Socket 设置 TCP_NODELAY 选项来关闭这个算法(关闭 Nagle 算法没有全局参数,须要根据每一个应用本身的特色来关闭)。
关闭 Nagle 算法
那延迟确认又是什么?
事实上当没有携带数据的 ACK,它的网络效率也是很低的,由于它也有 40 个字节的 IP 头 和 TCP 头,但没有携带数据。
为了解决 ACK 传输效率低问题,因此就衍生出了 TCP 延迟确认。
TCP 延迟确认的策略:
TCP 延迟确认
延迟等待的时间是在 Linux 内核中的定义的,以下图:
关键就须要 HZ 这个数值大小,HZ 是跟系统的时钟频率有关,每一个操做系统都不同,在个人 Linux 系统中 HZ 大小是 1000,以下图:
知道了 HZ 的大小,那么就能够算出:
TCP 延迟确承认以在 Socket 设置 TCP_QUICKACK 选项来关闭这个算法。
关闭 TCP 延迟确认
延迟确认 和 Nagle 算法混合使用时,会产生新的问题
当 TCP 延迟确认 和 Nagle 算法混合使用时,会致使时耗增加,以下图:
TCP 延迟确认 和 Nagle 算法混合使用
发送方使用了 Nagle 算法,接收方使用了 TCP 延迟确认会发生以下的过程:
很明显,这两个同时使用会形成额外的时延,这就会使得网络"很慢"的感受。
要解决这个问题,只有两个办法: