linux Socket資料發送中訊號SIGPIPE及相關errno的研究

來源:互聯網
上載者:User
socket send 錯誤

(2008-07-05 15:50:53)


轉載

標籤:

socket
send
it
 

Socket資料發送中訊號SIGPIPE及相關errno的研究
好久沒做過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);       

   
exit(0);
}

 

為了方便觀察錯誤輸出,lib/writen.c也做了修改,加了些日誌:

 

 

#include   
"unp.h"

ssize_t                       

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;       

           
}
           
else
               
return(-1);           

       
}

       
nleft -= nwritten;
       
ptr += nwritten;
       
printf("Already write %d, left %d, errno=%d/n", nwritten, nleft,
errno);
    }
   
return(n);
}

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訊號,至少不要用系統預設的處理方式處理這個訊號,系統預設的處理方式是退出進程,這樣你的應用就很難查處處理進程為什麼退出。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.