假設server和client 已經建立了串連,server調用了close, 發送FIN 段給client(其實不一定會發送FIN段,後面再說),此時server不能再通過socket發送和接收資料,此時client調用read,如果接收到FIN 段會返回0,但client此時還是可以write 給server的,write調用只負責把資料交給TCP發送緩衝區就可以成功返回了,所以不會出錯,而server收到資料後應答一個RST段,表示伺服器已經不能接收資料,串連重設,client收到RST段後無法立刻通知應用程式層,只把這個狀態儲存在TCP協議層。如果client再次調用write發資料給server,由於TCP協議層已經處於RST狀態了,因此不會將資料發出,而是發一個SIGPIPE訊號給應用程式層,SIGPIPE訊號的預設處理動作是終止程式。
有時候代碼中需要連續多次調用write,可能還來不及調用read得知對方已關閉了串連就被SIGPIPE訊號終止掉了,這就需要在初始化時調用sigaction處理SIGPIPE訊號,對於這個訊號的處理我們通常忽略即可,signal(SIGPIPE, SIG_IGN); 如果SIGPIPE訊號沒有導致進程異常退出,write返回-1並且errno為EPIPE。
#include <unistd.h>
int close(int fd);
close 關閉了自身資料轉送的兩個方向。
#include <sys/socket.h>
int shutdown(int sockfd, int how);
shutdown 可以選擇關閉某個方向或者同時關閉兩個方向,shutdown how = 1 or how = 2 (SHUT_WR or SHUT_RDWR),可以保證對等方接收到一個EOF字元(即發送了一個FIN段),而不管其他進程是否已經開啟了這個通訊端。而close不能保證,只有當某個sockfd的引用計數為0,close 才會發送FIN段,否則只是將引用計數減1而已。也就是說只有當所有進程(可能fork多個子進程都開啟了這個通訊端)都關閉了這個通訊端,close
才會發送FIN 段。
所以說,如果是調用shutdown how = 1 ,則意味著往一個已經接收FIN的通訊端中寫是允許的,接收到FIN段僅代表對方不再發送資料,但對方還是可以讀取資料的,可以讓對方可以繼續讀取緩衝區剩餘的資料。
下面使用shutdown 修改用戶端程式,在前面講過的使用select函數修改後的用戶端程式基礎上,修改很小一部分:
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; //表示已經輸入完畢 /* 關閉sock的寫端,還能夠接收資料,在sock的緩衝區末尾添加一個FIN段 */ shutdown(sock, SHUT_WR); } else { writen(sock, sendbuf, strlen(sendbuf)); memset(sendbuf, 0, sizeof(sendbuf)); } } |
為了測試我們想要的效果,需要在select函數修改後的伺服器端程式 的 134 行代碼之後,即writen 之前 sleep(4); 目的是接收到用戶端資料後不馬上回射回去,睡眠4s 後在用戶端已經關閉串連的情況下再發送資料。
先運行伺服器端程式,再運行用戶端程式,在用戶端標準輸入,迅速敲入兩行:AAAAA\n BBBBB\n 然後按下ctrl+d 即fgets 會返回NULL,然後調用shutdown關閉寫端,雖然伺服器端延時才發送資料,此時用戶端寫端已經關閉,但還是可以讀取到回射回來的資料,伺服器端最後得到一個FIN段,read 返回0,列印輸出 client close ,並且close(conn); 而用戶端在讀取服務端回射回來的兩次資料後,再次read 也返回0,故列印 server
connect close,break退出迴圈,進程順利退出。從下面的輸出還可以看出,因為延時的關係,所以不像以前那樣發射一行就回射一行。
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,那麼當延時後伺服器端發送資料給用戶端時,用戶端的讀端和寫端都已經關閉,第一次發AAAAA會返回一個RST段,根據本文前面所說,再次發BBBBB直接產生SIGPIPE訊號,預設會終止進程,但因為我們已經設定了忽略SIGPIPE訊號,所以伺服器端進程不會被終止,但用戶端也會出錯,因為回到while迴圈開頭,select阻塞等待時發現通訊端的讀端已經關閉,所以不能再關心可讀事件了,select會返回-1,錯誤碼是
EBADF: Bad File Descriptor。
參考:
《Linux C 編程一站式學習》
《TCP/IP詳解 卷一》
《UNP》