传输层协议(4):TCP 连接(下-1)

5.2.7 TCP 连接的收发空间

在5.2.3节“TCP 连接是什么”的结尾处,笔者写道:暂时忘记真相,还是记住 TCP 连接这个名词吧。不过很抱歉,本小节,我们还得旧话重提——对于 TCP 的任何一端来说,所谓 TCP 连接,不过是叫作 TCB 的一块内存而已。

TCB 这个数据结构,不同厂商的具体实现,其细节会各有不同,但是从本质上来说,它逃不脱 RFC 793 所定义的范围,如图5-36所示:

 

传输层协议(4):TCP 连接(下-1)

图5-36 TCB 主要字段

 

图5-36中,TCB 的主要字段,除了“其他相关变量”,剩余的几个字段的含义,图中已经表达的比较清楚,这里就不再重复解释。

但是,用户接收数据指针——指向用户接收数据所在的缓存——其背后却有一个潜台词:存放所接收数据的缓存的大小是有限的。这个比较容易理解,归根到底,TCP 的实现离不开计算机里面的程序执行,既然是计算机,其内存资源就是有限的。而且考虑到一个计算机里可能有多个连接,那么分配给每个连接的内存就会更加有限。

考虑这样一个场景,如图5-37所示:

 

传输层协议(4):TCP 连接(下-1)

图5-37 TCP 数据发送与接收

 

图5-37中,TCP 数据发送的速度是 110 MB/s,这些数据都将进入用户接收数据的缓存。用户处理数据,也就是从缓存中取走数据(释放内存)的速度是 10MB/s,而缓存最大容量是 1G,那么只需要10秒钟,缓存就会“撑满”。

此后,如果继续按此速度发送数据,那么接收方只能丢弃数据(或者由于内存溢出而异常退出)。这显然是 TCP 不愿意看到的,为了解决此问题,TCP 在其报文头中设计了1个字段“Window”,如图5-38所示:

 

传输层协议(4):TCP 连接(下-1)

图5-38 TCP 报文头中的 Window 字段

 

TCP 报文头中的“Window”字段,占有16个 bits,取值范围是0~65535,其目的就是通知对方自己还能接收多少字节的数据(再多,接收数据的缓存就溢出了),以使对方不要发送超出该数量的数据,如图5-39所示:

 

传输层协议(4):TCP 连接(下-1)

图5-39 Window 字段的作用

 

图5-39中,在 T0时刻,B 的接收缓存是6000字节,T1 时刻,A 给 B 发送了1000字节,T2 时刻,B 告知 A,它的 Window = 5000(即它还能再接收5000个字节)。

图5-39中,还有一个比较有意思的现象:T3时刻,A 向 B 发送了500字节的数据,在此之后,A 并没有等待 B 的 ACK 报文,而是在 T4 时刻又向 B 发送了 600字节的数据。

在 TCP 连接创建(三次握手)时、TCP 连接关闭(四次挥手)时,我们已经习惯了“一问一答”,即TCP 的一方发送了1次“SYN/FIN”报文之后,总在等对方的“ACK”报文。不过这种“一问一答”的方式在 TCP 数据发送阶段,则没有多大必要,而且效率也不高。所以,在图5-39中,我们看到 T3、T4时刻,A 连续发送数据给 B,而不必等待 B 的应答。

但是出来混,总是要还的。在“5.2.2.2节 数据传输过程分析(极简)”中我们知道,TCP 保证可靠性的本质(机制)是:甲方发送了多少数据给乙方,乙方会通过 ACK 报文告知甲方它接收了多少数据,如果双方对不上,甲方会重传乙方没有接收到的那部分数据。

固然,图5-39中 A 可以连续发送数据给 B,但是对于这些发送的数据,A 一定要在将来的某个时间内收到 B 的 ACK,否则(超时的话),A 还需要重传这些数据。

同时根据前面章节的描述,我们也知道,TCP 其实利用报文中的 SEQ(Sequence Number)、AKN(Acknowledgment Number)这两个字段来实现它的可靠性机制的(图5-39为了突出 Window 字段,没有描述这两个字段)。SEQ、AKN、Window 这3个字段通过有效地计算,就构成了 TCP 数据的收发空间。

 

5.2.7.1 TCP 数据的发送空间

我们先来看看 TCP 数据的发送空间,如图5-40所示:

 

传输层协议(4):TCP 连接(下-1)

图5-40 TCP 数据发送空间

 

首先说明一下,所谓发送空间(接收空间),不过是一些概念、一些数字而已,没什么玄妙深奥的内容,请您不要被这个高大上的名词给唬住了,^_^

图5-40中,数据分为两大部分:已发送数据、未发送数据。

已发送数据,又分为两部分:

(1)已发送已确认:这部分数据已经发送,并且已经得到对方的确认(确认收到),那么这部分数据其实只有概念上的意义,实际上完全可以从内存中抹去——往事随风去。

(2)已发送待确认:这部分数据已经发送,但是尚未得到对方确认(还不知道对方是否已经收到)。这部分数据必须先缓存起来,如果超过了一定时间仍然没有收到对方确认,那么这部分数据需要重新发送——留待成追忆。

