假設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函數 修改後的客戶端程序基礎上,修改很小一部分:
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。