三次握手
一次经典的三次握手的过程如下图所示:
三次握手最重要的就是交换彼此的ISN。我们需要重点掌握的是包交互过程中序列号变化的原理。
第一次握手
客户端发送的一个报文段是 SYN 报文,这个报文只有 SYN 标记被置位。该SYN的作用是同步客户端的初始序列号。
SYN报文不占用数据,但是它占用一个序列号(也就是占用1个字节)。下次发送的数据序列号要加1。客户端会随机选择1个数字作为初始序列号。
为什么 SYN 段不携带数据却要消耗一个序列号呢?
不占用序列号的段是不需要确认的(都没有内容确认个啥),比如 ACK 段。SYN报文段需要对方的确认,因此需要占用一个序列号。
关于这一点,可以记住如下的规则:
但凡消耗序列号的报文,一定要对端确认,否则会重传直到指定次数为止。
第二次握手
服务端收到客户端的 SYN 段以后,将 SYN 和 ACK 标记都置位。
该SYN的作用是同步服务端生成的初始序列号。
ACK用于告知发送端之前发送的SYN端已经收到了。
确认号字段指定了发送端下次发送报文的序列号(这里等于客户端的ISN加1)。
与前面类似,SYN+ACK报文虽然没有携带数据,由于它需要被确认,因此也消耗一个序列号。
第三次握手
客户端发送三次握手最后一个 ACK 段,这个 ACK 段用来确认收到了服务端发送的 SYN 段。
因为这个 ACK 段不携带任何数据,且不需要再被确认,这个 ACK 段不消耗任何序列号。
除了交换彼此的初始序列号,三次握手的另外一个重要作用就是交换一些辅助信息,比如最大段大小(MSS)、窗口大小(Win)、窗口缩放因子(WS)等。
三次握手的状态变化
三次握手过程的状态变化图如下
对于客户端而言:
-
初始的状态是处于CLOSE状态,CLOSE状态不是一个真实的状态,而是一个假象的起点和终点。
-
客户端调用connect以后会发送SYN同步报文给服务端,然后进入SYN-SENT状态。客户端将保持这个状态直到它收到服务端的确认包。
-
如果在SYN-SENT状态接收到服务端的确认包,它将发送服务端SYN报文的确认包,同时进入ESTABLISHED,表示自己已经准备好发送数据。
对于服务端而言:
- 初始状态同样是
CLOSED
状态 - 在执行listen方法后进入LISTEN状态,等待客户端连接
- 当收到客户端的SYN同步报文以后,会回复确认,同时发送自己的SYN同步报文。这时服务端进入SYN-RCVD阶段等待客户端的确认
- 当收到客户端的确认报文以后,进入
ESTABLISHED
状态。这时双方可以互相发数据了。
同时打开
TCP 支持同时打开,但是非常罕见,使用场景也比较有限,不过我们还是简单介绍一下。它们的包交互过程是怎么样的?TCP 状态变化又是怎么样的呢?
包交互的过程如下图:
以其中一方为例,记为 A,另外一方记为 B
- 最初的状态是
CLOSED
- A 发起主动打开,发送
SYN
给 B,然后进入SYN-SENT
状态 - A 还在等待 B 回复的
ACK
的过程中,收到了 B 发过来的SYN
,what are you 弄啥咧,A 没有办法,只能硬着头皮回复SYN+ACK
,随后进入SYN-RCVD
- A 依旧死等 B 的 ACK
- 好不容易等到了 B 的 ACK,对于 A 来说连接建立成功
问题
握手可以变为四次吗?
其实理论上完全是可以的,把三次握手的第二次的 SYN+ACK 拆成先回ACK包,再发 SYN 包就变成了「四次握手」
与 FIN 包不同的是,一般情况下,SYN 包都不携带数据,收到客户端的 SYN 包以后不用等待,可以立马回复 SYN+ACK,四次握手理论上可行,但是现实中我还没有见过。