图5-40中那个点“SND.UNA”(点“A”)是 SEND.Unacknowledged 的缩写,它是一个数字,它的含义如图5-41所示:

 

传输层协议(4):TCP 连接(下-1)

图5-41 SND.UNA 的含义

 

已发送待确认的报文有多个,SND.UNA 指的就是图5-41中的第1个报文中的 SEQ 字段的值。

未发送数据,也分为两部分:

(1)允许发送数据:“允许”二字,透着一股深深的令人困惑的气息。什么的数据允许被发送?什么样的数据又不允许被发送?什么样的数据最呀最摇摆?什么样的数据让对方乐开怀?

其实没那么神秘,这一切都与对方的接收窗口有关。前文中我们说过,所收到的对方报文的中Window 字段,代表对方最多还能接收多少字节——这个数字,也就是本方当前所能发送的最多字节数,如图5-42所示: 

 

传输层协议(4):TCP 连接(下-1)

图5-42 SND.WND 的含义

 

SND.WND 是一个动态变化的数字。比如:

(A)T0 时刻,收到对方1个报文,该报文的 Window 字段等于2000,那么 TCP 就将自己的 SDN.WND 设置为2000。

(B)T1 时刻,发送给对方 1000 个字节,那么 TCP 就将自己的 SDN.WND 修改为1000。

(C)T2 时刻,收到对方1个报文,该报文的 Window 字段等于3000,那么 TCP 就将自己的 SDN.WND 设置为2000。

......

图5-42和图5-40中,都出现了 SND.NXT,它是 SEND.NEXT 的缩写,其含义如图5-43所示:

 

传输层协议(4):TCP 连接(下-1)

图5-43 SND.NXT 的含义

 

图5-43中,假设刚刚发送的报文的 SEQ = 1000,那么下一个需要发送的报文的 SEQ 应该是多少呢?SND.NXT 就是用来标识“下一个需要发送的报文的 SEQ 的值”。当然,SND.NXT 也是一个动态变化的值,它与刚刚发送的报文有关。“5.2.2.1 TCP 数据传输过程(极简)”曾经描述过类似的公式,这里再修正一下(伪码):

 

# 刚刚发送的报文的 SEQ,记为 SND.SEQ

# 刚刚发送的报文的数据长度,记为 SND.DataLen

# 刚刚发送的报文的 SYN 标记,记为 SND.SYN

# 刚刚发送的报文的 FIN 标记,记为 SND.FIN

# 下一个需要发送的报文的 SEQ,记为 SND.NXT

# 计算公式如下:

 

SND.NXT = SND.SEQ + SND.DataLen

 

# SYN、FIN 也消耗掉1个***

if ((true == SND.SYN) or (true == SND.FIN))

{

    SND.NXT = SND.NXT + 1

}

 

顺便说一下,收到一个报文以后,如果要回1个报文,那么所回报文的 AKN,其计算公式与刚刚所描述的 SND.NXT 计算公式是同理的,如下:(伪码)

 

# 刚刚接收的报文的 SEQ,记为 RCV.SEQ

# 刚刚接收的报文的数据长度,记为 RCV.DataLen

# 刚刚接收的报文的 SYN 标记,记为 RCV.SYN

# 刚刚接收的报文的 FIN 标记,记为 RCV.FIN

# 需要发送的应答报文的 AKN,记为 SND.AKN

# 计算公式如下:

 

SND.AKN = RCV.SEQ + RCV.DataLen

 

# SYN、FIN 也消耗掉1个***

if ((true == RCV.SYN) or (true == RCV.FIN))

{

    SND.AKN = SND.AKN + 1

}

 

通过刚刚发送的报文,就能计算出下一个将要发送的报文的 SEQ(SND.NXT),再加上当前的发送窗口 SND.WND,就能确定:用户的待发送数据中,从 SND.NXT 字节开始,到(SND.NXT + SND.WND)字节结束,一共 SND.WND 个字节,是允许被发送的——红颜世难留!

(2)未允许发送数据:介绍了那么“允许发送数据”,未允许发送数据就很简单了——用户的待发送数据中,从(SND.NXT + SND.WND +1 )字节开始,以后所有的字节,都不允许被发送——白头人间寄!

当然,未允许发送数据也是动态变化的。当对方的应答报文中的 Window 字段增大时(对方又处理了一些字节,释放了一些内存,腾出新的空间来接收新的数据),未允许发送的数据的序号,会越来越向后移。

 

介绍了这么多,我们需要对“TCP 数据发送空间”做一个明确的澄清:

(1)用户所需要发送的数据,每一个字节都有1个编号,第1个字节的编号叫 ISS(Initial Send Sequence Number,后面的小节会讲述 ISS 的取值方法),后面字节的编号按顺序加“1”

(2)TCP 数据发送空间,是由3个数字组成,这些数字是:SND.UNA、SND.NXT、SND.WND

