在能够使用TCP收发数据前,有一些状态需要同步(比如双方的起始发送序列号ISN)。当双方发送信令,同步完这些状态后,就可以认为TCP连接建立了。所以建立TCP连接的过程也就是同步这些状态的过程,在建立连接的过程中,TCP协议栈要管理的,也就是这些状态。

打开连接

建立TCP连接前,两个主机的IP必须是联通的,所以建立连接的过程其实中两个IP地址之间“打开”连接的过程,双方需要交换的只是一个初始序列号,英文叫ISN(Initial Sequence Number),如下所示:

1: A --> SYN(ISN=111) --> B
2: A <-- ACK(SEQ=112) <-- B
3: A <-- SYN(ISN=211) <-- B
4: A --> ACK(SEQ=212) --> B
  1. A向B发送自己的ISN 111,这个ISN是SYN这个控制位的序列号;
  2. 当B收到A发来的SYN之后,把序列号加1(表示SYN已经收到,期待下一个字节),然后通过ACK向A发送确认;
  3. 和第1步一样,B向A发送自己的ISN。
  4. 和第2步一样,A向B发送确认ACK。

完成上面的四步,连接就建立完成了。但是如果你仔细观察,发现2和3其实可以合并起来做,于是变成:

1: A --> SYN(ISN=111) --> B
2: A <-- ACK(SEQ=112) SYN(ISN=211) <-- B
3: A --> ACK(SEQ=212) --> B

于是TCP建立连接时候的four-way handshake就变成了three-way handshake。中文一般把这翻译成三次握手,其实不太准确,个人觉得叫三向握手比较准确。

以下是从RFC793中摘抄出来,并简化的TCP连接建立的状态图:

                              +---------+ ---------\      active OPEN
                              |  CLOSED |            \    -----------
                              +---------+<---------\   \   snd SYN
                                |                    \   \
                   passive OPEN |                      \   \
                                V                        \   \
                              +---------+                 |    \
                              |  LISTEN |                 |     |
                              +---------+                 |     |
                   rcv SYN      |                         |     |
                  -----------   |                         |     V
 +---------+      snd SYN,ACK  /                        +---------+
 |         |<-----------------                          |         |
 |   SYN   |                    rcv SYN                 |   SYN   |
 |   RCVD  |<-------------------------------------------|   SENT  |
 |         |                    snd ACK                 |         |
 |         |------------------           ---------------|         |
 +---------+   rcv ACK of SYN  \       /  rcv SYN,ACK   +---------+
               --------------   |     |   -----------
                      x         |     |     snd ACK
                                V     V
                              +---------+
                              |  ESTAB  |
                              +---------+

上图的右侧,走的是TCP连接的active OPEN过程(客户端向服务器发起连接建立请求);上图的左侧,走的是TCP连接的passive OPEN过程(服务器侦听端口等待客户端连接)。不管是左侧还是右侧,都是从CLOSED状态走到ESTAB(连接建立)状态。需要特别注意的是从SYN SENT到SYN RCVD之间有个箭头,这是为了应付两个主机同时发起TCP连接的情况。就是为了解决在上面four-way handshake建立连接的时候,第2步和第3步互换的情况。

关闭连接

关闭连接的过程和上面上面four-way handshake的过程基本一致,不过不是使用SYN控制位,而是使用FIN控制位:

1: A --> FIN(SEQ=111) --> B
2: A <-- ACK(SEQ=112) <-- B
3: A <-- FIN(SEQ=211) <-- B
4: A --> ACK(SEQ=212) --> B

一个有趣的概念,TCP允许单方关闭连接。也就是说当A发完数据的时候可以向B发送FIN命令,关闭到B的通道;但是此时B还是可以往A发送数据。

以下是从RFC793中摘抄出来,并简化的TCP连接关闭的状态图:

                              +---------+
                              |  ESTAB  |
                              +---------+
                       CLOSE    |     |    rcv FIN
                      -------   |     |    -------
 +---------+          snd FIN  /       \   snd ACK          +---------+
 |  FIN    |<-----------------           ------------------>|  CLOSE  |
 | WAIT-1  |------------------                              |   WAIT  |
 +---------+          rcv FIN  \                            +---------+
   | rcv ACK of FIN   -------   |                            CLOSE  |
   | --------------   snd ACK   |                           ------- |
   V        x                   V                           snd FIN V
 +---------+                  +---------+                   +---------+
 |FINWAIT-2|                  | CLOSING |                   | LAST-ACK|
 +---------+                  +---------+                   +---------+
   |                rcv ACK of FIN |                 rcv ACK of FIN |
   |  rcv FIN       -------------- |    Timeout=2MSL -------------- |
   |  -------              x       V    ------------        x       V
    \ snd ACK                 +---------+delete TCB         +---------+
     ------------------------>|TIME WAIT|------------------>| CLOSED  |
                              +---------+                   +---------+

上图的左侧是主动发送FIN关闭连接的过程(通常是上层的应用调用了close操作),上图的右侧是被动接收FIN关闭的过程(在这过程中TCP需要告知上层应用当前的状况)。因为有半连接的这种中间状态存在,所以左侧有FIN WAIT-1和FIN WAIT-2状态,用于接收对端发送的数据;右侧有CLOSE WAIT状态,用于发送当前还未发送完的数据。

注意,RFC793要求主动关闭连接的那一方等上2MSL(MSL是Maximum Segment Lifetime的意思,指分段在网络中存活的时间,RFC793假设MSL是2分钟),然后再真正关闭连接。也就是为了确保网络中没有此次TCP连接接发的数据存留了,再进行下一次连接操作,这是个非常保守的策略。实际应用的时候可能会根据具体情况调整,参考WikiPedia的MSL页面

异常处理

上面的两个状态图并没有描述错误处理。 TCP的错误处理比较简单,主要分为两类,一类是收到不该收的数据,另一类是发送数据无应答。

首先是收到不该收的数据的问题。这种情况下,TCP的处理方式是使用RST控制位来重置连接,也就是我们经常会见到的错误显示“connetion reset by peer”,下面是一些例子:

  • 发送完SYN,收到的不是ACK
  • 发送完SYN,收到的ACK里面的序列号不对
  • 收到对方发来的数据,但是连接已经关闭
  • ……

第二类是发送数据无应答,也就是数据可能在传输过程丢掉了,或者对方没有给应答。这种情况下TCP需要一个重传的计时器来判断超时:

  • 发送SYN,没有收到ACK,计时器超时后重传
  • 发送完数据,没有收到ACK,计时器超时后重传
  • ……

(完)