1.TCP三次握手圖:
2.TCP四次揮手圖:
①第一次:主機1(可以使客戶端,也可以是服務器端),設置Seqr和Ack,向主機2發送一個FIN報文段;此時,主機1進入FIN_WAIT_1狀態;這表示主機1沒有數據要發送給主機2了;
②第二次:主機2收到了主機1發送的FIN報文段,向主機1回一個ACK報文段,Ack為Seq加1,同時進入CLOSE_WAIT狀態;主機1進入FIN_WAIT_2狀態;主機2告訴主機1,我“同意”你的關閉請求;
③第三次:主機2向主機1發送FIN報文段,請求關閉連接,同時主機2進入LAST_ACK狀態;
④第四次:主機1收到主機2發送的FIN報文段,向主機2發送ACK報文段,然后主機1進入TIME_WAIT狀態;主機2收到主機1的ACK報文段以后,就關閉連接;此時,主機1等待2MSL后依然沒有收到回復,則證明Server端已正常關閉,那好,主機1也可以關閉連接了。
注:MSL是指Max Segment Lifetime,即數據包在網絡中的最大生存時間。每種TCP協議的實現方法均要指定一個合適的MSL值,如RFC1122給出的建議值為2分鍾,又如Berkeley體系的TCP實現通常選擇30秒作為MSL值。這意味着TIME_WAIT的典型持續時間為1-4分鍾。
2.TCP四次揮手過程中通信雙方狀態解析:
-
FIN_WAIT_1: 其實FIN_WAIT_1和FIN_WAIT_2狀態的真正含義都是表示等待對方的FIN報文。而這兩種狀態的區別是:FIN_WAIT_1狀態實際上是當SOCKET在ESTABLISHED狀態時,它想主動關閉連接,向對方發送了FIN報文,此時該SOCKET即進入到FIN_WAIT_1狀態。而當對方回應ACK報文后,則進入到FIN_WAIT_2狀態,當然在實際的正常情況下,無論對方何種情況下,都應該馬上回應ACK報文,所以FIN_WAIT_1狀態一般是比較難見到的,而FIN_WAIT_2狀態還有時常常可以用netstat看到。(主動方)
-
FIN_WAIT_2:實際上FIN_WAIT_2狀態下的SOCKET,表示半連接,也即有一方要求close連接,但另外還告訴對方,我暫時還有點數據需要傳送給你(ACK信息),稍后再關閉連接。(主動方)
-
CLOSE_WAIT:表示在等待關閉。當對方close一個SOCKET后發送FIN報文給自己,你系統毫無疑問地會回應一個ACK報文給對方,此時則進入到CLOSE_WAIT狀態。接下來呢,實際上你真正需要考慮的事情是察看你是否還有數據發送給對方,如果沒有的話,那么你也就可以 close這個SOCKET,發送FIN報文給對方,也即關閉連接。所以你在CLOSE_WAIT狀態下,需要完成的事情是等待你去關閉連接。(被動方)
-
LAST_ACK: 被動關閉一方在發送FIN報文后,最后等待對方的ACK報文。當收到ACK報文后,也即可以進入到CLOSED可用狀態了。(被動方)
-
TIME_WAIT: 表示收到了對方的FIN報文,並發送出了ACK報文,就等2MSL后即可回到CLOSED可用狀態了。如果FIN WAIT1狀態下,收到了對方同時帶FIN標志和ACK標志的報文時,可以直接進入到TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。(主動方),一旦出錯了,其實會從FIN_WAIT_2狀態直接進入關閉狀態,並不會有time_wait狀態, 例如http://mxdxm.iteye.com/blog/895187!
-
CLOSED: 表示連接中斷。
3.為什么要有TIME_WAIT這個狀態?
為什么主動關閉的一端不直接進入closed狀態,而是要先進入time_wait並且停留兩倍的MSL時長呢?這是因為TCP是建立在不可靠網絡上的可靠協議。如果主動關閉的一端收到被動關閉一端的發出的FIN包后,返回ACK包,同時進入TIME_WAIT,但是由於網絡的原因,主動關閉一端發送的ACK包可能會延遲,從而觸發被動關閉一方重傳FIN包,這樣一來一回極端情況正好是2MSL。如果主動關閉的一端直接close或者不到兩倍MSL時間就關閉,那么被動關閉發出重傳FIN包到達,可能出現的問題是:舊的連接不存在,系統只能返回RST包;新的TCP連接已經建立,延遲包可能會干擾新連接。這都可能導致TCP不可靠。(所以需要舊的連接一直保持time_wait 2MSL時間)
主動關閉的一方在發送最后一個 ack 后就會進入 TIME_WAIT 狀態 停留2MSL(max segment lifetime)時間這個
是TCP/IP必不可少的,也就是“解決”不了的。也就是TCP/IP設計者本來是這么設計的
主要有兩個原因:
1. 防止上一次連接中的包,迷路后重新出現,影響新連接
(經過2MSL,上一次連接中所有的重復包都會消失)
2. 可靠的關閉TCP連接
在主動關閉方發送的最后一個 ack(fin) ,有可能丟失,這時被動方會重新發
fin, 如果這時主動方處於 CLOSED 狀態 ,就會響應 rst 而不是 ack。所以
主動方要處於 TIME_WAIT 狀態,而不能是 CLOSED 。
TIME_WAIT 並不會占用很大資源的,除非受到攻擊。
還有,如果一方 send 或 recv 超時,就會直接進入 CLOSED 狀態
5.TIME_WAIT太多怎么解決?
修改/etc/sysctl.conf :
net.ipv4.tcp_tw_reuse = 1 #表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連接,默認為0,表示關閉;
net.ipv4.tcp_tw_recycle = 1 #表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關閉。net.ipv4.tcp_timestamps 開啟時,net.ipv4.tcp_tw_recycle開啟才能生效,。
net.ipv4.tcp_timestamps = 1 #表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關閉。
net.ipv4.tcp_fin_timeout = 2 #用來設置保持在FIN_WAIT_2狀態的時間
保存后sysctl -p生效
################################ 案例分析 ##############################
情景:客戶端連續登錄兩次服務器,服務器應用把第一個socket斷掉,客戶端利用失效的第一個socket發送消息給服務器。
服務器利用抓包工具tcpdump能抓到數據包,但是服務器應用socket無法接收到數據!
原因剖析:服務器應用主動斷開socket的時候,客戶端其實就能監聽到socket斷掉了,如果客戶端自己不斷開socket,那么
就會進入close_wait狀態,眾所周知,只要你的應用有close_wait狀態,那么肯定是程序代碼出了問題,如下圖分析:
我們都知道tcp是全雙工的,假如我們把上圖左側作為服務器,右側作為客戶端,服務器主動斷開socket,給客戶端發送FIN報文,但是
客戶端一直不斷開socket,所以客戶端一直處於close_wait狀態。這時候socket其實已經失效了,這時客戶端繼續給服務器發消息,發的
第一條FIN報文服務器機器是能接收到數據包的,但是由於應用已經斷開socket,所以應用socket是接收不到數據的。所以說客戶端進入
close_wait后,他發的第一條消息是正確的,然后就處於斷開狀態。后面哪怕持續發信息也沒任何作用,哪怕不報錯。
正常操作應該是一方主動斷開socket之后,另外一方捕捉到然后立即正確的被動斷開socket(一旦程序代碼出錯就會處於close_wait狀態),
最終主動斷開socket的一方進入time_wait狀態,整個tcp四次揮手才算完美完成!
我們可以從上圖看出,主動斷開socket的一方,最終都會進入time_wait狀態,這是因為它需要處於這個狀態來防止,ack丟失后,另外一方
重復發FIN。需要等待2msl的時間。
PS:tcp長連接其實不是需要考慮time_wait狀態,耗盡句柄資源的問題,因為服務器主動斷開socket的情景不多。
分析工具:
ubuntu下安裝抓包工具tcpdump
sudo apt-get install wireshark 或 tcpdump
監聽端口數據包:
sudo tcpdump tcp port 8007
查看端口連接狀態
lsof -i:8007
查看tcp所有狀態
netstat -an|awk '/tcp/ {print $6}'|sort|uniq -c
查看tcp單個狀態
netstat -an | grep CLOSE_WAIT | wc -l