5.3.1.4 Urgent(紧急数据)

紧急数据(Urgent Data),TCP 有时候也简称 Urgent。作为 TCP 的一个特性,由于它的单词含义(紧急的、急迫的),Urgent 似乎是给人一种一目了然的感觉,实际上它却比较容易引起困扰。这个困扰是什么,我们暂且按下不表,先看看 Urgent 在 TCP 里面的定义是什么。

1)紧急数据的基本定义

Urgent 在 TCP 里面的定义是什么,如图5-97所示:

图5-97 TCP 报文结构

Urgent 在 TCP 中的定义,对应图5-97中的3个字段:

(1)Urgent 标签,简称 URG,也就是图5-97中的“U”,占位1个 bit。当 URG = 1 时,后面的两个字段才有意义。也就是说,URG = 1 时,这3个字段才能联合表达 TCP 的 Urgent 的含义,否则 TCP 认为 It’s not  Urgent(不紧急)!

(2)Urgent Pointer,占位 16 bits,它是一个无符号整数,指示数据字段(图5-97中的 Data)中哪个(些)数据是紧急数据。当然,只有 URG = 1 时,Urgent Window 这个字段才有意义。

(3)Data,TCP 报文中所包含的数据部分。当 URG = 1 时,这部分数据中有1个(或多个)字节的数据代表是紧急数据,而这个(些)紧急数据由 Urgent Pointer 指定,如图5-98所示:

图5-98 紧急数据的分歧

图5-98 是一个 TCP 报文的数据片段,假设这个报文的 SEQ(Sequence Number) = 10,URG = 1,Urgent Pointer = 11。那么:

(1)RFC 793 认为:紧急数据序列号 = SEQ + Urgent Pointer - 1 = 20,也就是图5-97中第“20”个字节是紧急数据。

(2)RFC 1122 认为:紧急数据序列号 = SEQ + Urgent Pointer = 21,也就是图5-97中第“21”个字节是紧急数据。

实际上 RFC 793 是错误的,RFC 1122 是正确的,它对 RFC 793 做了修订:

The second sentence is in error: the urgent pointer points to the sequence number of the LAST octet (not LAST+1) in a sequence of urgent data.  The description on page 56 (last sentence) is correct.

但是,混乱已经酿成了,RFC 793 发布于1981年9月,RFC 1122 发布于1989年10月,这造成 TCP 协议栈具体的实现中,有的不得不两种都支持,让用户通过配置文件配置到底采用哪一种方案。本文采用 RFC 1122 的方案。

RFC 793 所犯的这种错误,从某种意义上可以理解,毕竟人无完人,一时疏忽总是难免的。但是紧急数据的长度是多少呢?它从哪里开始,哪里结束呢?关于这两个问题,RFC 793 和 RFC 1122 的描述是德行:模糊不清,故意制造混乱。

请原谅我对伟大的 RFC 出言不逊。我们还是来看看两个 RFC 是怎么说的。

RFC 793是这么说的:This mechanism permits a point in the data stream to be designated as the end of urgent information.

这句话的意思是“Urgent Pointer 指示了紧急数据的结尾”,但是它同时也暗示了紧急数据不只是1个字节。

RFC 1122 是这么说的:A TCP MUST support a sequence of urgent data of any length.

这就不是暗示了,而是明确说明:紧急数据是不止是1个字节,而是任意长度。

至此我们看到了,无论是 RFC 793 还是 RFC 1122 都认为两点:

(1)紧急数据不止1个字节

(2)紧急数据的最后一个字节由 Urgent Pointer 指定(如前文所述:RFC 793 的指定方法有点问题,RFC 1122 对其做了修正)。

但是,TCP 的数据结构(报文头)中,只有1个字段 Urgent Pointer 指定了紧急数据的结尾,却再也没有其他字段以能指定紧急数据的起始位置或者长度(通过长度能间接推导出起始位置)。

这可让人怎么办?

面对这种模糊,一般有两种方案:

