這篇要開始講解當程式發生問題時封包的狀態為何?以及一些斷線分析的範例程式碼。
斷線 RST封包問題
當伺服器 close一個連接時,若client端接著發數據。根據TCP協定的規定,會收到一個RST響應,client再往這個服務器發送數據時,系統會發出一個SIGPIPE信號給進程,告訴進程這個連接已經斷開了,不要再寫了。
根據信號的默認處理規則SIGPIPE信號的默認執行動作是terminate(終止、退出),所以client會退出。若不想客戶端退出可以把SIGPIPE設為SIG_IGN
如: signal(SIGPIPE,SIG_IGN);// 這時SIGPIPE交給了系統處理。
註:服務器採用了fork的話,要收集垃圾進程,防止僵屍進程的產生,可以這樣處理: signal(SIGCHLD,SIG_IGN); //交給系統init去回收。這邊拿unpv13e 裡面的範例來修改當例子
這裡子進程就不會產生僵屍程序了。
#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信號,在write之前,對方關閉程式
本機服務端:
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,結果會有所不同呢。
從後來找到的書本中5.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錯誤.
因此,建議程式根據需要處理SIGPIPE信號做處理,盡量不要讓系統自動幫你把程式關掉,這樣你的應用就很難查處處理程式為什麼退出。
每一種信號都被OSKit給予了一個符號名,對於32位的i386平台而言,一個字32位,因而信號有32種。下面的表給出了常用的符號名、描述和它們的信號值。
更多的表格訊息請查看man page
符號名 信號值 描述 是否符合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 保留作為用戶自定義的信號 是
相關文章
檔案傳送與效能分析(1)-實驗過程與結果
檔案傳送與效能分析(2)-基本的檔案傳送
檔案傳送與效能分析(3)-斷線分析
檔案傳送與效能分析(4)-程式下載
沒有留言:
張貼留言
俗話說
凡走過必留下痕跡,凡住過必留下鄰居
凡爬過必留下樓梯,凡來過必留下IP
看過文章之後歡迎留下您寶貴的意見喔!