在计算机网络的
TCP/IP
协议栈中,位于传输层中的协议最常见的有两款,
TCP
和
UDP
。而说到
TCP
,入门的两个知识点也是考点,甚至揪细节的话,还会成为疑点的就是三次握手和四次挥手。
开局一张图,tcp状态机,从该状态机中,我们可以看到我们这次将要讨论的两个话题。
三次握手
三次握手的大致内容,甚至可以画成下图的漫画。
调侃归调侃,然而道理还是这么个道理。我们知道TCP的目的是提供可靠的字节流服务,为了准确无误地将数据送达目的地,TCP协议采纳三次握手策略来建立传输信道,其过程如下:
- 最初两端的TCP进程都处于
CLOSE(关闭)
状态。
- 发送方A主动发送一个带有
SYN( synchronize)
标志的数据包给接收方B。A这个时候会主动
(active open)
去
connect
服务器,并且发送
SYN
,假设序列号为J, 接收方是被动
(passive open)
,我们要关注的是客户端以及服务器发送的序列号都是随机的
- 接收方B在收到SYN后,如果同意建立连接,会回传发送另一个SYN以及一个ACK(应答)给发送端A,ACK的序列号是
J+1
表示是给SYN
J
的应答,新发送的SYN
K
序列号是
K
的数据包传递确认信息,表示我收到了。这个序列号也是随机的
- 发送方A再回传一个带有
ACK
标志的数据包,代表我知道了,表示握手结束。发送方在收到新SYN
K
, ACK
J+1
后,也回应ACK
K+1
以表示收到了,连接信道建立
- 然后两边就可以开始数据发送数据了
本质
我们不妨先来看看TCP报文首部的格式
这里TCP报文其实涉及到很多内容,不过这一次我们只关注控制位,因为控制位跟我们的三次握手四次挥手有关系。
- URG:当URG=1时,表明紧急指针字段有效,告诉系统这个报文段里有紧急数据,优先传送。
- ACK:当ACK=1时,确认号字段有效,ACK=0时确认号字段无效
- PSH:收到TCP的PSH=1的报文段,就尽快的交付接受应用进程,而不用等到整个缓存都满了再向上交付。
- RST:当RSH=1时表明TCP连接出现错误,必须释放连接,然后再重新建立连接。
- SYN:SYN=1时表示这是一个连接请求或连接接受报文。
- FIN:FIN=1表示此报文段的发送端的数据以发送完成,并要求释放运输连接。
需要注意的是,在三次握手之后传输数据的时候,如果握手完第一个数据包是客户端发送,第一个数据包的seq和ack和第三次握手的一样,那么之后发送的seq和ack是根据上一个接收包的seq、ack和len(数据长度)得到,具体为:seq=ack,ack=seq+len
序列号的作用
序列号的作用是使得一个TCP接收端可丢弃重复的报文段,记录以杂乱次序到达的报文段。因为TCP使用IP来传输报文段,而IP不提供重复消除或者保证次序正确的功能。另一方面,TCP是一个字节流协议,绝不会以杂乱的次序给上层程序发送数据。因此TCP接收端会被迫先保持大序列号的数据不交给应用程序,直到缺失的小序列号的报文段被填满。
我们在RFC793,也就是 TCP 的协议 RFC,你就会发现里面就讲到了为什么三次握手是必须的——TCP 需要 seq 序列号来做可靠重传或接收,而避免连接复用时无法分辨出 seq 是延迟或者是旧链接的 seq,因此需要三次握手来约定确定双方的 ISN(初始 seq 序列号)
A fundamental notion in the design is that every octet of data sent over a TCP connection has a sequence number. Since every octet is sequenced, each of them can be acknowledged. The acknowledgment mechanism employed is cumulative so that an acknowledgment of sequence number X indicates that all octets up to but not including X have been received.
TCP 设计中一个基本设定就是,通过TCP 连接发送的每一个包,都有一个
sequence number
,而因为每个包都是有序列号的,所以都能被确认收到这些包。确认机制是累计的,所以一个对
sequence number
X 的确认,意味着 X 序列号之前(不包括 X) 包都是被确认接收到的。
The protocol places no restriction on a particular connection being used over and over again. The problem that arises from this is — "how does the TCP identify duplicate segments from previous incarnations of the connection?" This problem becomes apparent if the connection is being opened and closed in quick succession, or if the connection breaks with loss of memory and is then reestablished.
TCP 协议是不限制一个特定的连接(两端 socket 一样)被重复使用的。所以这样就有一个问题:这条连接突然断开重连后,TCP 怎么样识别之前旧链接重发的包?——这就需要独一无二的 ISN(初始序列号)机制。
When new connections are created, an initial sequence number (ISN) generator is employed which selects a new 32 bit ISN. The generator is bound to a (possibly fictitious) 32 bit clock whose low order bit is incremented roughly every 4 microseconds. Thus, the ISN cycles approximately every 4.55 hours. Since we assume that segments will stay in the network no more than the Maximum Segment Lifetime (MSL) and that the MSL is less than 4.55 hours we can reasonably assume that ISN\’s will be unique.
当一个新连接建立时,初始序列号( initial sequence number ISN)生成器会生成一个新的32位的 ISN。
ISN的计算方式如下:
ISN = M + F(localhost, localport, remotehost, remoteport)
其中M是一个计时器,每隔4µs加1。 F是一个Hash算法,根据源IP、目的IP、源端口、目的端口生成一个随机数值。
这个生成器会用一个32位长的时钟,差不多
4µs
增长一次,因此
ISN
会在大约 4.55 小时循环一次。
而一个段在网络中并不会比最大分段寿命
MSL Maximum Segment Lifetime
,默认使用2分钟长,MSL 比4.55小时要短,所以我们可以认为 ISN 会是唯一的。
三次握手的作用
一个TCP连接由一个4元组构成,分别是两个IP地址和两个端口号。一个TCP连接通常分为三个阶段:启动、数据传输、退出(关闭)。客户端和服务端通信前要进行连接,“3次握手”的作用就是双方都能明确自己和对方的收、发能力是正常的。
三次握手的重要功能之一就是客户端和服务端交换ISN(Initial Sequence Number), 这样对方知道接下来接收数据的时候如何按序列号组装数据。
非要三次握手吗?
在三次握手的过程中,实际上实现的效果是让服务器端知道客户端知道服务器知道。这么说确实是有套娃的意味,可能就会有疑问了,让服务器知道客户端知道不就可以了吗?或者是难道不需要让客户端知道服务器端知道客户端知道服务器知道嘛?为何要多此一举?笔者曾在谢希仁老师的书中可以学习其的看法,他的观点是为了防止已失效的连接请求报文突然又传送到了对端,因而产生错误。
谢老师进一步解释,所谓“已失效的连接请求报文段”是这样产生的。
- 考虑一种正常情况, A发出连接请求,但因连接请求报文丢失而未收到确认。于是A再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。A共发送了两个连接请求报文段,其中第一个丢失,第二个到达了B,没有“已失效的连接请求报文段"。
- 现假定出现一种异常情况,即A发出的第一个连接请求报文段并没有丢失,而是在某些网络结点长时间滞留了,以致延误到连接释放以后的某个时间才到达B。本来这是一个早已失效的报文段。但B收到此失效的连接请求报文段后,就误认为是A又发出一次新的连接请求。于是就向A发出确认报文段,同意建立连接。假定不采用报文握手,那么只要B发出确认,新的连接就建立了。
- 由于现在A并没有发出建立连接的请求,因此不会理睬B的确认,也不会向B发送数据。但B却以为新的运输连接已经建立了,并一直等待A发来数据。B的许多资源就这样白白浪费了。
以上是学术派的理解,主要是从异常处理这个方面进行分析,如果是二次握手的话,如若出现拥塞而导致的重连则会导致,对端服务的异常,需要另外设计异常的处理机制。
之后在Google Groups的TopLanguage中看到一帖讨论TCP“三次握手”觉得挺有意思。贴主提出“TCP建立连接为什么是三次握手?”的问题,在众多回复中,有一条回复写道:“这个问题的本质是, 信道不可靠, 但是通信双发需要就某个问题达成一致。而要解决这个问题, 无论你在消息中包含什么信息, 三次通信是理论上的最小值。所以三次握手不是TCP本身的要求, 而是为了满足‘在不可靠信道上可靠地传输信息’这一需求所导致的。 请注意这里的本质需求,信道不可靠,数据传输要可靠。三次达到了, 后面你想接着握手也好, 发数据也罢, 跟进行可靠信息传输的需求就没关系了。因此,如果信道是可靠的, 即无论什么时候发出消息,对方一定能收到,或者你不关心是否要保证对方收到你的消息, 那就能像UDP那样直接发送消息就可以了。”
这其实可视为对“三次握手”目的的另一种窥探角度。满屏的工程化思维,毕竟这是对于信道传输不可靠的妥协,完成对可靠信息的传输。毕竟3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
比如说我们现在把三次握手改成仅需要两次握手,死锁是可能发生的。假设计算机S和C之间的通信,此时C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。漫画图解如图所示
在前面的描述中我们知道发送方与接收方都会有自己的 ISN (下面的例子中就是 X 与 Y)来做双方互发通信,具体的描述如下:
1. A --> B SYN my sequence number is X2. A <-- B ACK your sequence number is X3. A <-- B SYN my sequence number is Y4. A --> B ACK your sequence number is Y
2与3都是 B 发送给 A,因此可以合并在一起,成为three way (or three message) handshake,此时最终可以得出,三次握手是必须的:
A three way handshake is necessary because sequence numbers are not tied to a global clock in the network, and TCPs may have different mechanisms for picking the ISN\’s. The receiver of the first SYN has no way of knowing whether the segment was an old delayed one or not, unless it remembers the last sequence number used on the connection (which is not always possible), and so it must ask the sender to verify this SYN. The three way handshake and the advantages of a clock-driven scheme are discussed in [3].
因为 sequence numbers(序列号)没有绑定到整个网络的全局时钟(全部统一使用一个时钟,就可以确定这个包是不是延迟到的)以及 TCPs 可能有不同的机制来选择 ISN(初始序列号)。
接收方接收到第一个 SYN 时,没有办法知道这个 SYN 是是否延迟了很久了,除非他有办法记住在这条连接中,最后接收到的那个sequence numbers(然而这不总是可行的)。
这句话的意思是:一个 seq 过来了,跟现在记住的 seq 不一样,我怎么知道他是上条延迟的,还是上上条延迟的呢?所以,接收方一定需要跟发送方确认 SYN。
假设不确认 SYN 中的 SEQ,那么就只有:
1) A --> B SYN my sequence number is X2) A <-- B ACK your sequence number is X SYN my sequence number is Y
只有B确认了收到了 A 的 SEQ, A 无法确认收到 B 的。也就是说,只有 A 发送给 B 的包都是可靠的, 而 B 发送给 A 的则不是,所以这不是可靠的连接。这种情况如果只需要 A 发送给 B ,B 无需回应,则可以不做三次握手。
四次挥手(Four-Way-Wavehand)
说完三次握手,那另外的四次挥手也是这样吗?为了妥协、为了排除异常情况吗?让我们一步步演绎推理一下。