第1种方案是认为该报文中,从第1个字节开始,到 Urgent Pointer 结束,这段数据通通都是紧急数据,如图5-99所示:

图5-99 紧急数据的定义(1)

该方案认为,图5-99中的第10~21,这12个字节的数据,都是紧急数据。

第2种方案则不再纠结,根本不在意 RFC 是这么说的,就是认为紧急数据只有1个字节,如图5-100所示:

图5-100 紧急数据的定义(2)

业界普遍采用第2种方案,比如著名的 Linux 就是采用第2种方案。本文遵从 Linux,也采用第2种方案。

唉......Urgent 特性确实够混乱的。下面我们对 Urgent 的定义做一个简单的总结吧:

(1)URG = 1,紧急数据才有意义,否则报文中的数据没有紧急数据

(2)紧急数据只有1个字节。当 URG = 1 时,紧急数据位于报文中的数据的序号 = SEQ + Urgent Pointer。

2)紧急数据在接收方的行为

紧急数据在接收方的行为,指的是 TCP 接收方收到包含紧急数据的报文后,它该如何将这个报文中的数据提交给应用层,如图5-101所示:

图5-101 紧急数据在接收方的行为

图5-101中,我们假设 TCP 采用满泻提交模式(即使采用直流提交模式,TCP 对于紧急数据的提交方式也是一样的,只不过采用满泻提交模式,对比更加强烈,更容易理解)。

T0时刻,接收方缓存中有数据“xyz”。

T1时刻,接收方收到了“abcde”,其中“c”是紧急数据。此时接收方处理紧急数据的方案是:

(1)抢在其他所有数据之前(抢在本次接收的普通数据“abde”之前,也抢在已经存入接收缓存的普通数据“xyz”之前),提交给应用层

(2)而且是在第一时间提交,毫无停顿。

(3)更进一步,紧急数据(“c”)根本不会经过接收方缓存,而是直接提交给应用层。

(4)在紧急提交了紧急数据以后,对于本次接收的其他非紧急数据(“abde”),接收方按照正常流程处理:或者是满泻提交、或者是直流提交。

我们把以上1~3步所描述的提交方式称为紧急提交模式。

3)紧急数据在发送方的行为

总的来说,紧急数据在接收方的提交模式(紧急提交),比较清晰和简单。但是,紧急数据在发送方的行为,相对就比较令人困惑了。

在 TCP 的官方表达中,其把紧急数据称为带外数据(out-of-band,OOB),但是实际上TCP 的数据发送,根本就没有什么所谓的带外数据发送。

带外数据指的是发送采用与普通数据不一样的(逻辑)通道。而 TCP 发送紧急数据与普通数据,采用的是同一个通道。当然,这里需要稍微澄清一下:此时所说的通道,指的就是 TCP 连接,而不是 IP 层的路由。

由此我们知道,TCP 称紧急数据为带外数据,只是一个比方而已,或者只是为了一个形象地表达。但是,真的形象吗?

紧急数据在 TCP 的接收方,采用的是紧急提交模式。这种模式用大白话说,就是“插队”。那么,紧急数据在 TCP 的发送方,是否会插队发送呢?如图5-102所示:

5-102 紧急数据会插队发送吗

图5-102中,滑动窗口中5000个字节允许发送,另外还有10000个字节等待发送许可,也就是说一共有15000个字节有待发送。此时,用户调用 TCP 的 SEND 接口,期望发送1个紧急数据“n”,那么 TCP 发送的行为是:

(1)绝对不会插队发送,那个紧急数据“n”,老老实实地排在第15001位,等待 TCP 发送。

(2)TCP 发送方严格按照滑动窗口的滑动原则(叠加上 PUSH 行为,如果用户调用了 PUSH 发送的话),按部就班地发送数据。

也就是说,所谓紧急数据,在 TCP 发送方看来,一点都不“紧急”,该怎么就这么发送。那么,问题来了,紧急数据到底是什么含义呢?它到底要跟谁着急?