(3)这3个数字,标识了用户所需发送数据的状态:SND.UNA 之前数据,代表“已发送已确认”;SND.UNA~SND.NXT 之间的数据,代表“已发送待确认”;SND.NXT~SND.NXT + WND 之间的数据,代表“允许发送(尚未发送)”;SND.NXT + WND 之后的数据,代表“未允许发送(尚未发送)”。

 

5.2.7.2 TCP 数据的接收空间

TCP 数据接收空间的概念,如图5-44所示:

 

传输层协议(4):TCP 连接(下-1)

图5-44 TCP 数据接收空间

 

图5-44中的 RCV.NXT,是 Receive.Next 的缩写,它表示下一个将要接收报文的 SEQ 应该是多少,它的计算公式,完全等同于上一小节介绍的 SND.AKN,这里就不再重复。

图5-44中的 RCV.WND,是 Receive.Window 的缩写,它表示当前 TCP 连接还能接收多少字节的数据。

参照 TCP 数据发送空间的定义方式,TCP 数据接收空间的含义如下:

(1)TCP 数据接收空间是由2个数字组成:RCV.NXT、RCV.WND

(2)这2个数字代表的是对 TCP 所接收报文的 SEQ 的一种判断法则:

l 所接收报文的 SEQ 不应该小于 RCV.NXT,因为小于 RCV.NXT 的报文都已经接收并且确认了(回给对方 ACK 报文)。如果真的收到 SEQ 小于 RCV.NXT 的报文,那说明收到重复的报文(收到重复报文该如何处理,后面会描述)

l 下一个所接收报文的 SEQ 应该等于 RCV.NXT(如果不等于 RCV.NXT,该如何处理,后面会描述)

l 一共还能接收 RCV.WND 个字节的数据。如果所接收报文的数据的长度大于 RCV.WND,则不应该接收(丢弃报文,并且不应该回以 ACK 报文)

 

5.2.7.3 TCB 定义的补充

本小节的一开始,图5-36对 TCB 的定义中,有这样的描述“其他相关变量”。这些所谓的“其他相关变量”,指的就是 TCP 收发空间相关的变量,再加上 SND.UP 等留待以后再介绍的一些变量,总结如表5-12所示:

 

表5-12  TCB 中的“其他相关变量”

变量名

RFC 793 英文解释

说明

SND.UNA

send unacknowledged

已发送未确认的第1个报文的 SEQ

SND.NXT

send next

下一个发送报文的 SEQ

SND.WND

send window

可以发送数据的size(单位是字节)

SND.UP

send urgent pointer

后面章节会讲述

SND.WL1

segment sequence number used for last window update

 

SND.WL2

segment acknowledgment number used for last window update

 

ISS

initial send sequence number

ISS 赋值方法,下文会讲述

RCV.NXT

receive next

下一个所接收报文的 SEQ

RCV.WND

receive window

可以接收数据的size(单位是字节)

RCV.UP

receive urgent pointer

后面章节会讲述

IRS

initial receive sequence number

与 ISS 相对应

SEG.SEQ

segment sequence number

当前所接收报文的 SEQ

SEG.ACK

segment acknowledgment number

当前所接收报文的 AKN

SEG.LEN

segment length

当前所接收报文的长度(是报文的长度,不是报文中的数据的长度)

SEG.WND

segment window

当前所接收报文的 Window

SEG.UP

segment urgent pointer

当前所接收报文的 urgent pointer(后文会描述)

SEG.PRC

segment precedence value

当前所接收报文的优先级(下文会描述)

 

5.2.8 TCP 连接的优先级和安全性

几乎所有的协议,它们的特征、规格都会体现在它们的报文头上,TCP 协议是一个例外。第一眼看到 TCP 连接的优先级和安全性,鄙人是崩溃的——翻遍 TCP 的报文头,看不到这跟两个属性相关的字段。

RFC 793是这么描述的:The precedence and security parameters used in TCP are exactly those defined in the Internet Protocol (IP)。

是的,您没看错,TCP 的优先级和安全性,是在 IP(的报文头)中描述的。客观地说,TCP 与 IP 的分层和解耦做的并不彻底。不过这已经是事实了,只能接受。

TCP 的优先级(precedence)也就是 IP 报文中的优先级,即 IP 报文头中的 ToS 字段的前3个 bits,如图5-45所示:

 

传输层协议(4):TCP 连接(下-1)

图5-45 IP 报文头中的优先级

 

关于 ToS 和 IP 优先级,请您参阅4.3.3节“服务类型”,本文不再详述。

TCP 的安全,对应的是 IP 报文头中的可选字段 Security,如图5-46所示:

 

传输层协议(4):TCP 连接(下-1)

图5-46 IP 报文头中的安全字段

 

还是那句话,好读书不求甚解,IP 报文头中的安全字段,并不是初步学习 TCP/IP 协议所需要深度掌握的内容,所以本文也就点到为止,不再详述。如果您感兴趣,可以参考 RFC 791。

TCP 不仅仅是借用了 IP 报文的优先级和安全两个概念,而且对这两个概念还进一步做了处理。

