socket服務器開發中的SO_REUSEADDR選項與讓人心煩的TIME_WAIT

1 發現問題  

  我在開發一個socket服務器程序並反覆調試的時候,發現了一個讓人無比心煩的情況:每次kill掉該服務器進程並重新啓動的時候,都會出現bind錯誤:error:98,Address already in use。然而再kill掉該進程,再次重新啓動的時候,就bind成功了。真讓人摸不着頭腦。難道一定要嘗試兩次才顯得真誠?這不科學!

  我的第一反應是kill進程的時候,並沒有完全釋放掉socket資源,倒致第二次啓動的時候,bind失敗。那麼第三次怎麼又成功了呢?

  查資料:有人說是TIME_WAIT在搗鬼。

  回想一下,Linux下的TIME_WAIT大概是2分鐘,這樣也合情合理。那麼沒有釋放掉的資源是什麼呢,是端口嗎?機智的我立刻決定做實驗找出答案。啓動服務器程序,在與客戶建立連接之後,kill掉服務器。飛快地在terminal裏輸入命令:netstat -an|grep 9877。這裏9877是我服務器打算綁定的端口。果然:

  

  結果顯示9877端口正在被使用,並處於TCP中的TIME_WAIT狀態。再過兩分鐘,我再執行命令netstat -an|grep 9877,世界清靜了,什麼都沒有。

  終於找到了答案:果然是TIME_WAIT在搗鬼。

2 解決問題

  問題找到了,可是怎麼解決問題呢。如何才能結束掉這個TIME_WAIT狀態呢?否則每次調試之後,都要巴巴地等上兩分鐘,再進行下次調試。這太蠢了!想了好久,也沒想出解決辦法。那TCP中有沒有能關閉掉TIME_WAIT的選項呢?翻書!UNP中第7章就是講socket選項的。還真沒有找到。但是,我找到了SO_REUSEADDR選項。關於此選項,書上說可以起到以下4個不同的功用:

  (1)SO_REUSEADDR允許啓動一個監聽服務器並捆綁其衆所周知的端口,即使以前建立的將該端口用作他們的本地端口的連接仍存在。

  (2)允許在同一端口上啓動同一服務器的多個實例,只要每個實例捆綁一個不同的本地IP地址即可。

  (3)SO_REUSEADDR 允許單個進程捆綁同一端口到多個套接字上,只要每次捆綁指定不同的本地IP地址即可。

  (4)SO_REUSEADDR允許完全重複的捆綁:當一個IP地址和端口號已綁定到某個套接字上時,如果傳輸協議支持,同樣的IP地址和端口還可以捆綁到另一個套接字上。一般來說本特性僅支持UDP套接字。

  我遇到的情況正好符合情況1,並且書上說了:“所有TCP服務器都應該指定本套接字選項,以允許服務器在這種情形下被重新啓動。”那麼試試看嘍。

  

      上面兩行代碼,把此套接字listenFd設置爲允許地址重用(on=1,如果on=0就是不允許重用了)。這樣每次bind的時候,如果此端口正在使用的話,bind就會把端口“搶”過來。就不會報錯了。完美解決問題。

3 察漏補缺

  既然TIME_WAIT這麼討厭,那它的存在有什麼意義呢?畢竟服務器端已經中斷掉連接了呀。記得之前在看UNP的時候,上面好像有提到過,繼續翻書:

  書上說,TIME_WAIT狀態有兩個存在的理由:

  (1)可靠地實現TCP全雙工連接的終止;

  (2)允許老的重複分節在網絡中消逝。

  原來如此,解釋一下,上個圖:

  

  (1)如果服務器最後發送的ACK因爲某種原因丟失了,那麼客戶一定會重新發送FIN,這樣因爲有TIME_WAIT的存在,服務器會重新發送ACK給客戶,如果沒有TIME_WAIT,那麼無論客戶有沒有收到ACK,服務器都已經關掉連接了,此時客戶重新發送FIN,服務器將不會發送ACK,而是RST,從而使客戶端報錯。也就是說,TIME_WAIT有助於可靠地實現TCP全雙工連接的終止。

  (2)如果沒有TIME_WAIT,我們可以在最後一個ACK還未到達客戶的時候,就建立一個新的連接。那麼此時,如果客戶收到了這個ACK的話,就亂套了,必須保證這個ACK完全死掉之後,才能建立新的連接。也就是說,TIME_WAIT允許老的重複分節在網絡中消逝。

  回到我們的問題,由於我並不是正常地經過四次斷開的方式中斷連接,所以並不會存在最後一個ACK的問題。所以,這樣是安全的。不過,最終的服務器版本,還是不要設置爲端口可複用的。切記。

  加油加油。好好學習,天天向上。

  另外:UNP是本好書,要好好看呀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章