1. PSH 标志位
从你第一次抓包以来,PSH 标志位几乎与你形影不离。它的英文单词是 PUSH,表示“推”的意思。
1.1 接收缓冲区和发送缓冲区
在谈 PSH 标志位前,先来说说 TCP 双方是如何发送数据的。
假设有发送方 A 和接收方 B。发送方有一个发送缓冲区,接收方有一个接收缓冲区,见图 1。进程 A 发送”hello”, “world” 后,只是将这些数据写到自己的发送缓冲区,为了能讲清 PSH 的作用,不妨假设我们可以自己指定 PSH 标志。在图 1 所示的情况中,第一次 write 没有指定 PSH 标记,而第二次指定了 PSH 标志。
图1 TCP 协议中的发送缓冲区与接收缓冲区
接收进程 B,接收到 TCP 报文后,将数据放入到接收缓冲区。
1.2 PSH 的作用
在 1.1 节中,TCP 模块什么时候将数据发送出去(从发送缓冲区中取数据),以及 read 函数什么时候将数据从接收缓冲区读取都是未知的。
如果使用 PSH 标志,上面这件事就确认下来了:
- 发送端
对于发送方来说,由 TCP 模块自行决定,何时将接收缓冲区中的数据打包成 TCP 报文,并加上 PSH 标志(在图 1 中,为了演示,我们假设人为的干涉了 PSH 标志位)。一般来说,每一次 write,都会将这一次的数据打包成一个或多个 TCP 报文段(如果数据量大于 MSS 的话,就会被打包成多个 TCP 段),并将最后一个 TCP 报文段标记为 PSH。
当然上面说的只是一般的情况,如果发送缓冲区满了,TCP 同样会将发送缓冲区中的所有数据打包发送。
- 接收端
如果接收方接收到了某个 TCP 报文段包含了 PSH 标志,则立即将缓冲区中的所有数据推送给应用进程(read 函数返回)。
当然有时候接收缓冲区满了,也会推送。
接下来,通过实验来理解 PSH 标志位的作用。这个实验是经过设计的,只是为了方便你理解。
2. 实验
2.1 环境准备
服务器:unp/protocol/tools/tcpserver/psh_serv.c
,部署在 Linux 上。
客户端:unp/protocol/tools/winclient/psh_client.cpp
,部署在 Windows 上。
psh_serv 服务器从接收方接收数据,进行循环 read,每一次 read 返回会在屏幕打印读取的字节数。
psh_client 客户端向服务器发送数据,循环 write,每一次 write blocksize 个字节,循环 write count 次。blocksize 和 count 由命令行参数控制。
接下来我们进行两个实验。
2.2 实验1
客户端循环 write 8 次,每次 write 1024 字节
- Linux 上启动服务器
$ ./psh_serv 192.168.80.129 8000 8192
最后一个参数 8192 指明了 read 函数的一次要读取的字节数。
- Windows 上启动 OmniPeek 抓包,同时启动客户端
psh_client.exe 192.168.80.129 8000 8 1024
最后两个参数 8 1024
表示客户端循环写数据 8 次,每次写 1024 字节。
- 结果
图2 8 次写数据,每次写 1024 字节
图3 服务器每一次循环只 read 了 1024 字节就返回
从图 2 和图 3 我们可以看到,因为客户端每一次 write 后,发送的 TCP 报文都带有 PSH 标志位,接收方收到带有 PSH 的报文后,会立即推送给接收进程。
2.3 实验2
客户端只写一次数据,大小为 8192 字节
- Linux 上启动服务器
$ ./psh_serv 192.168.80.129 8000 8192
最后一个参数 8192 指明了 read 函数的一次要读取的字节数。
- Windows 上启动 OmniPeek 抓包,同时启动客户端
psh_client.exe 192.168.80.129 8000 1 8192
最后两个参数 1 8192
表示客户端循环写数据 1 次,每次写 8192 字节。
- 结果
图4 一次性发送 8192 字节
图5 接收方一次性将所有数据全部 read
从图 4 中可以看到,一次 write 了 8192 字节到缓冲区,但是因为 MSS 的限制(服务器端 MSS 为 1460,而客户端只有 1260),客户端会在两个 MSS 之间选一个最小值来确认发送 TCP 报文段大小。
图 4 中,客户端将 8192 字节的数据拆分成了 7 个报文段,注意只有最后一个报文段有 PSH 标志位。
只要接收方的缓冲区没有满,就会一直等待,直到接收到一个带有 PSH 标志的报文,read 才会返回。
3. 总结
- 掌握 PSH 的含义