用户在创建 TCP 连接时,即在调用 OPEN 接口时,会传入“优先级”和“安全”两个参数。也就是说,TCP 的连接,它其实是有“优先级”和“安全”这两个属性的。

当1个 TCP 报文到来时,TCP 会将自身的这两个属性与 TCP 报文(实际上是 IP 报文头)中的两个相应字段做比较,如果“合法”,才会做进一步的处理,否则会转向错误处理分支。

所谓“合法”,一般原则是:

(1)如果所接收的报文中的“precedence/security/compartment”与 TCP 连接中对应的值完全想等,则合法;

(2)如果如果所接收的报文中的“precedence/security/compartment”小于 TCP 连接中对应的值,则合法;

(3)如果如果所接收的报文中的“precedence/security/compartment”大于 TCP 连接中对应的值:(A)如果用户允许,则也是合法,并且将 TCP 连接对应的值修改为所接收报文中的值;(B)如果用户不允许,则是非法。(所谓用户允许与不允许,是只用户调用 OPEN 接口时所传入了“允许”的参数或者“不允许”的参数)

当然,这只是一般原则,具体到不同的连接状态、接收到不同的报文,其“合法”原则还有所不同,这个我们会放到后面继续讲述。

接收到非法的“precedence/security/compartment”的报文,TCP 的处理原则就是 发送 RST(Reset)报文。

RST报文又是什么意思呢?

 

5.2.9 TCP 的 RST 报文

TCP 的 RST(Reset)报文,其本身倒是比较简单,就是 TCP 报文头中的 Flags 字段中的 R(Reset)标志位取值为“1”,如图5-47所示:

 

传输层协议(4):TCP 连接(下-1)

图5-47 Reset 标志位

 

Reset,翻译成汉语有“重置、清零”的含义,笔者以为,在这个场景下,翻译成“清零”可能更准确。 

 

传输层协议(4):TCP 连接(下-1)

 

对于 TCP 连接而言,清零的含义就是关闭连接,而重置的含义,则有点歧义:似乎是关闭原有连接,重新启动一个新连接的意思。 

传输层协议(4):TCP 连接(下-1)

 

 RST 报文,就相当于“对方不想跟你说话,并向你仍了一个炸弹”,可以想象,收到 RST 报文的一方,除了关闭连接,还能做什么?

当然,这句话也不是绝对精确。对于处于 LISTEN 状态的 TCP 连接来说,它收到了这个炸弹,镇定自若,一笑而过,丢弃该报文,并且仍然是处于 LISTEN 状态。因为此时它还处于 LISTEN 状态,还没有建立连接,也就无所谓关闭,也就不需要关闭。就像你在街头耍酷,等着哪个美女来搭讪,此时如果有个美女对你翻白眼,你会这么办?继续耍酷呗,总不至于落荒而逃吧。 

 

传输层协议(4):TCP 连接(下-1)

耍裤

 

如果一个连接处于 SYN-RECEIVED 状态,并且它前一个状态是 LISTEN 状态,那么它收到 RST 报文时,也是丢弃该报文,并且回到 LISTEN 状态。如果它前一个状态不是 LISTEN 状态,那么它收到 RST 报文时,就会 Abort 该连接,并且状态转移到 CLOSED。

对于其他状态(既不是 LISTEN,也不是 SYN-RECEIVED)的连接,收到 RST 报文后,都会 Abort 该连接,并且状态转移到 CLOSED。当然,对于原本就处于 CLOSED 状态的 TCP 连接来说,它收到 RST 报文直接丢弃就可以了,也不需要状态转移,因为它的状态本来就是 CLOSED。

有一点需要强调,以上所说的 TCP 连接收到 RST 报文时的行为,有一个前提,那就是 RST 报文是“合法”的。这里所谓的“合法”也跟 TCP 连接当是的状态有关:

(1)对于除了 SYN-SENT 状态以外的其他连接,其所收到的 RST 报文的 SEQ(Sequence Number)需要在自己的接收窗口之内(接收窗口的概念,会在5.3节讲述,这里暂且不用追究细节)。

(2)对于 SYN-SEND 状态的 TCP 连接,其所收到的 RST 报文,其 ACK 标志必须为“1”。

(3)补充一点,以上状态不包括 CLOSED 状态。对于 CLOSED 状态的 TCP 连接来说,从某种意义上讲,所有收到的报文,都是非法的(下文还会继续描述 CLOSED 状态的连接,收到报文的处理情况)。

收到“合法”的 RST 报文,TCP 连接会保持或者返回 LISTEN 状态,或者 Abort 连接,转移到 CLOSED 状态。那么收到“非法”的 RST 报文呢?这个问题暂且搁置,这里先讲述 TCP 连接什么时候会发送 RST 报文。

何时发送 RST 报文,与 TCP 连接的状态有关。

对于 CLOSED 状态的 TCP 连接来说,收到所有的报文它都会丢弃。除了丢弃,如果所收到的报文不是 RST 报文,那么 TCP 协议栈还会给对方回一个 RST 报文。

