在计算机网络的TCP/IP
协议栈中,位于传输层中的协议最多见的有两款,TCP
和UDP
。而说到TCP
,入门的两个知识点也是考点,甚至揪细节的话,还会成为疑点的就是三次握手和四次挥手。算法
开局一张图,tcp状态机,从该状态机中,咱们能够看到咱们此次将要讨论的两个话题。缓存
三次握手的大体内容,甚至能够画成下图的漫画。服务器
调侃归调侃,然而道理仍是这么个道理。咱们知道TCP的目的是提供可靠的字节流服务,为了准确无误地将数据送达目的地,TCP协议采纳三次握手策略来创建传输信道,其过程以下:网络
CLOSE(关闭)
状态。SYN( synchronize)
标志的数据包给接收方B。A这个时候会主动(active open)
去connect
服务器,而且发送SYN
,假设序列号为J, 接收方是被动(passive open)
,咱们要关注的是客户端以及服务器发送的序列号都是随机的J+1
表示是给SYNJ
的应答,新发送的SYN K
序列号是K
的数据包传递确认信息,表示我收到了。这个序列号也是随机的ACK
标志的数据包,表明我知道了,表示握手结束。发送方在收到新SYNK
, ACKJ+1
后,也回应ACKK+1
以表示收到了,链接信道创建咱们不妨先来看看TCP报文首部的格式并发
这里TCP报文其实涉及到不少内容,不过这一次咱们只关注控制位,由于控制位跟咱们的三次握手四次挥手有关系。app
须要注意的是,在三次握手以后传输数据的时候,若是握手完第一个数据包是客户端发送,第一个数据包的seq和ack和第三次握手的同样,那么以后发送的seq和ack是根据上一个接收包的seq、ack和len(数据长度)获得,具体为:seq=ack,ack=seq+lenless
序列号的做用是使得一个TCP接收端可丢弃重复的报文段,记录以杂乱次序到达的报文段。由于TCP使用IP来传输报文段,而IP不提供重复消除或者保证次序正确的功能。另外一方面,TCP是一个字节流协议,毫不会以杂乱的次序给上层程序发送数据。所以TCP接收端会被迫先保持大序列号的数据不交给应用程序,直到缺失的小序列号的报文段被填满。socket
咱们在RFC793,也就是 TCP 的协议 RFC,你就会发现里面就讲到了为何三次握手是必须的——TCP 须要 seq 序列号来作可靠重传或接收,而避免链接复用时没法分辨出 seq 是延迟或者是旧连接的 seq,所以须要三次握手来约定肯定双方的 ISN(初始 seq 序列号)tcp
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.ide
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), 这样对方知道接下来接收数据的时候如何按序列号组装数据。
在三次握手的过程当中,实际上实现的效果是让服务器端知道客户端知道服务器知道。这么说确实是有套娃的意味,可能就会有疑问了,让服务器知道客户端知道不就能够了吗?或者是难道不须要让客户端知道服务器端知道客户端知道服务器知道嘛?为什么要画蛇添足?笔者曾在谢希仁老师的书中能够学习其的见解,他的观点是为了防止已失效的链接请求报文忽然又传送到了对端,于是产生错误。
谢老师进一步解释,所谓“已失效的链接请求报文段”是这样产生的。
以上是学术派的理解,主要是从异常处理这个方面进行分析,若是是二次握手的话,如若出现拥塞而致使的重连则会致使,对端服务的异常,须要另外设计异常的处理机制。
以后在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 X 2. A <-- B ACK your sequence number is X 3. A <-- B SYN my sequence number is Y 4. 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 X 2) 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 无需回应,则能够不作三次握手。
说完三次握手,那另外的四次挥手也是这样吗?为了妥协、为了排除异常状况吗?让咱们一步步演绎推理一下。
通俗的说法:
当被动方收到主动方的FIN报文通知时,它仅仅表示主动方没有数据再发送给被动方了。但未必被动方全部的数据都完整的发送给了主动方,因此被动方不会立刻关闭SOCKET,它可能还须要发送一些数据给主动方后,再发送FIN报文给主动方,告诉主动方赞成关闭链接,因此这里的ACK报文和FIN报文多数状况下都是分开发送的。
注意到在挥手过程当中出现了两个状态Close-wait
和Time-wait
,这个其实也是须要设计四次握手的缘由。
客户端发送了FIN
链接释放报文以后,服务器收到了这个报文,就进入了CLOSE-WAIT
状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕以后,服务器会发送FIN
链接释放报文。
客户端接收到服务器端的FIN
报文后进入此状态,此时并非直接进入CLOSED
状态,还须要等待一个时间计时器设置的时间2MSL
。
为何A在TIME-WAIT状态必须等待2MSL的时间呢?
这么作有两个理由:
这是由于服务端的LISTEN状态下的SOCKET当收到SYN报文的链接请求后,它能够把ACK和SYN(ACK起应答做用,而SYN起同步做用)放在一个报文里来发送。但主动关闭链接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你全部的数据都所有发送给对方了,因此可能未必会立刻会关闭SOCKET,也便可能还须要发送一些数据给对方以后,再发送FIN报文给对方来表示你赞成如今能够关闭链接了,因此它这里的ACK报文和FIN报文多数状况下都是分开发送的
横当作岭侧成峰,从不一样的角度,不一样的视野来看待这件事确实受益颇多。