1. 程式人生 > >《unix網路程式設計》(11)tcp伺服器的幾種常見狀況分析

《unix網路程式設計》(11)tcp伺服器的幾種常見狀況分析

accept返回前連線終止

        《unix網路程式設計》(10)wait/waitpid處理僵死程序(SIGCHLD訊號)該文章中介紹了客戶正常終止時,由於父程序使用wait處理SIGCHLD訊號時,阻塞於accept,核心會使得accept返回一個EINTR錯誤(被中斷的系統呼叫)。

         另一種情形也會導致accept返回非致命的錯誤:三次握手完成後,客戶TCP傳送一個RST(復位);此時伺服器端即將呼叫accept。

伺服器程序終止

啟動客戶和伺服器,客戶端傳送”test1“正常回顯,然後殺死伺服器子程序,模擬伺服器程序崩潰。其過程如下:

(1)kill子程序,子程序所有描述符關閉,導致向客戶發FIN,客戶響應一個ACK。這是TCP連線終止的前部分。

(2)SIGCHLD訊號發到父程序,被正確處理。

(3)客戶沒其他問題,但是客戶阻塞於fgets呼叫上,等待從終端輸入。

(4)此時,在客戶端鍵入”test2 after kill”:客戶TCP把資料傳送到伺服器,TCP允許這樣做,因為客戶收到FIN指標是伺服器程序關閉了連線的伺服器端,從而不再發資料(半連線)。FIN的接收沒有告知客戶TCP伺服器程序終止。伺服器收到資料,由於之前的連線已經終止,所以響應一個RST

(5)然而客戶看不到RST,因為它呼叫write後立即呼叫read,而read的是第2步中的伺服器的FIN,所以read立即返回0。客戶沒想到會收到EOF,所以以出錯資訊退出(伺服器過早終止)。

        該例子的問題在於:FIN到達套接字,客戶阻塞於fgets。客戶實際上對應兩個描述符——套接字和使用者輸入,它不能如我們編寫的客戶端那樣阻塞於這兩個中的一個,而應該阻塞於其中任何一個輸入上。這正是select和poll的目的之一。

SIGPIPE訊號

         如上邊小節中,往一個已經接收FIN的套接中寫是允許的,接收到FIN僅僅代表對方不再發送資料。但是在收到RST後繼續寫操作,核心會向程序傳送SIGPIPE訊號;其預設行為是終止程序。對於這個訊號的處理我們通常忽略即可。無論程序捕獲該訊號並從其處理函式返回,還是忽略,寫操作都會返回EPIPE錯誤。

伺服器主機崩潰

必須在不同主機執行伺服器和客戶機才能模擬該情形。

啟動伺服器、客戶機;從網路中斷開伺服器。此時過程:

(1)伺服器崩潰後,不會發送任何東西。

(2)客戶鍵入文字,由write寫入核心,再由客戶TCP傳送。客戶阻塞於read,等待回射。

(3)客戶TCP持續重傳資料試圖收到伺服器的ACK。一般會等待數分鐘才放棄重傳。客戶最終放棄(假設此段時間內伺服器沒恢復)。既然客戶阻塞於read,所有返回錯誤ETIMEOUT。然而如果某個中間路由判定伺服器不可達,會響應一個目的不可達ICMP訊息,那麼返回的錯誤時EHOSTUNREACH或ENETUNREACH。

         儘管最終客戶最終知道伺服器不可達或崩潰,但已經過了數分鐘。所用方法是對read設定一個超時

        上述的情形要給伺服器發資料才知道伺服器崩潰。如果不主動發資料還想檢測伺服器主機崩潰,就要採用SO_KEEPALIVE套接字選項

伺服器崩潰後重啟

伺服器崩潰後重啟,其TCP丟失了崩潰前的所有連線資訊,因此對所受到的來自客戶的資料響應一個RST;客戶收到RST時正阻塞於read,所以會返回ECONNRESET錯誤。

客戶要檢測伺服器崩潰與否,又不主動發資料,就需要採用SO_KEEPALIVE套接字選項或某些客戶/伺服器心搏函式

伺服器主機關機

Unix系統關機,init程序向所有程序傳送SIGTERM訊號(可被捕獲),等待5-20秒,然後給還在執行程序發SIGKILL訊號(不能捕獲)。

如果不捕獲SIGTERM,那麼伺服器將由SIGKILL終止。當伺服器子程序終止時,和上邊“伺服器程序終止”一節中一樣,正如那節中所說,要在客戶端使用select後poll使得伺服器一經終止,客戶就能檢測到