众所周知,tcp连接的建立有一个过程称为三次握手,是因为tcp客户端和服务端连接的建立要经过三次的数据交互。

    1. TCP A TCP B
    2. 1. CLOSED LISTEN
    3. 2. SYN-SENT --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED
    4. 3. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
    5. 4. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED
    6. 5. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED

    上面是RFC 793中关于tcp连接建立的案例。
    TCP A代表的是客户端,TCP B代表的是服务端。服务端的初始状态是LISTEN,表示服务端正在监听某个端口,等待客户端的连接。
    此时TCP A向B发送了一个TCP报文,表示要与B建立连接。这个报文有一个序列号为100,然后标志位中SYN标志为1。
    服务端B收到该报文后,知道A想与自己建立连接。B为了告诉A自己同意A的连接,也向A发送了一个报文,该报文的序列号是300,确认号ACK是101,表示B已经收到了A序列号101之前的所有数据了。标志位中,SYN和ACK都为1。
    最后一次握手,A向B发送了标志位ACK为1的报文,表示A收到了B的SYN报文,此时连接正式建立,接下来A和B就可以互相发送数据了。

    从另一个角度理解三次握手: 第一次A向B发送SYN报文,B收到该报文后,确认了自己可以接收到A的报文,接收是正常的。 然后B向A发送SYN报文,A收到报文后,A就确认了自己发送和接收都是正常的。 最后一次A发向B的ACK报文,是为了让B确认自己的发送是正常的。换句话说,是A通知B,你的上一条报文我已经收到了,你发送过来的数据没问题。

    下面我们通过抓包工具来实际地看下tcp三次握手的具体细节:
    抓包准备,使用Java编写socket服务端和客户端,具体代码见另一篇文章
    先启动服务端,然后启动客户端,客户端连接到服务端。
    此时在抓包工具中,抓到了四个tcp报文:
    屏幕快照 2020-07-26 15.23.14.png
    首先可能会奇怪,三次握手为什么会有四个报文,其实看上图,第四个报文前写着”TCP Window Update”,应该是关于窗口的,所以只看前三个报文就可以。
    关于三次握手的细节,我们重点关注标志位、序列号和确认号。

    1. 第一条报文,源端口53196是客户端的端口,目的端口17121是服务端的端口,所以是客户端发给服务端的报文。标志位为SYN,序列号为0,没有确认号,因为确认号只有在有ACK标志的时候才有效。
    2. 第二条报文,从端口可看出,是服务端发给客户端。标志位有SYN和ACK。序列号为0,而确认号为1,表示服务端已经收到了从客户端发来的序列号为1之前的所有报文。
    3. 第三条报文是客户端发送给服务端的确认,ACK标志位,序列号为1,确认号为1.客户端告诉服务端,我收到了你的上一条报文。