HTTP与TCP协议-GET与POST请求抓包分析
首先我在服务器上部署了一个Web服务(注:容器的1423端口被绑定到了宿主机的8010端口),可以接收一个最简单的Get和Post请求,如下图

然后在终端对其发送Get和Post请求,都是成功的,如下图:

然后使用Wireshark对Get请求和Post请求分别抓包,进行分析。
本文先详细分析Get请求,对于Post请求仅指出与Get请求的区别。
Get请求抓包分析
在Wireshark抓包一次Get请求如下图,可以看出其先进行三次握手建立连接,然后发送请求,最后四次挥手断开连接。

下面对这10帧数据依次分析。
三次握手
前三帧进行了三次握手建立连接,从图3可以看出,数据报和上篇文章ICMP-Wireshark抓包分析描述的ICMP格式相似,最外层为Ethernet II数据包,内部为IPv4数据包,最内部包含TCP数据包。所以本文不再对Ethernet II和IPv4进行详细分析,直接看IPv4的Data部分,也就是TCP数据包。

一般地,TCP的帧格式如下

然后我们查看第一次握手的情况。
第一次握手
从图4可以看到,TCP报文如下,共44字节:
1 | c5 fd 1f 4a da 9c f9 fa 00 00 00 00 b0 02 ff ff 63 e5 00 00 02 04 05 b4 01 03 03 06 01 01 08 0a 09 ff 7d ac 00 00 00 00 04 02 00 00 |
先观察数据包的大小,资料偏移为0xb,即整个数据包大小为0xb个字,即11*4=44byte。因为数据包Header部分占有20字节(5个字),因为资料偏移大于5,就意味着第160位标红处的“选项”有内容要填充,必要时在末尾填充0,上述数据包在“选项”部分填充了24byte的内容。

将上述内容填充至TCP的帧格式内,如下图:

主要看到,序列号码0xda9cf9fa,SYN为1。
其中:
- 序列号码:表示当前报文段的数据的第一个字节的序号,此序号为随机生成(为什么?)。本次以序列号0xda9cf9fa向服务端发送了1字节的数据,那么下次向服务端发送的序列号就是0xda9cf9fb。序列号和确认号码ACK有重要关系,第二次握手时会说明。
- SYN:为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步
第二次握手
TCP报文如下
1 | 1f 4a c5 fd 57 f4 44 99 da 9c f9 fb a0 12 71 20 2b fa 00 00 02 04 05 90 04 02 08 0a cf b6 6b 9d 09 ff 7d ac 01 03 03 07 |
其Source Port为8081,Destination Port为50685,序列号码0x57f44499,ACK为0xda9cf9fb,SYN为1。
与第一次握手相比,SYN均为1,序列号码Seq为两个不同的无关值,第二次握手设置了ACK。
也就是说:
- 序列号码:表示当前报文段的数据的第一个字节的序号。本次以序列号0x57f44499向客户端发送了1字节的数据,那么下次向客户端发送的数据包的序列号就是0x57f4449a。
- ACK:本次确认号码为0xda9cf9fb,意味着告诉客户端我已经收到序列号为0xda9cf9fb之前数据报,下一次期望收到的的数据包序列号应为0xda9cf9fb(也就是本次确认号码)。
第三次握手
TCP报文如下
1 | c5 fd 1f 4a da 9c f9 fb 57 f4 44 9a 80 10 08 03 c3 a6 00 00 01 01 08 0a 09 ff 7d c5 cf b6 6b 9d |
其Source Port为50685,Destination Port为8081,序列号码为0xda9cf9fb,ACK为0x57f4449a,SYN为0。
与第二次握手相比,第三次握手的SYN成为了0,且ACK成为了第二次握手的Seq+1,序列号码Seq是第二次握手的ACK。
也就是说:
- 序列号码:表示当前报文段的数据的第一个字节的序号。本次发送的数据包序列号为0xda9cf9fb,与第二次握手时服务端期望接收到的序列号相同。
- ACK:本次确认号码为0x57f4449a,意味着第二次握手时客户端所发送的序列号为0x57f4449a之前数据报已经被服务端接收,下次期待接收到的数据包序列号应为0xda9cf9fb。

三次握手,双方分别确认了如下能力:

Get请求
在Wireshark中可以看到,HTTP协议是由TCP协议支撑。

先看下,在HTTP请求中,TCP字段头的信息:

分析可以得出,序列号码为0xda9cf9fb,ACK为0x57f4449a,SYN为0。与第三次握手的TCP报文相同,因为第三次握手为客户端向服务端发送数据并未得到服务端回应,所以客户端发起的Get请求,所以其ACK为第二次握手接收到的序列号Seq+1,序列号码应该为第二次握手接收到的ACK。
HTTP数据报共90字节,计算方法如下图:

在TCP报文部分,可以看到Wireshark已经给出了[TCP Segment Len: 90],但是仅根据TCP协议帧字段是计算不出来90的,推测Wireshark也是根据上图方法计算的。
接下来,看下HTTP报文本身。

