有時候代碼中需要連續(xù)多次調(diào)用write,可能還來不及調(diào)用read得知對方已關(guān)閉了連接就被SIGPIPE信號終止掉了,這就需要在初始化時調(diào)用sigaction處理SIGPIPE信號,對于這個信號的處理我們通常忽略即可,signal(SIGPIPE, SIG_IGN);如果SIGPIPE信號沒有導(dǎo)致進(jìn)程異常退出(捕捉信號/忽略信號),write返回-1并且errno為EPIPE(Broken pipe)。
#include <unistd.h>
int close(int fd);
close 關(guān)閉了自身數(shù)據(jù)傳輸?shù)膬蓚€方向。
#include <sys/socket.h>
int shutdown(int sockfd, int how);
shutdown 可以選擇關(guān)閉某個方向或者同時關(guān)閉兩個方向,shutdown how = 0 or how = 1 or how = 2 (SHUT_RD orSHUT_WR or SHUT_RDWR),后兩者可以保證對等方接收到一個EOF字符(即發(fā)送了一個FIN段),而不管其他進(jìn)程是否已經(jīng)打開了這個套接字。而close不能保證,只有當(dāng)某個sockfd的引用計數(shù)為0,close 才會發(fā)送FIN段,否則只是將引用計數(shù)減1而已。也就是說只有當(dāng)所有進(jìn)程(可能fork多個子進(jìn)程都打開了這個套接字)都關(guān)閉了這個套接字,close 才會發(fā)送FIN 段。
所以說,如果是調(diào)用shutdown how = 1 ,則意味著往一個已經(jīng)發(fā)送出FIN的套接字中寫是允許的,接收到FIN段僅代表對方不再發(fā)送數(shù)據(jù),但對方還是可以讀取數(shù)據(jù)的,可以讓對方可以繼續(xù)讀取緩沖區(qū)剩余的數(shù)據(jù)。
下面使用shutdown 修改客戶端程序,在前面講過的使用select函數(shù)修改后的客戶端程序基礎(chǔ)上,修改很小一部分:
C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if(FD_ISSET(fd_stdin,&rset))
{
if(fgets(sendbuf,sizeof(sendbuf),stdin)==NULL)
{
stdineof=1;//表示已經(jīng)輸入完畢
/*關(guān)閉sock的寫端,還能夠接收數(shù)據(jù),在sock的發(fā)送緩沖區(qū)末尾添加一個FIN段*/
shutdown(sock,SHUT_WR);
}
else
{
writen(sock,sendbuf,strlen(sendbuf));

memset(sendbuf,0,sizeof(sendbuf));
}
}
為了測試我們想要的效果,需要在select函數(shù)修改后的服務(wù)器端程序的 134 行代碼之后,即writen 之前 sleep(4); 目的是接收到客戶端數(shù)據(jù)后不馬上回射回去,睡眠4s 后在客戶端已經(jīng)關(guān)閉連接的情況下再發(fā)送數(shù)據(jù)。
先運行服務(wù)器端程序,再運行客戶端程序,在客戶端標(biāo)準(zhǔn)輸入,迅速敲入兩行:AAAAAn BBBBBn 然后按下ctrl+d 即fgets 會返回NULL,然后調(diào)用shutdown關(guān)閉寫端,雖然服務(wù)器端延時才發(fā)送數(shù)據(jù),此時客戶端寫端已經(jīng)關(guān)閉,但還是可以讀取到回射回來的數(shù)據(jù),服務(wù)器端最后得到一個FIN段,read 返回0,打印輸出 client close ,并且close(conn); 而客戶端在讀取服務(wù)端回射回來的兩次數(shù)據(jù)后,再次read 也返回0,故打印server connect close,break退出循環(huán),進(jìn)程順利退出。從下面的輸出還可以看出,因為延時的關(guān)系,所以不像以前那樣發(fā)射一行就回射一行。
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echoser_select
recv connect ip=127.0.0.1 port=54010
fdsgfgd
gfedg
client close
...........................
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_select_shutdown
local ip=127.0.0.1 port=54010
fdsgfgd
gfedg
fdsgfgd
gfedg
server connect close
如果我們將客戶端程序中的shutdown 改成了 close,那么當(dāng)延時后服務(wù)器端發(fā)送數(shù)據(jù)給客戶端時,客戶端的讀端和寫端都已經(jīng)關(guān)閉,第一次發(fā)AAAAA會返回一個RST段,根據(jù)本文前面所說,再次發(fā)BBBBB直接產(chǎn)生SIGPIPE信號,默認(rèn)會終止進(jìn)程,但因為我們已經(jīng)設(shè)置了忽略SIGPIPE信號,所以服務(wù)器端進(jìn)程不會被終止,但客戶端也會出錯,因為回到while循環(huán)開頭,select阻塞等待時發(fā)現(xiàn)套接字的讀端已經(jīng)關(guān)閉,所以不能再關(guān)心可讀事件了,select會返回-1,錯誤碼是 EBADF: Bad File Descriptor。
參考:
《Linux C 編程一站式學(xué)習(xí)》
《TCP/IP詳解 卷一》
《UNP》
愛華網(wǎng)