对于非 CLOSED 状态的 TCP 连接而言,发送 RST 报文的理由,都与“precedence/security/compartment”有关。

对于同步状态的 TCP 连接来说——RFC 793 把 ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT、CLOSING、LAST-ACK、TIME-WAIT 等七种状态称为同步状态(synchronized state),其实也就是 TCP 连接建立(ESTABLISHED)以及其后的状态————如果收到 SEQ 超出接收窗口外或者 AKN 错误的 TCP 报文,它们反而不会发送 RST 报文,只是默默地发送一个空的 ACK 报文(只有报文头,不包含数据),报文中指明当前它当前的 SEQ(潜台词也就是它所期望的接收报文中的 AKN)。

也就说,序号错了没有关系,咱可以重新校正。但是,对于同步状态的 TCP 连接来说,如果所接收报文中的“precedence/security/compartment”与其自身的对应的值不匹配(不想等),那么毫不客气地回以一个 RST 报文,并且转移到 CLOSED 状态。

对于非同步状态的 TCP 连接来说——RFC 793 把 LISTEN、SYN-SENT、SYN-RECEIVED 三种状态称为非同步状态(non-synchronized state),其实也就是 TCP 连接建立(ESTABLISHED)之前的状态——它所面对“precedence/security/compartment”的态度,比同步状态的 TCP 连接,要复杂一些。

首先,它要求所接收报文的“security/compartment”要与自己连接的相对应的值完全匹配(相等),否则就发送 RST 报文。

其次,它对于“precedence”的态度,则有所区别。不过这种区别描述起来相当苦涩,不易于理解,我们还是用一个表格来表述,如表5-13所示:

 

表5-13  非同步状态对于 precedence 的处理情形

场景

处理情形

TCP 连接已经发送了 SYN 报文,并且对方也已经 ACK

它所接收的报文(包括包含该次 ACK 的报文)的 precedence 必须要完全匹配(相等)自己的 precedence,否则发送 RST 报文

TCP 连接已经发送了 SYN 报文,并且还没有收到对方的 ACK

(1)如果所接收报文的 precedence 小于或等于自己的 precedence,那么它认为是合法的,继续进行后面的处理

(2)如果所接收报文的 precedence 大于自己的 precedence,它要看用户是否允许:(A)如果用户允许,则也是合法,并且将 TCP 连接对应的值修改为所接收报文中的值;(B)如果用户不允许,则是非法,那就发送 RST 报文。

 

5.2.10 TCP 连接状态机的进一步阐述

TCP 连接所面临的场景有:

(1)用户调用 TCP 接口

(2)一方等待另一方 TCP 报文

(3)一方收到另一方的 TCP 报文

在这些场景中,除了上一小节(TCP 连接的状态机)所描述的“经典”流程,还有其他情形、其他细节需要进一步阐述。

比如用户调用 TCP 接口,这是用户自己的决定,他可以在任意时刻调用任意接口,对于 TCP 来说,有的调用是“合法”的,有的调用是“非法”的——合法会怎么处理,非法会怎么处理?

再比如,TCP 的一方发送报文给另一方时,它会等待对方的应答。但是,如果对方没有应答呢?

再比如,TCP 一方收到另一方的报文时,如果报文是“非法”的,它又应该怎么处理?

 

下面我们就分别阐述这些场景的细节。

 

5.2.10.1 用户调用 TCP 接口

用户调用 TCP 接口,具体来说,就是用户(人)所编写的程序,调用 TCP 协议栈的编程接口。

TCP 的编程接口有:OPEN、SEND、RECEIVE、CLOSE、ABORT、STATUS。这些接口的基本含义,如表5-14所示:

 

表5-14  TCP 接口基本含义

接口

基本含义

OPEN

主动(active OPEN)或被动(passive OPEN)打开一个 TCP 连接。这个与具体的socket 编程相比,稍微有一点点歧义。

 

主动打开一个 TCP 连接,socket 编程的伪码如下:

socket = createsocket(......);

socket.connect(remote_socket);

 

被动打开一个 TCP 连接,socket 编程的伪码如下:

socket = createsocket(......);

socket.listen(remote_socket);//remote_socket 可以为 null

SEND

发送 TCP 数据

RECEIVE

接收 TCP 数据

CLOSE

关闭连接

ABORT

终止正在发送或接收的数据,并且关闭连接

STATUS

获取当前 TCP 的状态和 TCB 的指针(指针是编程术语。如果您不了解编程,就将 TCB 指针简单理解为  TCB 具体内容)

 

从表5-14也可以看到,TCP 的接口对用户封装了很多细节,比如打开连接、关闭连接,就封装了它们背后的三次握手、四次挥手等。

表5-14所说的是 TCP 各个接口的基本含义,或者说是正常的处理流程。但是当 TCP 连接处于不同的状态时,用户调用不同的接口,TCP 会有不同的反应:有时候是合法调用(即正常处理),有时候是非法调用(TCP 会返回错误)。

下面我们详细讲述每个接口在不同状态下的调用情况。

 

1)调用 OPEN 接口