一般地,HTTP请求帧格式如下:

可以看到,抓包得到的HTTP数据报如下
1 | 474554202f76312f696e666f2f68656c6c6f20485454502f312e310d0a486f73743a20666c7864752e636e3a383031300d0a557365722d4167656e743a206375726c2f372e36342e310d0a4163636570743a202a2f2a0d0a0d0a |
和上图给出的HTTP请求帧格式一致。注意,请求头后面有一个空行,在Wireshark截图中也可以看到。
HTTP数据包到达服务器后,直接由服务端监听8010的进程接收、解析并响应。
Get请求之后的一次握手
从本文Wireshark截图可以看到,在Get请求(第45帧)发出之后,紧接着接收到了来自服务器的一次握手数据包(第46帧),然后才是服务器返回的数据(第47帧)。下面对第46帧进行一下分析说明。

第46帧是由服务端向客户端发送的,是为了告诉客户端:已经接收到来自客户端的序列号为0xda9cfa54及之前的数据包(也就是刚才发的Get请求,即第45帧),下一个数据包序列号请从0xda9cfa55开始。
在刚才的Get请求中,其向服务端发送数据包中的ACK字段为0x57f4449a,序列号为0xda9cf9fb,共发送了90字节数据,所以服务端会给客户端一个回应。
Get请求的响应
第47帧是Get请求的响应,如下图

一般地,HTTP响应帧格式如下:

可以看到,抓包得到的HTTP数据报如下
1 | 485454502f312e3120323030204f4b0d0a436f6e74656e742d547970653a206170706c69636174696f6e2f6a736f6e3b20636861727365743d7574662d380d0a446174653a205475652c203037204a756e20323032322031353a33313a303720474d540d0a436f6e74656e742d4c656e6774683a2031340d0a0d0a2248656c6c6f2c776f726c642122 |
和上图给出的HTTP响应帧格式一致。注意,Content-Length的值就是响应体的字节数。
HTTP数据包到达客户端后,直接由客户端监听50685的进程接收、解析并响应。
第48帧,是客户端告知服务端已接收到第47帧而发的数据包。
四次挥手
下图中,第49、50、51帧是双方挥手断开连接的过程。

具体来说,
- 第一次(第49帧):客户端向服务端发送带有FIN(Finish)字段和对48帧的ACK的TCP包,告知服务端请求连接终止。此时客户端处于
FIN-WAIT-1
状态,即“等待远程TCP的连接中断请求,或先前的连接中断请求的确认”; - 第二次(第50帧):服务端接收到客户端发送的第49帧,并且发现自己也没数据再要发送,就回复了FIN和对第49帧的ACK,告知客户端请求连接终止。此时客户端到服务端的连接释放。
- 第三次(第51帧):客户端接收到服务端发送的第50帧,向对方回复已接收到,然后客户端进入
TIME_WAIT
(时间等待)状态,TIME-WAIT
- 等待足够的时间以确保远程TCP接收到连接中断请求的确认;
注意 !!!这个时候由服务端到客户端的 TCP 连接并未释放掉,需要经过时间等待计时器设置的时间 2MSL(一个报文的来回时间) 后才会进入 CLOSED
状态(这样做的目的是确保服务端收到自己的 ACK 报文(即第52帧)。如果服务端在规定时间内没有收到客户端发来的 ACK 报文的话,服务端会重新发送 FIN 报文给客户端,客户端再次收到 FIN 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文给服务端)。服务端收到 ACK 报文之后,就关闭连接了,处于 CLOSED
状态。
在这里来看,好像是“三次挥手”。
对于真正的四次挥手,应该是:
- 第一次(第49帧):客户端向服务端发送带有FIN(Finish)字段和对48帧的ACK的TCP包,告知服务端请求连接终止。此时客户端处于
FIN-WAIT-1
状态,即“等待远程TCP的连接中断请求,或先前的连接中断请求的确认”; - 第二次(第50帧):服务端接收到客户端发送的第49帧,但是服务端对客户端还有信息要发,就只回复了第49帧的ACK(未回复FIN),告知客户端已知悉连接终止请求,此时客户端到服务端的连接释放;
- 第三次(第51帧):服务端也想中断请求了,向客户端发送一个FIN报文,告知客户端要断开连接请求;
- 第四次(第52帧):客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,此时客户端处于
TIME_WAIT
(时间等待)状态,然后等待时间2MSL后释放到服务端的连接。
所以本文的“三次挥手”是把四次挥手的第二次和第三次合并成一次了。
为什么客户端释放对服务端的连接之后,依然可以向服务端发送ACK?
因为FIN状态位表示:“发送后,当前客户端仍然会接收服务器的数据,但是不会接收他本地服务的数据(应用层的数据)”,所以客户端在发送Fin后仍然能发送ACK是不矛盾的,因为ACK不是本地服务的数据,是放在TCP头部的,而不是Body中。
Post 请求
如下图,对Post请求的抓包:

可以看到只有HTTP请求帧有少许差别,本质上还是HTTP协议,不再赘述。