它到底要跟谁着急?这个问题问得好——哈哈,还有自己夸自己的——我们先看一个古老的笑话:

话说,有俩个人去非洲寻宝。不巧俩人遇到了狮子。这时,其中一个人放下手里的东西,把背后的包打开,迅速的换上了跑鞋。另一个人不解,说:“你难道跑得过狮子吗?”只听那个人说,“是啊!当然跑不过它,但只要比你跑得快就行啦。”

TCP 所谓的紧急数据,它的应用场景跟这个笑话的寓意一样:追求的不是绝对速度(紧急),而是相对速度(紧急)。

我们举一个例子。假设你是一名程序猿,但是你的工作却是天天写 PPT。有一天你终于厌烦了这种生活,你发誓一定要写代码(唉......多么屌丝的誓言)。于是你决定先登录到服务器,将上面的 PPT 先删除上面的 PPT——是的,你要跟昨天彻底告别。

本来,你删除 PPT 的命令是(伪码):del *.pptx,但是你误敲成 del *.*。“del *.*”是什么意思?那是要删光服务器上所有的文件啊......上帝啊!下一步怎么办?

也许 TCP 能救你!你马山再发送1个紧急数据“n”——我们假设“n”代表不执行任何命令——这个时候,你所敲的这些命令,对于 TCP 来说,它的报文可能是这样的,如图5-103所示:

图5-103 紧急数据的应用

图5-103只表达了 TCP 的数据部分,这个报文的 SEQ = 10,URG = 1,Urgent Pointer = 6,也就是序号为16的那个字节“n”是紧急数据。

对于这样一段数据,如前文所说,TCP 发送方并不着急,它按照滑动窗口的规则,该怎么发送就怎么发送。重点是这样一段数据到了接收方以后,TCP 接收方会在其他数据之前,将紧急数据“n”紧急提交给应用层。对应的应用层接到“n”这个字符以后,就不会执行后面接收到的命令。那么,“del *.*”这个命令就不会被执行——服务器上的文件完好无损——从此,你又可以安安静静地写你的 PPT 了。至于写代码的念头,唉......还是趁早打消吧,毕竟 TCP 也不是每次都能帮到你,如果你敲“n”的命令稍微慢一点的话......

以上所举的例子,从技术角度来讲,会有一些漏洞,你也不能完全当真,只是当作一个故事能帮助理解紧急数据的用途即可。

抽象地讲,紧急数据的应用场景是:如果发送客户端程序由于一些原因需要取消已经写入服务器的请求,那么它就需要向服务器紧急发送一个标识取消的请求。使用紧急数据的实际程序例子有telnet、rlogin、ftp 等命令。前两个程序(telnet和rlogin)会将中止字符作为紧急数据发送到远程端。这会允许远程端冲洗所有未处理的输入,并且丢弃所有未发送的终端输出。这会快速中断一个向客户端屏幕发送大量数据的运行进程。ftp命令也会使用紧急数据来中断一个文件的传输。

由此,我们知道了紧急数据的应用场景,但是它在 TCP 发送方的故事还没有结束。在“5.3.1.3 PUSH”小节中,我们讲述了 PSH 标签,可以由用户调用 SEND 接口时主动打上,也可以由 TCP 自动打上。而 URG 标签,只能由用户主动打上。也就是说,紧急数据只能由用户标识,TCP 不会自动标识某个数据是紧急数据。

不过,这不是最主要的,最主要的是 TCP 发送方打 UGR 标签的行为,虽然紧急数据的标识只能由用户主动标识。假设用户调用 TCP 接口发送一串字符,如下(伪码):

SEND(“abcdefgh”,URG = 1)

根据前文描述我们知道,TCP 的具体实现(比如 linux)仅仅认为紧急数据只有1个字节,所以用户如果调用了上述接口,TCP 会认为只有最后1个字符“h”是紧急数据。