TCP 连接在不同状态下,用户调用 OPEN 接口,TCP 内核的处理过程及响应结果,如表5-15所示:

 

表5-15  OPEN 接口调用情况

当前状态

OPEN 接口调用情况

CLOSED

这是调用 OPEN 接口时的“标准”状态:在状态是 CLOSED(也即还没有连接)的时候,OPEN(打开/创建)一个连接。

在这个时候,OPEN 一个连接,还必须得满足相关条件,才能 OPEN 成功:

(1)OPEN 接口的参数要传对,比如主动 OPEN,就需要传入对端的 socket,另外安全参数、优先级等也要符合要求。

(2)TCP 内核资源要足够:比如队列资源等(队列资源,5.3节“TCP 数据传输”会讲述),这里先简单理解为内存(这样理解不准确,但是也能帮助理解)

(3)用户要有权限访问本地 socket

如果这些条件不满足,则返回错误。(具体错误信息,请参见 RFC 793)

LISTEN

在 LISTENT 状态的时候,原本是为了等待远端的 TCP 创建连接的请求,属于一种被动状态,此时调用 OPEN,有点不按常理出牌,但也还算能接受——调用 OPEN 后,TCP 连接将从被动等待变为主动 OPEN。此时 OPEN 是否成功等,与 CLOSED 状态时调用 OPEN 接口情形一致。

SYN-SENT

处于这个状态在调用 OPEN,就属于非法调用了,TCP 会返回:"error:  connection already exists"(TCP 连接已经存在)

SYN-RECEIVED

同 SYN-SENT 状态

ESTABLISHED

同 SYN-SENT 状态

FIN-WAIT-1

同 SYN-SENT 状态

FIN-WAIT-2

同 SYN-SENT 状态

CLOSE-WAIT

同 SYN-SENT 状态

CLOSING

同 SYN-SENT 状态

LAST-ACK

同 SYN-SENT 状态

TIME-WAIT

同 SYN-SENT 状态

 

2)调用 SEND 接口

TCP 连接在不同状态下,用户调用 SEND 接口,TCP 内核的处理过程及响应结果,如表5-16所示:

 

表5-16  SEND 接口调用情况

当前状态

SEND 接口调用情况

CLOSED

非法调用,返回错误提示信息。

如果用户没有权限访问本地 socket,则返回"error:  connection illegal for this process";否则,返回"error:  connection does not exist"。

LISTEN

这仍然是一个变“被动”为“主动”的故事。

 

如果参数中没有制定远端 socket,则返回错误提示信息"error:  foreign socket unspecified"。

如果指定了远端 socket,则背后做一堆的事情:

(1)TCP 自己调用 OPEN。OPEN 成功与否,请参见表5-15。

(2)如果成功,经过三次握手以后,TCP 连接的状态变为“ESTABLISHED”以后,则发送数据。

SYN-SENT

等待三次握手,连接状态变为“ESTABLISHED”以后:如果队列资源足够,则发送数据;否则,返回错误提示信息"error:  insufficient resources"。

SYN-RECEIVED

同 SYN-SENT 状态

ESTABLISHED

这是最“标准”的发送数据(SEND)的状态:连接创建好以后,当然要发送数据了。但是,发送数据的前提,也需要有资源——队列资源。

如果队列资源足够,则正常发送数据。

否则,返回错误提示信息"error:  insufficient resources"。

FIN-WAIT-1

处于此种状态的 TCP 连接,是不能发送数据的,所以直接返回错误信息"error:  connection closing"。

FIN-WAIT-2

同 FIN-WAIT-1 状态

CLOSE-WAIT

虽然是半连接,但是处于 CLOSE-WAIT 状态的 TCP 连接,丝毫不影响其发送数据,所以这也算非常“标准”的发送数据(SEND)的状态。

具体情形,同 ESTABLISHED 状态。

CLOSING

同 FIN-WAIT-1 状态

LAST-ACK

同 FIN-WAIT-1 状态

TIME-WAIT

同 FIN-WAIT-1 状态

 

3)调用 RECEIVE 接口

TCP 连接在不同状态下,用户调用 RECEIVE 接口,TCP 内核的处理过程及响应结果,如表5-17所示:

 

表5-17  RECEIVE 接口调用情况

当前状态

RECEIVE 接口调用情况

CLOSED

非法调用,返回错误提示信息。

如果用户没有权限访问本地 socket,则返回"error:  connection illegal for this process";否则,返回"error:  connection does not exist"。

LISTEN

等待三次握手,连接状态变为“ESTABLISHED”以后:如果队列资源足够,则接收数据;否则,返回错误提示信息"error:  insufficient resources"。

SYN-SENT

同 LISTEN 状态

SYN-RECEIVED

同 LISTEN 状态

ESTABLISHED

这是接收数据(RECEIVE)最标准的状态。

如果队列资源足够,则接收数据;否则,返回错误提示信息"error:  insufficient resources"。

接收到数据以后,还需要处理:重组、PUSH 标记、Urgent 标记等,这个放到5.3节“TCP 数据传输”讲述。

FIN-WAIT-1

