linux網路編程之socket(十):shutdown 與 close 函數 的區別

來源:互聯網
上載者:User

假設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》

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.