我寫了一個伺服器程式, 在Linux下測試時, 總是莫名退出. 最後跟蹤到是write調用導致退出. 用gdb執行程式, 退出時提示"Broken pipe".
最後問題確定為, 對一個對端已經關閉的socket調用兩次write, 第二次將會產生SIGPIPE訊號, 該訊號預設結束進程.
具體的分析可以結合TCP的"四次握手"關閉. TCP是全雙工系統的通道, 可以看作兩條單工通道, TCP串連兩端的兩個端點各負責一條. 當對端調用close時, 雖然本意是關閉整個兩條通道, 但本端只是收到FIN包. 按照TCP協議的語義, 表示對端只是關閉了其所負責的那一條單工通道, 仍然可以繼續接收資料. 也就是說, 因為TCP協議的限制, 一個端點無法獲知對端的socket是調用了close還是shutdown.
對一個已經收到FIN包的socket調用read方法, 如果接收緩衝已空, 則返回0, 這就是常說的表示串連關閉. 但第一次對其調用write方法時, 如果發送緩衝沒問題, 會返回正確寫入(發送). 但發送的報文會導致對端發送RST報文, 因為對端的socket已經調用了close, 完全關閉, 既不發送, 也不接收資料. 所以, 第二次調用write方法(假設在收到RST之後), 會產生SIGPIPE訊號, 導致進程退出.
為了避免進程退出, 可以捕獲SIGPIPE訊號, 或者忽略它, 給它設定SIG_IGN訊號處理函數:
signal(SIGPIPE, SIG_IGN);
這樣, 第二次調用write方法時, 會返回-1, 同時errno置為SIGPIPE. 程式便能知道對端已經關閉.
PS: Linux下的SIGALRM似乎會每1秒鐘往後位移1毫秒, 但Windows下經過測試完全準時, 不差1毫秒.
當然還有其他方法來處理SIGPIPE
設定當前socket在進行寫操作時不產生SIGPIPE。
?
| 12 |
int
set = 1;setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void
*)&set, sizeof(int)); |
這樣做的好處在於:在某些情況 下我們並不需要一個全域的SIGPIPE handler。但是據說這種並不通用,在linux下沒有相應的定義—-但我在mac下測試通過。
轉自 http://blog.csdn.net/mustanglau/article/details/4485491