虽然是半连接,但是处于 FIN-WAIT-1 状态的 TCP 连接,丝毫不影响其接收数据,所以这也算非常“标准”的发送数据(SEND)的状态。

具体情形,同 ESTABLISHED 状态。

FIN-WAIT-2

同 FIN-WAIT-1 状态

CLOSE-WAIT

处于此状态,意味着对端已经不能发送数据了。

所以,本端队列中如果还有“残留”数据(残留,只是一个比方,没有它意),那么 TCP 会将队列中的数据返回给用户。

否则,返回用户错误提示信息"error:  connection closing"。

CLOSING

直接返回用户错误提示信息"error:  connection closing"

LAST-ACK

同 CLOSING 状态

TIME-WAIT

同 CLOSING 状态

 

4)调用 CLOSE 接口

TCP 连接在不同状态下,用户调用 CLOSE 接口,TCP 内核的处理过程及响应结果,如表5-18所示:

 

表5-18  CLOSE 接口调用情况

当前状态

CLOSE 接口调用情况

CLOSED

非法调用,返回错误提示信息。

如果用户没有权限访问本地 socket,则返回"error:  connection illegal for this process";否则,返回"error:  connection does not exist"。

LISTEN

首先澄清一下 TCP 连接的状态与用户调用 TCP 接口的“不同步”情况。

用户可以调用 TCP 接口 RECEIVE,但是此时 TCP 连接的状态仍然可能处于 LISTEN 状态,因为 TCP 还在等待“三次握手”的最后完成。

 

所以:

如果调用 CLOSE 接口之前,用户没有调用 RECIEVE 接口,那么 TCP 就直接关闭连接(删除 TCB),进入 CLOSED 状态;

否则,TCP 除了直接关闭连接以外,还需给正在执行的 RECEIVE 接口返回错误提示信息"error:  closing"

SYN-SENT

首先澄清一下 TCP 连接的状态与用户调用 TCP 接口的“不同步”情况。

用户可以调用 TCP 接口 SEND、RECEIVE,但是此时 TCP 连接的状态仍然可能处于 SYN-SENT 状态,因为 TCP 还在等待“三次握手”的最后完成。

 

所以:

如果调用 CLOSE 接口之前,用户没有调用 SEND、RECIEVE 接口,那么 TCP 就直接关闭连接(删除 TCB),进入 CLOSED 状态;

否则,TCP 除了关闭连接以外,还需给正在执行的 SEND、RECEIVE 接口返回错误提示信息"error:  closing"

SYN-RECEIVED

如果调用 CLOSE 接口之前,用户没有调用 SEND 接口并且也没有数据需要发送,那么 TCP 就发送 FIN 报文,进入 FIN-WAIT-1 状态;

否则,CLOSE 调用则排队等候处理。因为 TCP 还需要先进入 ESTABLISHED 状态(中间可能还需要两次握手)

ESTABLISHED

首先是排队,如果前面有数据需要发送的话。排到 CLOSE 被处理时,发送 FIN 报文,进入 FIN-WAIT-1 状态。

FIN-WAIT-1

严格地说,这是非法调用。TCP 应该返回错误提示信息"error: connection closing" 。不过,如果第2个 FIN 还没发出来,TCP 也可以返回"ok"。

无论返回说明,TCP 都是按部就班地执行“四次挥手”流程,不会受 CLOSE 接口调用的影响。

FIN-WAIT-2

同 FIN-WAIT-1 状态

CLOSE-WAIT

首先是排队,待所有数据发送完毕以后,发送 FIN 报文,进入 CLOSING 状态。

CLOSING

TCP 返回错误提示信息 "error:  connection closing"。

LAST-ACK

同 CLOSING 状态

TIME-WAIT

同 CLOSING 状态

 

5)调用 ABORT 接口

TCP 连接在不同状态下,用户调用 ABORT 接口,TCP 内核的处理过程及响应结果,如表5-19所示:

 

表5-19  ABORT 接口调用情况

当前状态

ABORT 接口调用情况

CLOSED

非法调用,返回错误提示信息。

如果用户没有权限访问本地 socket,则返回"error:  connection illegal for this process";否则,返回"error:  connection does not exist"。

LISTEN

同 LISTEN 状态调用 CLOSE 接口的情形

SYN-SENT

同 SYN-SENT 状态调用 CLOSE 接口的情形

SYN-RECEIVED

(1)给对端发送 RST 报文

(2)所有排队待处理的 SEND、RECEIVE 接口,都返回提示信息"connection reset"

(3)所有排队待发送或重发的数据(除了上述的 RST 报文),都清空(不再发送)

(4)删除 TCB,进入 CLOSED 状态

ESTABLISHED

同 SYN-RECEIVED 状态

FIN-WAIT-1

同 SYN-RECEIVED 状态

FIN-WAIT-2

同 SYN-RECEIVED 状态

CLOSE-WAIT

同 SYN-RECEIVED 状态

CLOSING

返回 OK,删除 TCB,进入 CLOSED 状态

LAST-ACK

同 CLOSING 状态

TIME-WAIT

