linux網絡編程(二)TCP通訊狀態
- TCP狀態轉換
- 為什么需要等待2MSL?
- 端口復用
TCP狀態轉換
tcp協議連接開始會經過三次握手,客戶端和服務器開始都會處于CLOSED狀態
第一次握手:客戶端會先發送SYN請求給服務器,客戶端處于SYN_SET狀態,
第二次握手:服務器接收到SYN后,發給客戶端ACK回答和SYN請求,服務器從LISTEN變成SYN_RCVD
第三次握手:客戶端接收到ACK和SYN請求后,發送給服務器ACK回應,客戶端從SYN_SET變成ESTABLISHED狀態,服務器接收到ACK回應后,也變為ESTABLISHED狀態
tcp協議關閉會經過四次握手,假設客戶端為主動關閉,服務器為被動關閉
第一次握手:客戶端發送FIN請求,狀態變為FIN_WAIT_1
第二次握手:服務器接收到FIN請求,同時發送ACK應答,狀態為CLOSE_WAIT,客戶端收到ACK應答后,變為FIN_WAIT_2狀態,此時處于半關閉的狀態
第三次握手:服務器發送FIN請求,狀態為LACK_ACK
第四次握手:客戶端接收到FIN請求后,發送ACK應答,狀態變為TIME_WAIT,等待2MSL之后關閉
- CLOSED:表示初始狀態。
- LISTEN:該狀態表示服務器端的某個SOCKET處于監聽狀態,可以接受連接。
- SYN_SENT:這個狀態與SYN_RCVD遙相呼應,當客戶端SOCKET執行CONNECT連接時,它首先發送SYN報文,隨即進入到了SYN_SENT狀態,并等待服務端的發送三次握手中的第2個報文。SYN_SENT狀態表示客戶端已發送SYN報文。
- SYN_RCVD: 該狀態表示接收到SYN報文,在正常情況下,這個狀態是服務器端的SOCKET在建立TCP連接時的三次握手會話過程中的一個中間狀態,很短暫。此種狀態時,當收到客戶端的ACK報文后,會進入到ESTABLISHED狀態。
- ESTABLISHED:表示連接已經建立。
- FIN_WAIT_1: FIN_WAIT_1和FIN_WAIT_2狀態的真正含義都是表示等待對方的FIN報文。區別是:FIN_WAIT_1狀態是當socket在ESTABLISHED狀態時,想主動關閉連接,向對方發送了FIN報文,此時該socket進入到FIN_WAIT_1狀態。
FIN_WAIT_2狀態是當對方回應ACK后,該socket進入到FIN_WAIT_2狀態,正常情況下,對方應馬上回應ACK報文,所以FIN_WAIT_1狀態一般較難見到,而FIN_WAIT_2狀態可用netstat看到。 - FIN_WAIT_2:主動關閉鏈接的一方,發出FIN收到ACK以后進入該狀態。稱之為半連接或半關閉狀態。該狀態下的socket只能接收數據,不能發。
- TIME_WAIT: 表示收到了對方的FIN報文,并發送出了ACK報文,等2MSL后即可回到CLOSED可用狀態。如果FIN_WAIT_1狀態下,收到對方同時帶
FIN標志和ACK標志的報文時,可以直接進入到TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。 - CLOSING: 這種狀態較特殊,屬于一種較罕見的狀態。正常情況下,當你發送FIN報文后,按理來說是應該先收到(或同時收到)對方的 ACK報文,再收到對方的FIN報文。但是CLOSING狀態表示你發送FIN報文后,并沒有收到對方的ACK報文,反而卻也收到了對方的FIN報文。什么情況下會出現此種情況呢?如果雙方幾乎在同時close一個SOCKET的話,那么就出現了雙方同時發送FIN報文的情況,也即會出現CLOSING狀態,表示雙方都正在關閉SOCKET連接。
- CLOSE_WAIT: 此種狀態表示在等待關閉。當對方關閉一個SOCKET后發送FIN報文給自己,系統會回應一個ACK報文給對方,此時則進入到CLOSE_WAIT狀態。接下來呢,察看是否還有數據發送給對方,如果沒有可以close這個SOCKET,發送FIN報文給對方,即關閉連接。所以在CLOSE_WAIT狀態下,需要關閉連接。
- LAST_ACK: 該狀態是被動關閉一方在發送FIN報文后,最后等待對方的ACK報文。當收到ACK報文后,即可以進入到CLOSED可用狀態。
為什么需要等待2MSL?
當客戶端也就是主動關閉的一端,接收到服務器也就是被動關閉的一端的FIN請求時,需要回應ACK回應,但是客戶端并不能保證ACK回應服務器一定能收到,就添加了一個保護機制,在2MSL時間內,如果服務器沒有收到ACK回應,服務器會再一次發送FIN請求,知道能收到客戶端收到ACK回應。服務器在2MSL之內一直沒有收到的話,客戶端將做超時處理,關閉失敗。
作用:
- 讓4次握手關閉流程更加可靠,4次握手的最后一個ACK是是由主動關閉方發送出去的,若這個ACK丟失,被動關閉方會再次發一個FIN過來。若主動關閉方能夠保持一個2MSL的TIME_WAIT狀態,則有更大的機會讓丟失的ACK被再次發送出去。
- 防止lost duplicate對后續新建正常鏈接的傳輸造成破壞。lost uplicate在實際的網絡中非常常見,經常是由于路由器產生故障,路徑無法收斂,導致一個packet在路由器A,B,C之間做類似死循環的跳轉。IP頭部有個TTL,限制了一個包在網絡中的最大跳數,因此這個包有兩種命運,要么最后TTL變為0,在網絡中消失;要么TTL在變為0之前路由器路徑收斂,它憑借剩余的TTL跳數終于到達目的地。但非常可惜的是TCP通過超時重傳機制在早些時候發送了一個跟它一模一樣的包,并先于它達到了目的地,因此它的命運也就注定被TCP協議棧拋棄。
端口復用
在server的TCP連接沒有完全斷開之前不允許重新監聽是不合理的
在server代碼的socket()和bind()調用之間插入如下代碼:
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));