本篇是上篇《面试中的TCP/UDP协议》的姊妹篇,上篇简单从概念的角度把TCP/UP协议的格式、特性解释了一下,但并没有在实际场景中看到TCP/UDP报文的格式,而走马观花的过一遍只是死记硬背,对于一些易混点还是一知半解,比如:
为了感受实际场景中的TCP/UDP的格式,专门用wireshark来抓包, 实战分析TCP、UDP的协议内容。
看完本文会了解到的知识点:
这里以我用浏览器访问一次https://www.baidu.com为例, 用wireshark进行抓包,重点看TCP协议在实际场景中的应用。
wireshark的自动解析很不错,每个标志位的意义数值都标出来了。
为了更好的理解这几个返回结果的关系,先了解下A记录和CNAME记录
A记录是用来指定主机名(或域名)对应的IP地址记录。
CNAME记录用作别名,相当于域名之间的映射(例如www.baidu.com 映射到www.a.shifen.com),而不直接与IP地址对应。
在访问www.baidu.com的时候,dns协议从dns服务器进行解析,查询到www.baidu.com到www.a.shifen.com的映射记录,而与真正IP地址(180.101.49.11, 180.101.49.11)绑定的则是www.a.shifen.com这个A记录。
(有趣吧,经常访问的www.baidu.com居然只是一个别名)
但问题来了,当我们在浏览器中直接输入www.a.shifen.com的时候,并不会出来百度的页面,而是直接倒到百度的错误页面。
猜想可能是,在发起HTTP请求的时候,输入的url会写入HTTP请求头部,这样在服务端会进行判断,发现不是www.baidu.com的时候,会显示错误页。
至于现在百度为什么还要保留这个www.a.shifen.com的DNS配置,就不得而知了,估计又是一个历史遗留问题。
注意:
注意:
因为TCP包是以流水线形式发出的,比如发送端顺序的发出 Seq=1、Seq=2、Seq=3。 那么如果ACK确认的序号和收到的包的序号一致的话,那么需要发回 Ack=1、Ack=2、Ack=3 共三个包。
但是TCP协议对此进行了优化,只需要发送一个ACK包就能代表说自己已经收到了前面三个包, 那就是发送Ack=4 (期望收到Seq为4的包)。这样节省了ACK确认的数量。)
注意:
其实TCP协议中比较核心的还是序列号的变化,光用三次握手体现不出这个关系,下面对多个数据包的数据流图来对序列号的变化进行分析。
wireshark可以绘制TCP流,放这个图主要是想理清一下包序号的之间的关系。
从上面这6个包的接收发送,我们可以总结出以下几点:
TCP通信的一方,在接收到数据包后,可能会发出(1. ACK确认包(不带数据), 2. ACK确认包(带数据))或者只发出ACK确认包(带数据),需要明确的一点是,在第一种情况中,两个包的序号并没有改变(并没有因为是两个包,第二个包序号就比第一个包序号多一,这有点反直觉)。
说明了序号的增长,并不是依赖自己发包的数量和顺序,而是依赖对方已接收并确认的数据
TCP序号根据数据流编码,Ack=154不仅仅表示期待收到对方序列号为4的包,另一层意思是,确认已经收到153字节,并期待下次收到第154字节起始的数据(虽然这两种理解产生的序号值都一样)
这里对上面总结中提到发出两个序列号一样的数据包做出进一步的解释:
仔细回看一下上面的TCP流图,我们会发现一方会向另一方发送ACK包(不带数据),而且序号与接下来要发送的数据包一致,这种数据包一方面是对已收到消息的回复,另外一种含义则是为了刺探通信的对方是否存活。 原因如下:
长连接的环境下,进行一次数据交互后,很长一段时间内无数据交互时,客户端可能意外断电、死机、崩溃、重启,还是中间路由网络无故断开,这些TCP连接并未来得及正常释放,那么,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,且有可能导致在一个无效的数据链路层面发送业务数据,结果就是发送失败。所以服务器端要做到快速感知失败,减少无效链接操作,这就有了TCP的Keepalive(保活探测)机制。 ------理解TCP长连接(Keepalive)
HTTP的KeepAlive是为了保证TCP连接的连接复用
而TCP的KeepAlive是为了保证TCP连接的可用
猜测HTTP使用长连接,这也符合我们常规的逻辑(进入一个网站,我们通常还会获取这个网站的其他数据,如果频繁的断开,连接,会导致不小的时间开销。)
以下是查询资料得到的答案:
HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据。
HTTP首部的Connection: Keep-alive是HTTP1.0浏览器和服务器的实验性扩展,当前的HTTP1.1 RFC2616文档没有对它做说明,因为它所需要的功能已经默认开启,无须带着它,但是实践中可以发现,浏览器的报文请求都会带上它。
如果HTTP1.1版本的HTTP请求报文不希望使用长连接,则要在HTTP请求报文首部加上Connection: close。
此次时间有限,下次实验补上这部分(修改HTTP报文中的Connection,看能否抓到FIN包)。
这里直接上数据流图,主要是看序列号和Ack号的变化:
由服务端的443端口发往客户端的49753端口,这里和我们在课本上看到的只发FIN包不一样,实际情况中为了节省发包次数,一般会在最后一次数据包中把FIN标志位置为1,即是ACK+FIN包(ACK位也置为1)。
这里和课本上一致,先回复一个ACK包,表示收到FIN包。
第三个包传送长度为31的数据,注意,这里传送数据的包序号和前一个回复ACK的序号一样
传送数据完毕,发送FIN、ACK均置为1的包。
接受到客户端发来的FIN包,进行确认。
奇怪的是这里居然回复了两个ACK包,两个包不一样的是Ack号:
理论上,应该只会发第二个ACK包,为什么会发前一个ACK包呢?
在各种搜资料也找不到结果的情况下,做出自己的猜测:
出现这种情况是因为,一方发完自己的数据后,紧接着发送FIN包。
而另一方之所以要分为两个包进行确认的原因,是为了保证发来的数据能够完整接收到,对每一个携带数据的包,都要单独用一个ACK包进行确认,即便对方在发完数据包后马上发了FIN包。
因此第一个包是对数据包的确认,第二个包是对FIN包的确认。
为了搞明白这是否是偶然情况,重复上面的步骤又抓了几次包,可以发现在下图中也出现了在接收到FIN包之后,也出现返回两次ACK包的情况。
虽然wireshark把很多数据含义都进行了标识,但是其中的一些内在联系还是需要我们自己去挖掘(如序列号关系、DNS解析流程),而比较重要的东西在课本上要么是一笔带过,要么缺少实例分析,对细节提及甚少。所以学习还是还要自己主动a …