同 CLOSING 状态

 

6)调用 STATUS 接口

TCP 连接在不同状态下,用户调用 STATUS 接口,TCP 内核的处理过程及响应结果,如表5-20所示:

 

表5-20  STATUS 接口调用情况

当前状态

STATUS 接口调用情况

CLOSED

非法调用,返回错误提示信息。

如果用户没有权限访问本地 socket,则返回"error:  connection illegal for this process";否则,返回"error:  connection does not exist"。

LISTEN

返回 "state = LISTEN", 和 TCB 指针。

SYN-SENT

返回 "state = SYN-SENT", 和 TCB 指针。

SYN-RECEIVED

返回 "state = SYN-RECEIVED", 和 TCB 指针。

ESTABLISHED

返回 "state = ESTABLISHED", 和 TCB 指针。

FIN-WAIT-1

返回 "state = FIN-WAIT-1", 和 TCB 指针。

FIN-WAIT-2

返回 "state = FIN-WAIT-2", 和 TCB 指针。

CLOSE-WAIT

返回 "state = CLOSE-WAIT", 和 TCB 指针。

CLOSING

返回 "state = CLOSING", 和 TCB 指针。

LAST-ACK

返回 "state = LAST-ACK", 和 TCB 指针。

TIME-WAIT

返回 "state = TIME-WAIT", 和 TCB 指针。

 

5.2.10.2 等待对方报文

一方等待另一方的 TCP 报文,如果在规定时间内等到对方的报文,那么就按照“正常”的流程往下走,如果没有等到,则意味着超时。“正常”流程,我们在5.2.6节(TCP 连接的状态机)已经做过描述,并且还会继续在下一小节(5.2.6.3 收到对方报文)继续描述。所以,本小节只介绍“超时”情况。

超时也分为3种情形:TCP 连接创建时、TCP 连接关闭时、TCP 数据传输时。第3种情形(TCP 数据传输时)留待下一节讲述,本小节只讲述前两种情形。

TCP 创建连接的“三次握手”,从报文分类的角度,也可以“分解”为“四次握手”,如图5-48所示:

 

传输层协议(4):TCP 连接(下-1)

图5-48 创建 TCP 连接时的“四次”握手

 

图5-48将原来三次握手(请参见图5-30)中的第2次握手一分为二,从而分解为四次握手。图5-48还是有点复杂,我们继续将其抽象,如图5-49所示:

 

传输层协议(4):TCP 连接(下-1)

图5-49 创建 TCP 连接时的“四次”握手的抽象

 

图5-49中有两处等待时间——两处等待时间都是一个意思——TCP 的一方发送 SYN 请求时,等待另一方的 ACK。 

 

传输层协议(4):TCP 连接(下-1)

 

天青色等烟雨,而我在等你。这么浪漫的歌词背后却隐藏着一个无情的事实:并没有承诺永远会等你。

TCP 也不能无休止的等下去,它总有一个超时时间。更加抽象地说,只要是请求报文,TCP 就在等一个应答确认(ACK)报文。而在 TCP 的世界里,有等待就有超时。这不仅体现在 TCP 连接的创建,也体现在连接的关闭,如图5-50所示:

 

传输层协议(4):TCP 连接(下-1)

图5-50 关闭 TCP 连接时的四次挥手的抽象

 

图5-50(关闭连接),“1”和“3”两个等待点,与图5-37(创建连接)的两个等待点(“1”和“2”)本质上是一样的,都是1个请求报文在等待1个应答确认(ACK)报文。只不过关闭连接的请求报文是 FIN,创建连接的请求报文是 SYN。

图5-50比图5-49还多了两个等待点:等待点“2”是 A 在等待 B 发送 FIN 报文(关闭连接请求);等待点“4”是 A 在等待 2MSL 时间,然后关闭自己。不过等待点“4”是 TCP 固有设计,不能算作异常处理,所以本小节不讨论它。

TCP 的连接是相互的,A 向 B 请求关闭了连接之后,它还得等待 B 也向它请求关闭连接——双方都关闭以后,TCP 连接才认为是关闭了。这是等待点“2”的真正含义。

同理,TCP 连接的创建,也应该有类似一个等待点。只不过在创建连接时,TCP 将 B 的确认与请求报文合二为一,所以我们在图5-37中没有看到那个对应的等待点。

总结一下,TCP 连接的创建和关闭,所涉及的等待点,如图5-51所示:

 

传输层协议(4):TCP 连接(下-1)

图5-51 TCP 连接创建和关闭的等待点

 

图5-51所描述的等待点有两类:

(1)“请求-确认”等待点,一方发送完 SYN/FIN 请求,等待另一方应答确认。

(2)“关闭连接”等待点,一方处于 FIN-WAIT-1 状体,等待另一方发送 FIN 请求。

有人的地方,就有江湖。有等待的地方,就有超时。关于这两类等待点,TCP 是这样处理的:

(1)超时时间,可以由用户(TCP 的实现者、TCP 的使用者)自行设置。

(2)超时以后,其结果都是关闭连接(删除相应的 TCB)。