網絡編程-關閉連接(1)-C/C++相關系統(tǒng)調用

背景

在linux網絡編程中,經常需要編寫關閉socket的代碼,比如心跳檢測失敗需要關閉重連;網絡報異常需要關閉重連。但究竟關閉操作做了什么,卻不太清楚。目前項目使用Netty框架來實現的網絡編程,查看netty源碼可以得知,netty最終是調用了java Nio的close接口做的關閉操作,那么想研究清楚這個close操作究竟做了什么,可以從兩個方向入手,這兩個方向也是從下至上的。

  1. 搞清楚如果使用C/C++編程,應該調用哪個系統(tǒng)調用函數?函數內部做了什么,涉及到什么TCP/IP的協(xié)議參數
  2. 搞清楚java nio在調用close方法時,究竟使用了哪個系統(tǒng)調用?

本文首先解決的是第一步,搞清楚系統(tǒng)調用相關的知識。

相關系統(tǒng)調用

Linux平臺下,提供了兩個系統(tǒng)調用函數供開發(fā)人員使用:

  • close函數
  • shutdown函數
close函數
int close(int sockfd);

這個函數的具體行為由一個TCP/IP套接字選項控制:SO_LINGER

SO_LINGER的在頭文件<sys/socket.h>中定義如下:

struct linger{
int l_onoff;
int l_linger;
}

根據這個選項參數的不同,close的邏輯如下:
1)l_onoff=0,l_linger=1或者0時(這個是默認選項)

  • close會立即返回,0為成功-1為失敗
  • 調用進程在該套接字上不能再發(fā)送或接收請求
  • 接收緩沖區(qū)中的數據將會被拋棄
  • 如果發(fā)送緩沖區(qū)中還有數據,會由操作系統(tǒng)在后臺繼續(xù)發(fā)送
  • 如果套接字的引用計數變?yōu)?,則發(fā)送FIN表示關閉
    • 引用計數:進程和子進程可以共享一個套接字,每當一個進程做了close操作,引用計數就會減1
  • 最后釋放套接字的系統(tǒng)資源

2)l_onoff=1,l_linger=0時

  • close會立即返回
  • 調用進程在該套接字上不能再發(fā)送或接收請求
  • 發(fā)送和接收緩沖區(qū)中的數據都會被拋棄
  • 如果套接字的引用計數變?yōu)?,則發(fā)送RST到對端,并且狀態(tài)直接變成CLOSED
    • 注:RST沒有超時重發(fā)機制,如果對端沒有收到RST,繼續(xù)發(fā)送,那么又會促使本端發(fā)送RST,直到對方收到
  • 最后釋放套接字的系統(tǒng)資源

3)l_onoff=1,l_linger>0時

  • 如果是阻塞的socket,close函數不會立即返回;非阻塞的會立即返回
  • 調用進程在該套接字上不能再發(fā)送或接收請求
  • 接收緩沖區(qū)中的數據將會被拋棄
  • 如果發(fā)送緩沖區(qū)中還有數據,會由操作系統(tǒng)在后臺繼續(xù)發(fā)送
  • 如果套接字的引用計數變?yōu)?,則發(fā)送FIN表示關閉,在套接字狀態(tài)編程CLOSED前,如果超時時間到,返回EWOULDBLOCK錯誤
  • 最后釋放套接字的系統(tǒng)資源

總結一下:
默認情況和第三種情況對比,默認情況相當于一個異步請求,并且無法得知操作結果;第三種情況,可以在超時時間范圍內做close處理,發(fā)送未發(fā)送完畢的數據。第二種情況屬于粗暴的關閉socket,在2MSL時間范圍內如果新建立了一個“化身”(ip port dip dport都一樣的套接字),可能會被前一個套接字相關的數據所影響。
注:對2MSL不理解的小伙伴,可以看下這篇博客,講解的很清晰:
為什么tcp的TIME_WAIT狀態(tài)要維持2MSL

shutdown函數

有一種業(yè)務場景,客戶端發(fā)送數據到服務端,發(fā)送完畢后,客戶端就可以關閉客戶端寫方向的連接了,等待服務端處理。
業(yè)務需求是保證客戶端發(fā)送的數據都會被服務端應用程序接收并處理。如果使用close函數關閉連接,最多只能保證,全部數據都已經發(fā)送到了對端的接收緩沖區(qū)中(使用SO_LINGER相關配置項),但是無法確保對端的應用程序一定讀取到數據(close以后,本端socket就無法讀了)。

在這種業(yè)務場景下,如果需要確保服務端一定讀取到了數據,可以考慮使用shutdown函數。

int shutdown(int sockfd,int howto);

執(zhí)行shutdown函數,成功返回0,出錯返回-1。

howto是這個函數的設置選項:

  • SHUT_RD:關閉套接字的讀方向。讀緩沖區(qū)中的數據都會被拋棄,如果有新數據到達,都將被ACK,并且被悄悄丟棄。
  • SHUT_WR:關閉套接字的寫方向。在套接字發(fā)送緩沖區(qū)的數據都會被繼續(xù)發(fā)送過去,然后發(fā)送正常的FIN開始揮手流程。
  • SHUT_RDWR:讀和寫兩個方向都關閉

只使用shutdown函數,也無法保證滿足我們上面提到的業(yè)務需求,即保證服務端應用程序是否正確讀取數據。目前有兩種解決方式可以實現上述業(yè)務需求:

  • shutdown后,使用read函數,等待對端的FIN發(fā)送過來,此時read函數返回0
  • 應用級別確認:完全發(fā)送數據后,再讀取一個字節(jié)的數據(這個數據是客戶端和服務端的自定義協(xié)議,比如:服務端完全接受數據后,可以繼續(xù)發(fā)送一字節(jié)的數據,代表讀取成功)

第一種方式流程圖如下(摘自《Unix網絡編程》):

Image.png

第二種方式流程圖如下(摘自《Unix網絡編程》):

Image1 [2].png
close函數和shutdow函數的區(qū)別
  1. close函數會計算引用計數,當計數為0時才觸發(fā)揮手操作;shutdown函數則不需要判斷引用計數來觸發(fā)揮手操作
  2. close函數可以終止兩個方向的傳輸,shutdown可以控制只終止一個方向的
  3. close函數會關閉資源,shutdown函數不會
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容