題目描述
1.什么是三次握手,四次揮手?為什么分別要三次與四次?
2.tcp協議中,close_wait與time_wait狀態分別代表什么含義,為什么要設計這兩種狀態,解決了什么問題?
3.time_wait為什么要等待2MSL
4.平時排查問題中遇見大量close_wait應該如何處理?
參考答案
1.首先要理解TCP協議的定位,從wikipedia上抄一下定義:傳輸控制協議(英語:Transmission Control Protocol,縮寫:TCP)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,由IETF的RFC 793定義。在簡化的計算機網絡OSI模型中,它完成第四層傳輸層所指定的功能。用戶數據報協議(UDP)是同一層內另一個重要的傳輸協議。
並且TCP是一個雙向全雙工的傳輸協議,這個后面再詳細解釋。
然后再聊一下一下tcp的6個標志位:
- SYN(synchronous) Synchronize sequence numbers to initiate a connection
- ACK(acknowledgement) The ackowledgement number is valid
- PSH(傳送) The receiver should pass this data to the application as soon as possible
- FIN(finish結束) The sender is finished sending data.
- RST(reset重置) Reset the connection
- URG(urgent) The URGENT POINTER field contains valid data
step1:client端嘗試建立連接,發送了一個tcp報文,這個tcp報文header里的SYN標志位是1,同時會隨機生成ISN(initial sequence number)作為sequence number塞到報文的header里,這時候client端進入SYN-SENT狀態
step2:server端接受到client端發送的請求,返回報文表示已經收到建立連接的請求,並同時嘗試建立server端到客戶端的連接。所以這時候header里的SYN與ACK標志位同時被置為1,且server端生成自己的ISN作為sequence number,而ack number則為client端的seq number+1。
step3:client端收到報文,這是client->server端的連接已經被建立,意味着已經可以從client端向server端發送數據,但ciient端同時也要發送ack消息給server端,這時候ack標志位為1,ack number為server端的seq number+1,當server端接受到這條消息表示連接建立成功。
然后回答為什么需要三次握手:首先tcp協議是可靠的,所以通過ack機制保證發送方可以確認接收方是否接收到了消息。其次,我們前面提到了tcp是雙向全雙工的,這意味着什么?意味着一旦建立連接后,client和server都可以主動像對方主動發送消息,且發送數據的時候同時也能夠接受數據。所以step1+step2其實是建議client->server端的連接,而step2+step3建立的是server->client的連接。其實到這里我們已經理解了,step2其實是出於效率方面的考慮把2步並為1步,在返回ack的時候同時合並了一個建立連接的報文,所以由4步並為了3步。

step1:client主動發送fin包給server,此時的seq number為u,client端進入fin_wait_1狀態
step2:server端接受到消息,發送ack包給client,此時的seq number為v,server端進入close_wait狀態(一般這個時候會通知應用層進行相關的操作),client此時進入fin_wait_2狀態,client->server的連接已經被close
step3:在等待應用層完成相關操作后,server端也發送fin包,嘗試關閉server->client的連接,此時server端進入last_ack狀態(看到很多地方說這個時候會帶上一個client端中斷的ack,這個我理解沒什么必要?不知道有沒有人可以幫我解釋一下)
step4:client端返回ack給server端,並進入time_wait狀態,持續2msl
為什么要四次揮手:其實大致流程跟三次握手差不多,唯一的差別只是中間兩步並沒有並成一步,之所以沒有並成一步應該是給應用層一點時間來做close的准備工作。
2.close_wait與time_wait在上面應該已經都說了,close_wait表示接受到了對方申請關閉連接的請求,但是這個時候可能你的應用層還有事情需要處理,否則這2步就可以合並成一步,直接進入last-ack狀態了。而當完成step4之后,server端可能因為網絡原因沒有接受到ack,這個時候會重復step3,如果client端沒有進入time_wait狀態而是直接關閉,將會導致server端無法正常關閉。同時,由於網絡中的消息傳遞是存在延時的,如果發送完ack之后立即進入closed狀態,然后在相同的port上立即建立新的連接,則有可能接受到上一次連接的殘存消息,可能會導各種不可預知的致異常出現。
3.考慮最壞的情況,step4client發送給server,這個時候消息丟了,這一步驟最長占用1msl,server端判斷消息丟失后,重復step3重新發送fin給client,這一步最長占用1msl,所以加起來就是2msl。
4.其實這個問題問得不太好,我們先要了解大量close_wait有什么危害。因為linux分配給一個用戶的文件句柄是有限的(一般是1024),如果time_wait或者colse_wait兩種狀態被一直保持,這些通道會被一直占有,很快就會報出too many open files in system(這個是os層面的報錯),然后就gg了。所以首先有有意識大量的close_wait是有危險的,然后根據上面所說的,close_wait是由對象主動發起斷開,而你一直沒有返回ack,那么你就會一直維持在close_wait狀態(一般來說都是client端與server端建立了連接,然后client端忘記close,server端開始主動斷開連接,但是client端沒有響應,然后就掛起在那了)。然后平時發生問題首先可以等到機器上netstat -na | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}',看看連接的情況(不過有monitor的話一般這一步可以省略)。然后netstat -an,看一下都是哪些連接在time_wait狀態,根據server端的ip理論上可以判斷出來是哪個連接,再然后就是去看這一塊的代碼,有沒有忘記釋放連接的地方。
后記:現實場景中可能會更加復雜,比如消息的亂序,丟失等種種情況,上面只討論了正向流程,想再深入的話可能還需要考慮更多的異常流程。
參考文獻:
1.https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE
2.http://telescript.denayer.wenk.be/~hcr/cn/idoceo/tcp_header.html


