通过wireshark抓包
首先,通过在命令行使用ping baidu.com,获取该域名对应的IP地址。得到的响应如下图所示。
其中39.156.66.10就是需要的IP地址,后面需要通过这个IP地址,作为wireshark的过滤条件,方便我们找到对应的报文。
接着,打开wireshark,开始抓包。然后在命令行输入curl -I baidu.com来向百度发送一个HTTP请求。此时可以停止wireshark抓包。在wireshark的过滤器中,输入ip.addr == 39.156.66.10来过滤发送给百度的请求。可以得到如下过滤后的报文。
前三条是TCP三次握手建立连接的报文,中间三个是包括一个HTTP请求,一个TCP的确认报文和一个HTTP响应报文,响应报文的TCP确认报文和第一次挥手被放在了同一个报文里。后面的四条是TCP四次挥手断开连接的报文。
报文分析
在下面的内容里,发起TCP建立请求的被称为客户端,被请求方称为服务端。发送TCP报文的被称为发送方,接收TCP报文的被称为接收方。所以服务端、客户端是固定的,但他们都可以是发送方,也都可以是接收方。
在分析报文之前,先简单介绍一下TCP协议的头部。TCP整体结构如下图所示。
根据《计算机网络 自顶向下方法 第7版》p154
- 源端口号、目的端口号:即发送方和接收方的对应进程的端口。
- 序号:序号在建立连接时确认,是一个随机的数字,报文中的序号表示当前报文的第一个字节在整个分组中的序号,不是分组的序号。
- 确认号:当头部的ACK字段为1时,表示序号在该确认号前的字节都已收到。也可以理解为,期望收到的下一个分组的序号。
- 首部长度:其实是数据偏移,单位是4字节。因为TCP头部包括选项部分,所以头部长度是可变的。不过一般选项字段都是空,首部长度也就是固定的20字节。
- 接收窗口:用于流量控制,表示发送该报文的端现在最多能接收多少个分组,发送方应当保证已发出的未确认的分组数量不大于该接收窗口的大小。当接收窗口为0时,接收方无法接收新的分组,发送方继续发送分组只会丢失,此时为了试探接收方窗口何时能恢复接收,发送方发送数据部分为1字节的分组维持和接收方的通信。
- 校验和:用于差错检测,帮助接收方确认接收到的分组在网络传输的过程中,是否因为干扰等问题,数据出现错误。
- RST、FIN、SYN:用于连接的建立和拆除。
- CWR、ECE:用于明确拥塞通告。属于拥塞控制的一部分。
- ACK:为1时,该报文的确认号中的值是有效的,即该报文段包括一个对已被成功接收报文端的确认。
- URG、PSH、紧急数据指针: 用于处理紧急数据的。除PSH外实际中并没有被应用,可以忽略。PSH一般在最后一个分组中置为1。
到这里我们可以开始分析wireshark抓包中的各个报文了。
三次握手
首先,来看第一个报文,即三次握手的第一次握手。
第一次握手
整个第一次握手的报文消息详细解析,都在图中标注。我们来看比较重要的几个部分。
- 开始是2字节的源端口号和2字节的目的端口号。
- 紧接着的四个字节,表示的是客户端传送给服务端的初始序号,这是一个随机值,目的是维护安全。
- 注意,再紧接着的四个字节是确认号,不过,在第一次握手的报文中,ACK标志bit位是0,所以,实际上在第一次握手时,确认号是无意义的,因为这时候也没有什么需要确认的。
- 再后面的4个bit,表示的是TCP头部长度,也可以理解为数据偏移量。注意,这里是8,而该字段的单位是4字节,所以TCP头部是8*4 = 32字节;
- 紧接着的是4个保留bit位,和8个特殊标志bit位。这些标志按顺序依次是CWR、ECE、URG、ACK、PSH、RST、SYN、FIN,关注ACK、SYN、FIN即可。这里SYN为1,表示是一个建立连接的报文,ACK为0,表示本报文的确认号无意义。
- 后面2字节表示接收窗口大小,是用于流量控制的。
- 再后面,是2字节的校验和,2字节的紧急数据指针。
- 需要额外指出的是,此报文指出TCP头部有32个字节,而不是一般报文是20字节。这里是因为在选项部分,本报文携带了一些额外的数据,其中包括MSS(Maximum Segment Size): 1460 bytes 等信息。
接着,来看第二次握手的报文。
第二次握手
图中标注了第二次握手的报文详细信息,其大部分都和第一次握手类似。来看比较重要的几个部分
- 源端口号和目的端口号正好和第一次握手相反,说明这是服务端响应客户端的报文
- 服务端给出了一个自己的初始序号A1 7C 07 91,对应2709260177。
- 服务端的ACK bit位是1,说明该报文的确认号是有意义的,确认号对应的值是15 ec 6b df,恰好是第一次握手的报文的序号+1,表示在该数字之前的字节都已经收到,期望下一次收到的报文的序号是这个数
- 这里TCP头部也有32字节,选项部分同样说明了自己的MSS等信息,这里的MSS给的值是1412字节。
总体上来说,第二次握手的报文和第一次握手的报文的区别在ACK bit位是1,并且确认号是第一次握手报文的序号+1,此外,两个报文都包括了额外的选项部分,并且头部都有32个字节。
继续,看第三次握手的报文。
第三次握手
第三次握手的大部分内容也都是类似的,挑出重点部分。
- 第三次握手的序号是第二次的确认号。确认号是第二次的序号+1。
- 首部长度是20字节,头部没有了选项部分。
- SYN字段是0。这里是因为,第一次握手时客户端请求连接,第二次握手时服务器确认并接受连接,第三次握手实际连接已经成功建立。实际上,第三次握手是可以携带应用数据的,不过没见过这样做的。
- 接收窗口的值一直在变化,这是流量控制的一部分,报文发送方发送自己的窗口大小,接收方根据对方窗口大小,调节报文发送速率。
到这里,TCP三次握手结束,连接成功建立。
数据传输
客户端向服务端发送第三次握手的报文之后,紧接着会发送数据传输的信息。在这里是HTTP请求。来看这个请求对应的报文的信息。
HTTP请求
首先,来看HTTP层协议,将该部分翻译过来,就是图中的HTTP部分标注。包含了请求行、请求头、空行。注意,这里的HTTP部分有73个字节,也是就说,TCP数据部分有73个字节。
实际上,程序通过
IP层的数据总长度-IP头长度-TCP头长度来计算数据长度。
然后来看TCP协议部分,主要有以下几点。
- 该报文的头长度也是20字节
- 该报文的确认号、序号都和第三次握手的一样。
- 其中PSH bit为是1。这里是因为这个报文只有这一个分组,整个报文传输完毕,无需等待其他分组,通知接收方应当立即将报文交给应用程序,而不需要缓存起来等待足够的数据再交付给上层。
因为HTTP是基于TCP的协议,所以每一个HTTP请求,即使只有一个分组,也至少涉及到两个TCP报文的传输,因为接收方会发送一个确认报文给发送方。下面,来看这个http请求对应的TCP确认报文。
HTTP请求的TCP确认报文
这个报文内,有以下几点需要注意。
- 上一个HTTP请求的http部分有73个字节,所以本确认报文的确认号,就应当是上一个请求的序号+73。
- 因为接收方直到目前没有传输数据给发送方,所以序号目前只是建立连接时确定的初始序号+1。
- 这里PSH标志位的值没有置1,可以理解为只有携带了数据的报文的最后一个分组,才会将PSH标志位置为1。
也就是说,这个HTTP请求报文,涉及一次HTTP数据报文和一次TCP确认报文。如果数据量较大,需要分组传输的话,涉及的传输更多。
接下来的,就是HTTP响应的报文了,也就是服务端响应给客户端的HTTP报文。
HTTP响应
这里和之前的没有太大区别,不过这一次,报文由服务端发送,并且携带了数据,这里携带了305字节的数据,所以,客户端接收后发送的确认报文的确认号,将会是本报文的序号+305。
接着看HTTP响应对应的TCP确认报文,实际上,这个也是第一次挥手的报文。
HTTP响应的TCP确认报文
- 因为响应携带了305字节的数据,所以本次确认报文的确认号是之前的确认号/接收到报文的序号+305。
- 这里ACK为1,表示确认号有意义,FIN为1,表示这是一个断开连接的报文,所以,这也是第一次挥手的报文。
四次挥手
第一次挥手和HTTP响应的确认报文是同一个报文,和其他的报文相比,除了FIN bit位被置为1,并没有什么其他区别。这里直接从第二次挥手开始看。
第二次挥手
第二次挥手的报文,由服务端发出,对第一次挥手的报文进行了确认。并且其中FIN是0,因为该报文只是接收方对发送方的第一次挥手报文的确认。服务端准备断开连接,准备好后,服务端发出FIN报文。如下图
第三次挥手
注意,这里FIN是1,这是由服务端发出的FIN报文,表示服务端准备好断开连接。服务器发送该报文后,进入LAST_ACK阶段。当客户端接收到此报文时,会返回最后一条确认报文给服务端。
第四次挥手
服务端接收到这条确认消息后,并不会再发送报文,而是将连接关闭,进入CLOSED状态。状态变化序列
下面是三次握手、四次挥手中,服务端、客户端状态变化序列图
TCP状态序列
### 总结 1. 客户端发起连接时,第一次握手的报文中ACK为0,确认号为0,因为没有需要确认的部分。 2. 前两次握手的SYN是1,第三次是0,因为通过一轮交互,双方都已经传递了SYN为1的报文。并且,第三次报文是可以携带应用层数据的,只是一般没有这样。 3. 只有前两次握手的TCP头部长度是32字节,其余的都是常见的20字节。这多出的12字节里,携带了自身的MSS(Maximum Segment Size)等信息。 4. 第一次握手时,发起连接的一方告知接收方自己定义的初始序号,接收方在第二次握手的报文中,通过确认号确认对方的序号(即通过将确认号置为对方序号+1,表明之前序号的字节都已经收到,期望下一个收到的分组序号是该确认号)。 5. 接收方是通过IP层的数据长度,减去IP头长度和TCP头长度来计算出本次接收到的分组中的数据长度的。 6. PSH bit位通常在最后一个分组报文中被置为1,告知接收方应当立即将接收到的报文交给应用层,而无需继续缓存起来等待完整的报文。 7. TCP使用累积确认的方式,通过确认号和需要的方式,来处理丢失的问题。通过校验位和差错修复,来处理报文因噪音等出现的错误。