考虑一个极端一点的情况,假设滑动窗口的大小等于3,那么 TCP 会将“abcdefgh”分割成3个报文发送。现在问题来了:这3个报文的 URG 标签即 Urgent Pointer 该如何赋值呢?如图5-104所示:

图5-104 紧急数据的报文分割

图5-104,“abcdefgh”,由于发送窗口太小,这8个字符被分解为3个包,而且只有第3个包中的“h”才是紧急数据。

但是,我们看到,从报文1开始,URG 标签就被打上了(URG = 1),而且 Urgent Pointer 指向了未来。因为对于报文1来说,SEQ + Urgent Pointer = 1 + 7 = 8,然而第8个字节并不在报文1里,而是在第报文3里,所以说 Urgent Pointer 指向了未来。

报文2也是同理,早早地打上了 URG 标签,但是 Urgent Pointer 依然指向未来。

只有报文3算是真正完成了紧急数据的发送。

我们想象一下 TCP 报文的接收方,收到1个号称是紧急的报文,里面却没有紧急数据,又收到号称是紧急的报文,里面却没有紧急数据 ......

唉,TCP 发送方太皮——我怕来不及,就不告诉你!直到感觉你的皱纹,有了岁月的痕迹!

TCP 为什么要这么做呢?鄙人猜想,它是想做一个骑墙派。前文说过,RFC 认为:

(1)紧急数据不止1个字节

(2)紧急数据的最后一个字节由 Urgent Pointer 指定

但是 RFC 用 Urgent Pointer 定义了紧急数据的结尾,却没有定义紧急数据的开头(或者长度),弄得 TCP 具体实现者无所适从,最后要么是仅仅认为紧急数据只有1个字节(比如 Linux),要么是把该报文的第1个字节到 Urgent Pointer 所指定的字节,这之间全部的字节都认为是紧急数据。

当用户调用接口:SEND(“abcdefgh”,URG = 1),TCP 该怎么办呢?这时候它就骑墙了:

(1)发送的时候,相关的报文,我都打上 URG 标签

(2)接收的时候,你看着办,你认为全都是,那就全都是紧急数据,你认为只有1个字节是紧急数据,那就1个字节。

(注:TCP 的发送和接收,可能不是同一个实现者。比如 TCP 发送是 Windows,TCP 接收是 Linux)。

骑墙派的哲学是两边都不得罪,既不得罪 RFC,也不得罪 TCP 的具体实现者。

但是,骑墙派期望是两边都不得罪,实际上却是两边都得罪,两边都讨好,如图5-105所示:

图5-105 骑墙派的忧愁

图5-105中,发送缓存中已经有了“hello”5个字符,此时用户调用了 TCP 接口:

SEND(“abcdefgh”,URG = 1)

然后 TCP 发送方将这13个字符拼成了1个报文发送出去,该报文的:SEQ = 1,URG = 1,Urgent Pointer = 12。

此时,如果认为从第1个字节(SEQ = 1)到第13个字节(Urgent Pointer = 12),也就是认为“helloabcdefgh”都是紧急数据,显然是不对的。所以骑墙派是不能让 RFC 满意的,而图5-104所描述的情形,也不能让 TCP 接收方满意。

满脸的忧伤

TCP 的这种忧伤,忍一忍也能过去:得罪了 RFC,那就不伺候了,就认为只有1个字节是紧急数据;多发了几个无关的紧急报文(图5-104所描述的情形),那就多发了,唉咋地咋地。但是,TCP 还可能将紧急数据给发错了,如图5-106所示:

图5-106 紧急报文发错了

图5-106中,T1 时刻,用户调用 TCP 接口,发送了紧急数据“hello”(只有“o”是紧急数据),但是实际上 TCP 并没有发送该数据,而是将之存入了发送缓存。

T2 时刻,用户又调用 TCP 接口,发送了紧急数据“abcdefgh”(只有“h”是紧急数据)。

