伺服器關了正在串連的用戶端socket_fd,用戶端做出的反應過程
當伺服器close一個串連時,若client端接著發資料。根據TCP協議的規定,會收到一個RST響應,client再往這個伺服器發送資料時,系統會發出一個SIGPIPE訊號給進程,告訴進程這個串連已經斷開了,不要再寫了。 根據訊號的預設處理規則SIGPIPE訊號的預設執行動作是terminate(終止、退出),所以client會退出。若不想用戶端退出可以把SIGPIPE設為SIG_IGN 如: signal(SIGPIPE,SIG_IGN);這時SIGPIPE交給了系統處理。 伺服器採用了fork的話,要收集垃圾進程,防止殭屍進程的產生,可以這樣處理: signal(SIGCHLD,SIG_IGN); 交給系統init去回收。
這裡子進程就不會產生殭屍進程了。
===============================================================================
關於SIGPIPE導致的程式退出
收集一些網上的資料,以便參考:
http://blog.chinaunix.net/u2/69143/showart_1087349.html
當伺服器close一個串連時,若client端接著發資料。根據TCP協議的規定,會收到一個RST響應,client再往這個伺服器發送資料時,系統會發出一個SIGPIPE訊號給進程,告訴進程這個串連已經斷開了,不要再寫了。
根據訊號的預設處理規則SIGPIPE訊號的預設執行動作是terminate(終止、退出),所以client會退出。若不想用戶端退出可以把SIGPIPE設為SIG_IGN
如: signal(SIGPIPE,SIG_IGN);
這時SIGPIPE交給了系統處理。
伺服器採用了fork的話,要收集垃圾進程,防止殭屍進程的產生,可以這樣處理:
signal(SIGCHLD,SIG_IGN); 交給系統init去回收。
這裡子進程就不會產生殭屍進程了。
http://www.cublog.cn/u/31357/showart_242605.html
好久沒做過C開發了,最近重操舊業。
聽說另外一個項目組socket開發遇到問題,發送端和接受端資料大小不一致。建議他們採用writen的重發機制,以避免訊號中斷錯誤。採用後還是有問題。PM讓我幫忙研究下。
UNP n年以前看過,很久沒做過底層開發,手邊也沒有UNP vol1這本書,所以做了個測試程式,研究下實際可能發生的情況了。
測試環境:AS3和redhat 9(預設沒有nc)
先下載unp源碼:
wget http://www.unpbook.com/unpv13e.tar.gz
tar xzvf *.tar.gz;
configure;make lib.
然後參考str_cli.c和tcpcli01.c,寫了測試代碼client.c
#include "unp.h"
#define MAXBUF 40960
void processSignal(int signo)
{
printf("Signal is %d/n", signo);
signal(signo, processSignal);
}
void
str_cli(FILE *fp, int sockfd)
{
char sendline[MAXBUF], recvline[MAXBUF];
while (1) {
memset(sendline, 'a', sizeof(sendline));
printf("Begin send %d data/n", MAXBUF);
Writen(sockfd, sendline, sizeof(sendline));
sleep(5);
}
}
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
signal(SIGPIPE, SIG_IGN);
//signal(SIGPIPE, processSignal);
if (argc != 2)
err_quit("usage: tcpcli [port]");
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[1]));
Inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd); /* do it all */
exit(0);
}
為了方便觀察錯誤輸出,lib/writen.c也做了修改,加了些日誌:
/* include writen */
#include "unp.h"
ssize_t /* Write "n" bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
printf("Begin Writen %d/n", nleft);
if ( (nwritten = write(fd, ptr, nleft)) = 0) {
if (nwritten 0 && errno == EINTR) {
printf("intterupt/n");
nwritten = 0; /* and call write() again */
}
else
return(-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
printf("Already write %d, left %d, errno=%d/n", nwritten, nleft, errno);
}
return(n);
}
/* end writen */
void
Writen(int fd, void *ptr, size_t nbytes)
{
if (writen(fd, ptr, nbytes) != nbytes)
err_sys("writen error");
}
client.c放在tcpclieserv目錄下,修改了Makefile,增加了client.c的編譯目標
client: client.c
${CC} ${CFLAGS} -o $@ $ ${LIBS}
接著就可以開始測試了。
測試1 忽略SIGPIPE訊號,writen之前,對方關閉接受進程
本機服務端:
nc -l -p 30000
本機用戶端:
./client 30000
Begin send 40960 data
Begin Writen 40960
Already write 40960, left 0, errno=0
Begin send 40960 data
Begin Writen 40960
Already write 40960, left 0, errno=0
執行到上步停止服務端,client會繼續顯示:
Begin send 40960 data
Begin Writen 40960
writen error: Broken pipe(32)
結論:可見write之前,對方socket中斷,發送端write會返回-1,errno號為EPIPE(32)
測試2 catch SIGPIPE訊號,writen之前,對方關閉接受進程
修改用戶端代碼,catch sigpipe訊號
//signal(SIGPIPE, SIG_IGN);
signal(SIGPIPE, processSignal);
本機服務端:
nc -l -p 30000
本機用戶端:
make client
./client 30000
Begin send 40960 data
Begin Writen 40960
Already write 40960, left 0, errno=0
Begin send 40960 data
Begin Writen 40960
Already write 40960, left 0, errno=0
執行到上步停止服務端,client會繼續顯示:
Begin send 40960 data
Begin Writen 40960
Signal is 13
writen error: Broken pipe(32)
結論:可見write之前,對方socket中斷,發送端write時,會先調用SIGPIPE響應函數,然後write返回-1,errno號為EPIPE(32)
測試3 writen過程中,對方關閉接受進程
為了方便操作,加大1次write的資料量,修改MAXBUF為4096000
本機服務端:
nc -l -p 30000
本機用戶端:
make client
./client 30000
Begin send 4096000 data
Begin Writen 4096000
執行到上步停止服務端,client會繼續顯示:
Already write 589821, left 3506179, errno=0
Begin Writen 3506179
writen error: Connection reset by peer(104)
結論:可見socket write中,對方socket中斷,發送端write會先返回已經發送的位元組數,再次write時返回-1,errno號為ECONNRESET(104)
為什麼以上測試,都是對方已經中斷socket後,發送端再次write,結果會有所不同呢。從後來找到的UNP5.12,5.13能找到答案
The client's call to readline may happen before the server's RST is received by the client, or it may happen after. If the readline happens before the RST is received, as we've shown in our example, the result is an unexpected EOF in the client. But if the RST arrives first, the result is an ECONNRESET ("Connection reset by peer") error return from readline.
以上解釋了測試3的現象,write時,收到RST.
What happens if the client ignores the error return from readline and writes more data to the server? This can happen, for example, if the client needs to perform two writes to the server before reading anything back, with the first write eliciting the RST.
The rule that applies is: When a process writes to a socket that has received an RST, the SIGPIPE signal is sent to the process. The default action of this signal is to terminate the process, so the process must catch the signal to avoid being involuntarily terminated.
If the process either catches the signal and returns from the signal handler, or ignores the signal, the write operation returns EPIPE.
以上解釋了測試1,2的現象,write一個已經接受到RST的socket,系統核心會發送SIGPIPE給發送進程,如果進程catch/ignore這個訊號,write都返回EPIPE錯誤.
因此,UNP建議應用根據需要處理SIGPIPE訊號,至少不要用系統預設的處理方式處理這個訊號,系統預設的處理方式是退出進程,這樣你的應用就很難查處處理進程為什麼退出。
http://blog.csdn.net/shcyd/archive/2006/10/28/1354577.aspx
在Unix系統下,如果send在等待協議傳送資料時網路斷開的話,調用send的進程會接收到一個SIGPIPE訊號,進程對該訊號的預設處理是進程終止。
在Unix系統下,如果recv函數在等待協議接收資料時網路斷開了,那麼調用recv的進程會接收到一個SIGPIPE訊號,進程對該訊號的預設處理是進程終止。
處理方法:
在初始化時調用signal(SIGPIPE,SIG_IGN)忽略該訊號(只需一次)
其時send或recv函數將返回-1,errno為EPIPE,可視情況關閉socket或其他處理
gdb:
gdb預設收到sigpipe時中斷程式,可調用handle SIGPIPE nostop print
相關
(1)SIG_DFL訊號專用的預設動作:
(a)如果預設動作是暫停線程,則該線程的執行被暫時掛起。當線程暫停期間,發送給線程的任何附加訊號都不交付,直到該線程開始執行,但是SIGKILL除外。
(b)把掛起訊號的訊號動作設定成SIG_DFL,且其預設動作是忽略訊號 (SIGCHLD)。
(2)SIG_IGN忽略訊號
(a)該訊號的交付對線程沒有影響
(b)系統不允許把SIGKILL或SIGTOP訊號的動作設定為SIG_DFL
(3)指向函數的指標--捕獲訊號
(a)訊號一經交付,接收線程就在指定地址上執行訊號捕獲程式。在訊號捕 獲函數返回後,接受線程必須在被中斷點恢複執行。
(b)用C語言函數調用的方法進入訊號捕捉程式:
void func (signo)
int signo;
func( )是指定的訊號捕捉函數,signo是正被交付訊號的編碼
(c)如果SIGFPE,SIGILL或SIGSEGV訊號不是由C標準定義的kill( )或raise( )函數所產生,則從訊號SIGFPE,SIGILL,SIGSEGV的訊號捕獲函數正常返回後線程的行為是未定義的。
(d)系統不允許線程捕獲SIGKILL和SIGSTOP訊號。
(e)如果線程為SIGCHLD訊號建立訊號捕獲函數,而該線程有未被等待的以終止的子線程時,沒有規定是否要產生SIGCHLD訊號來指明那個子線程。
每一種訊號都被OSKit給予了一個符號名,對於32位的i386平台而言,一個字32位,因而訊號有32種。下面的表給出了常用的符號名、描述和它們的訊號值。
符號名 訊號值 描述 是否符合POSIX
SIGHUP 1 在控制終端上檢測到掛斷或控制線程死亡 是
SIGINT 2 互動注意訊號 是
SIGQUIT 3 互動中止訊號 是
SIGILL 4 檢測到非法硬體的指令 是
SIGTRAP 5 從陷阱中回朔 否
SIGABRT 6 異常終止訊號 是
SIGEMT 7 EMT 指令 否
SIGFPE 8 不正確的算術操作訊號 是
SIGKILL 9 終止訊號 是
SIGBUS 10 匯流排錯誤 否
SIGSEGV 11 檢測到非法的記憶體調用 是
SIGSYS 12 系統call的錯誤參數 否
SIGPIPE 13 在無讀者的管道上寫 是
SIGALRM 14 報時訊號 是
SIGTERM 15 終止訊號 是
SIGURG 16 IO通道緊急訊號 否
SIGSTOP 17 暫停訊號 是
SIGTSTP 18 互動暫停訊號 是
SIGCONT 19 如果暫停則繼續 是
SIGCHLD 20 子線程終止或暫停 是
SIGTTIN 21 後台線程組一成員試圖從控制終端上讀出 是
SIGTTOU 22 後台線程組的成員試圖寫到控制終端上 是
SIGIO 23 允許I/O訊號 否
SIGXCPU 24 超出CPU時限 否
SIGXFSZ 25 超出檔案大小限制 否
SIGVTALRM 26 虛時間警報器 否
SIGPROF 27 側面時間警報器 否
SIGWINCH 28 視窗大小的更改 否
SIGINFO 29 訊息請求 否
SIGUSR1 30 保留作為使用者自訂的訊號1 是
SIGUSR2 31 保留作為使用者自訂的訊號 是
注意:Linux訊號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的訊號機制比較簡單和原始,後來在實踐中暴露出一些問題,因此,把那些建立在早期機制上的訊號叫做"不可靠訊號",訊號值小於SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的訊號都是不可靠訊號。這就是"不可靠訊號"的來源。它的主要問題是:進程每次處理訊號後,就將對訊號的響應設定為預設動作。在某些情況下,將導致對訊號的錯誤處理;因此,使用者如果不希望這樣的操作,那麼就要在訊號處理函數結尾再一次調用signal(),重新安裝該訊號。
另外,我再做一些補充,產生RST響應以至於系統發出SIGPIPE訊號,應該分為兩種情況:
1. 用戶端到服務端之間網路斷掉,或者服務端斷電等,物理串連斷掉了,這種情況下用戶端不會退出,send函數正常執行,不會感覺到自己出錯。因為由於物理網路斷開,服務端不會給用戶端回應錯誤訊息,沒有RST響應,自然也不會產生SIGPIPE訊號。但是當服務端再恢複正常的時候,對用戶端send來的訊息會產生RST響應,用戶端就收到SIGPIPE訊號了,程式退出,但是這時send函數是能夠返回 -1的。可以進行異常處理。
2.用戶端到服務端的網路能通,服務程式掛掉,用戶端程式會馬上退出,因為服務端能正常返回錯誤訊息,用戶端收到,SIGPIPE訊號就產生了。不過我不確定此時服務端返回是的RST響應,抓包來看沒有RST標誌。水平有限,唯寫到這了。
轉載聲明: 本文轉自 http://linux.chinaunix.net/techdoc/net/2008/10/10/1037290.shtml (Linux 時代)