我寫了一個服務(wù)器程序,在Linux下測試,然后用C++寫了客戶端用千萬級別數(shù)量的短鏈接進(jìn)行壓力測試.
最后問題確定為, 對一個對端已經(jīng)關(guān)閉的socket調(diào)用兩次write, 第二次將會生成SIGPIPE信號, 該信號默認(rèn)結(jié)束進(jìn)程.
具體的分析可以結(jié)合TCP的"四次握手"關(guān)閉. TCP是全雙工的信道, 可以看作兩條單工信道, TCP連接兩端的兩個端點各負(fù)責(zé)一條. 當(dāng)對端調(diào)用close時, 雖然本意是關(guān)閉整個兩條信道, 但本端只是收到FIN包. 按照TCP協(xié)議的語義, 表示對端只是關(guān)閉了其所負(fù)責(zé)的那一條單工信道, 仍然可以繼續(xù)接收數(shù)據(jù). 也就是說, 因為TCP協(xié)議的限制, 一個端點無法獲知對端的socket是調(diào)用了close還是shutdown.
對一個已經(jīng)收到FIN包的socket調(diào)用read方法, 如果接收緩沖已空, 則返回0, 這就是常說的表示連接關(guān)閉. 但第一次對其調(diào)用write方法時, 如果發(fā)送緩沖沒問題, 會返回正確寫入(發(fā)送). 但發(fā)送的報文會導(dǎo)致對端發(fā)送RST報文, 因為對端的socket已經(jīng)調(diào)用了close, 完全關(guān)閉, 既不發(fā)送, 也不接收數(shù)據(jù). 所以, 第二次調(diào)用write方法(假設(shè)在收到RST之后), 會生成SIGPIPE信號, 導(dǎo)致進(jìn)程退出.
為了避免進(jìn)程退出, 可以捕獲SIGPIPE信號, 或者忽略它, 給它設(shè)置SIG_IGN信號處理函數(shù):
signal(SIGPIPE, SIG_IGN);
這樣, 第二次調(diào)用write方法時, 會返回-1, 同時errno置為SIGPIPE. 程序便能知道對端已經(jīng)關(guān)閉.
在linux下寫socket的程序的時候,如果嘗試send到一個disconnected socket上,就會讓底層拋出一個SIGPIPE信號。
這個信號的缺省處理方法是退出進(jìn)程,大多數(shù)時候這都不是我們期望的。因此我們需要重載這個信號的處理方法。調(diào)用以下代碼,即可安全的屏蔽SIGPIPE:
signal (SIGPIPE, SIG_IGN);
我的程序產(chǎn)生這個信號的原因是:
client端通過 pipe 發(fā)送信息到server端后,就關(guān)閉client端, 這時server端,返回信息給 client 端時就產(chǎn)生Broken pipe 信號了,服務(wù)器就會被系統(tǒng)結(jié)束了。
對于產(chǎn)生信號,我們可以在產(chǎn)生信號前利用方法 signal(int signum, sighandler_t handler) 設(shè)置信號的處理。如果沒有調(diào)用此方法,系統(tǒng)就會調(diào)用默認(rèn)處理方法:中止程序,顯示提示信息(就是我們經(jīng)常遇到的問題)。我們可以調(diào)用系統(tǒng)的處理方法,也可以自定義處理方法。
系統(tǒng)里邊定義了三種處理方法:
(1)SIG_DFL信號專用的默認(rèn)動作:
(a)如果默認(rèn)動作是暫停線程,則該線程的執(zhí)行被暫時掛起。當(dāng)線程暫停期間,發(fā)送給線程的任何附加信號都不交付,直到該線程開始執(zhí)行,但是SIGKILL除外。
(b)把掛起信號的信號動作設(shè)置成SIG_DFL,且其默認(rèn)動作是忽略信號 (SIGCHLD)。
(2)SIG_IGN忽略信號
(a)該信號的交付對線程沒有影響
(b)系統(tǒng)不允許把SIGKILL或SIGTOP信號的動作設(shè)置為SIG_DFL
3)SIG_ERR
項目中我調(diào)用了signal(SIGPIPE, SIG_IGN), 這樣產(chǎn)生