现在问题来了,TCP 实际上是接纳了两个紧急数据发送的任务,但是它在 T3 时刻却用1个报文给发出去了,正如图5-106所示,这个报文的 SEQ = 1、URG = 1、Urgent Pointer = 12,也就是说仅仅是“h”是紧急数据,而“o”变成了普通数据(非紧急数据)。

也就是 TCP 在紧急数据发送层面的一个问题:如果上一个紧急数据还没发送,有可能会被下一个紧急数据给“冲刷/替代”掉_——而且用户并不知道他的紧急数据何时能被正确发送,何时会被“冲刷/替代”。

回到本小节开头的那句话:作为 TCP 的一个特性,由于它的单词含义(紧急的、急迫的),Urgent 似乎是给人一种一目了然的感觉,实际上它却比较容易引起困扰。

我们来数数 Urgent 有多少让人迷惑的点吧:

(1)Urgent Pointer 关于紧急数据的指示,RFC 793 定义错了,RFC 1122 给纠正了过来,但是这中间经历了8年,混乱的种子就此埋下。

(2)RFC 一方面信誓旦旦地说“紧急数据是一串数据”,但是它只定义了紧急数据的结尾指示(Urgent Pointer),却没有定义紧急数据的起始位置,造成了具体实现者无所适从,最后绝大部分实现者只能选择“紧急数据其实就是1个字节”

(3)TCP 所发送的报文中,可能是 URG = 1,但是该报文并不包含紧急数据

(4)如果上一个紧急数据还没发送,有可能会被下一个紧急数据给“冲刷/替代”掉——而且用户并不知道他的紧急数据何时能被正确发送,何时会被“冲刷/替代”。

Urgent 是一种会呼吸的痛

4)Urgent 与 PUSH 的比较

Urgent 与 PUSH 相比,有一定相似的地方,不过它们之间的差别实际上很大的。我们有一张表来总结两者之间的差别,如表5-28所示:

表5-28  Urgent 与 PUSH 的比较

发送/接收

比较项

PUSH

Urgent

全部

目标

减少时延:

1、发送缓存中的数据立刻发送

2、接收缓存中的数据立刻提交给应用层

提升相对速度:

1、紧急数据比普通数据更早地提交给应用层

发送方

是否插队发送

N

N

发送方

是否满足滑动窗口规则

Y

Y

发送方

发送行为

打破原来的原则(比如 Nagle 算法),发送缓存中的数据立刻发送

发送缓存中的数据按照原来的原则(比如 Nagle 算法)发送

发送方

用户是否可以指定

Y

Y

发送方

TCP 是否可以自动指定

Y

N

接收方

数据提交模式

接收缓存中的所有数据立刻提交给应用层

1、只有紧急数据立刻而且抢先提交给应用层,而且不经过接收缓存

2、其他数据(普通数据)按照原有的提交模式(比如满泻提交模式)提交

全部

概念清晰

Y

N

