23-TCP 协议(紧急标志)

1. 紧急标志

当你再次看到图 1 时,相信你已经无比的亲切,再观察下面彩色的 6 个标志位,有 5 个你已经熟知了,还剩下最后一个 URG,对,就是图 1 中那个鲜红的,醒目的那个位置。


这里写图片描述
图1 TCP 首部

除此之外,还有一个字段—— 16 位紧急指针,它正是配合 URG 标志位一起使用的,言外之意就是这个字段只有在 URG 被置位时才有意义。因为只有一个紧急指针,这也意味着它只能标识一个字节的数据。这个指针指向了紧急数据最后一个字节的下一个字节。


这里写图片描述
图2 紧急数据与紧急指针

我们知道 TCP 在传输数据时是有顺序的,它有字节号,URG 配合紧急指针,就可以找到紧急数据的字节号。紧急数据的字节号公式如下:

(urgSeq)=TCP(seq)+(urgpoint)1

比如图 2 中的例子,如果 seq = 10, urgpoint = 5, 那么字节序号 urgSeq = 10 + 5 -1 = 14.

知道了字节号后,就可以计算紧急数据字位于所有传输数据中的第几个字节了,如果从第 0 个字节开始算起,那么紧急数据就是第 urgSeq - ISN - 1 个字节(还记得 ISN 吗,它表示初始序列号),减 1 表示不包括第一个 SYN 段,因为一个 SYN 段会消耗一个字节号。

2. 紧急标志的作用

紧急标志可以用来通知对端:我放了一个紧急数据在数据流中,你看着办吧!这个特性往往可以来达到通知的目的。

一旦 TCP 知道了你要发送紧急数据,那么在接下来的数据发送中,TCP 会将所有的 TCP 报文段中的 URG 标志置位,哪怕该报文段中不包含紧急数据,这个行为会持续到紧急数据被发送出去为止。

3. 一个示例

该数据是运行 unp/protocol/tools/tcpserver/urg_server.cunp/protocol/tools/winclient/urg_client.cpp 时抓取的。

urg_client 程序每次发送 1024 字节的数据,一共发送 8 次。在第 4 次的时候,发送了 1 字节的紧急数据(字符 'X')和 1024 字节普通数据。类似下面这段代码:

for (i = 1; i <= 8; ++i) {   if (i == 4) write(sockfd, 'X', 1, URG);   write(sockfd, buf, 1024); }

服务器 urg_server 接收到客户端的连接请求后,先等待 3 秒,再接收数据,每次接收 1024 字节,类似下面这样:

while(1) {   sleep(3);   read(sockfd, buf, 1024);   //... }

最后,抓取到的数据如图 3.


这里写图片描述
图3 抓取的包含 URG 的 TCP 报文

可以看到,当客户端在第一次发送了 1024 字节后(数据包 4),5 号数据包就发送了一个 URG。实际上,write 函数首先将数据写入自己的缓冲区,如果客户端发送缓冲区足够大,几乎一瞬间就会把 8193 个字节写入到缓冲区。

第一次 write 1024 字节到缓冲区后,TCP 直接将这 1024 字节发出去了。在收到对方的 ACK 前,TCP 都不会再发送数据(Nagle 算法),客户端又连续 write 了 7 次,写入发送缓冲区。因为在第 4 次的时候,客户端发送了一个字节紧急数据,因此,接下来 TCP 在每次发送报文时都会打开 URG 标志。直到紧急数据被发送出去。

我们看看最后一个红框框,也就是 22 号数据包:


这里写图片描述
图4 紧急数据 'X'

紧急指针 urgent pointer 的值为 1, 也就是当前 TCP 段中第一个字节。这一个字节的紧急数据,随普通数据一起发送了出去。

4. 一些坑

如果发送方多次发送紧急数据,最后一个数据的紧急指针会将前面的覆盖。比方说你发送了一个字节的紧急数据 'X',在 'X' 尚未被 TCP 发送前,你又发送了一个紧急数据 'Y',那么在后面的 TCP 报文中,紧急指针都是指向了 'Y' 的。

很多系统的实现,包括 Linux 将紧急数据称之为带外数据(out-of-band data, OOB),意为在连接之外传送的数据,实际上这是不对的(《TCP/IP 详解》一书称此不正确的)。即使是紧急数据,仍然会随着普通数据流一起发送,并不会单独为紧急数据开辟一条新的连接通道单独发送。这从图 3 中我们也可以看到,紧急数据并没有被优先发送出去。

5. 总结

  • 知道紧急标志位的作用
  • 紧急指针的含义
  • 紧急数据与带外数据