在能够使用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
- A向B发送自己的ISN 111,这个ISN是SYN这个控制位的序列号;
- 当B收到A发来的SYN之后,把序列号加1(表示SYN已经收到,期待下一个字节),然后通过ACK向A发送确认;
- 和第1步一样,B向A发送自己的ISN。
- 和第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,计时器超时后重传
- ……
(完)