传输层协议(9):滑动窗口(3)——会呼吸的痛相关推荐

  1. 传输层协议TCP—滑动窗口(6)

    1 传输控制 TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的.可靠的.基于字节流的传输层通信协议.在前面的章节中,我们介绍了 TCP 连接的相关概念 ...

  2. 计算机网络传输层(tcp滑动窗口与流量控制、拥塞控制)

    ④ TCP的滑动窗口 TCP的滑动窗口是以字节为单位的,是缓存的一部分,用来暂时存放字节流. 为了便于理解,我们只考虑A向B发送数据,B给出确认的场景.即A有发送窗口,B有接收窗口. 当发送方收到接收 ...

  3. 传输层与数据链路层滑动窗口协议的异同

  4. Linux_网络_传输层协议 TCP通信滑动窗口(快重传),流量控制,拥塞控制(慢启动),延迟应答,捎带应答,TCP常见问题(字节流,粘包),Listen半连接队列

    紧跟Linux_网络_传输层协议 TCP/UDP继续补充 文章目录 1. TCP通信时滑动窗口(效率) 2. 流量控制(可靠性) 3. 拥塞控制(慢启动) 4. 延迟应答 5. 捎带应答(提高通信效率 ...

  5. 3.4.1 计算机网络之流量控制(停止-等待协议、滑动窗口、后退N帧协议GBN、选择重传协议SR)、滑动窗口、可靠传输机制

    文章目录 0.思维导图 1.什么是流量控制? 2.什么是可靠传输机制? 3.什么是滑动窗口机制? 4.可靠传输.流量控制.滑动窗口之间的关系 5.停止-等待协议 (1)为什么要有停止-等待协议? (2 ...

  6. 简述tcp协议三报文握手过程_华为原理 | 传输层协议amp;交换转发原理

    Interface GigabitEthernet0/0/0 ip address 12.1.1.2 255.255.255.0 arp-proxy enable \\华为接口下默认没有开启代理ARP ...

  7. TCP连续ARQ协议和滑动窗口协议

    TCP协议通过使用连续ARQ协议和滑动窗口协议,来保证数据传输的正确性,从而提供可靠的传输. 一.ARQ协议 ARQ协议,即自动重传请求(Automatic Repeat-reQuest),是OSI模 ...

  8. 4-1:TCP协议之传输层的作用及传输层协议TCP和UDP

    文章目录 一:传输层的定义 二:通信处理 三:传输层协议 四:TCP协议的可靠和性能 一:传输层的定义 前面说过,IP首部有一个协议字段用于标识网络层(IP)的上一层采用哪一种传输层协议.根据这个字段 ...

  9. 传输层端口号的范围是多少?被分为哪两部分_6.传输层协议

    前言 传输层定义了主机应用程序之间端到端的连通性.传输层中最为常见的两个协议分别是传输控制协议TCP ( Transmission Control Protocol )和用户数据包协议UDP ( Us ...

  10. 传输层协议 ——— TCP协议

    文章目录 TCP协议 谈谈可靠性 TCP协议格式 序号与确认序号 窗口大小 六个标志位 确认应答机制(ACK) 超时重传机制 连接管理机制 三次握手 四次挥手 流量控制 滑动窗口 拥塞控制 延迟应答 ...

最新文章

  1. centos静态IP配置方法
  2. oracle 11g ORA-12514
  3. 轻量级目标检测大合集
  4. pat天梯赛L1-056. 猜数字
  5. JAVA进阶day02
  6. Spring Boot 实用开发技巧————Eclipse 远程调试
  7. 结构体(struct)与类(class)
  8. 有趣的 Mysql 存储引擎 1
  9. 精益思想,从哪里开始?
  10. 最新版c语言经典习题100例(最全面)
  11. java diamond 运算符_解决:Java source1.5不支持diamond运算符,请使用source 7或更高版本以启用diamond运算符...
  12. 在react-native fetch中 then res res.json 是什么意思
  13. Kafka:增加Topic的分区数
  14. 二手手机设备回收小程序开发
  15. 欧文分校的计算机科学博士,加州大学欧文分校计算机科学硕士排名第37(2020年TFE Times排名)...
  16. tableview概述
  17. 解决CSS3瀑布流、多列布局时内容被截断、错乱
  18. python 检查身份证号的正确性
  19. 让一个技术人员快速破产的9个方法!
  20. HC/LS/HCT/F系列芯片的区别(转)

热门文章

  1. python中__init__()、__new__()、__call__()、__del__()几个魔法方法的用法
  2. 新人如何在职场中生存
  3. 向数据源DataTable 中添加新的一列,并向其赋值
  4. 汉字注音符号学习(引用自维基百科)
  5. hiho一下 第二十九周 最小生成树三·堆优化的Prim算法【14年寒假弄了好长时间没搞懂的prim优化:prim算法+堆优化 】...
  6. Android Listener侦听的N种写法
  7. [Advance] How to debug a program (下):示例
  8. R语言学习笔记:简单的回归分析
  9. java泛型方法的使用
  10. mysql.sock文件丢失的一个原因