網路編程實戰

来源:https://www.cnblogs.com/flysong/archive/2020/05/04/12826943.html
-Advertisement-
Play Games

“一段數據流從應用程式發送端,一直到應用程式接收端,總共經過了多少次拷貝?” 先看發送端,當應用程式將數據送到發送緩衝區時,調用的是 send 或 write 方法,如果緩存中沒有空間,系統調用就會失敗或者阻塞。我們說,這個動作事實上是一次”顯式拷貝“。而在這之後,數據將會按照 TCP/IP 的分層... ...


 

  “一段數據流從應用程式發送端,一直到應用程式接收端,總共經過了多少次拷貝?”

  先看發送端,當應用程式將數據送到發送緩衝區時,調用的是 send 或 write 方法,如果緩存中沒有空間,系統調用就會失敗或者阻塞。我們說,這個動作事實上是一次”顯式拷貝“。而在這之後,數據將會按照 TCP/IP 的分層再次進行拷貝,這層的拷貝對我們來說就不是顯式的了。

  接下來輪到 TCP 協議棧工作,創建 Packet 報文,並把報文發送到傳輸隊列中(qdisc),傳輸隊列是一個典型的 FIFO 隊列,隊列的最大值可以通過 ifocnfig 命令輸出的 txqueuelen 來查看。通常情況下,這個值有幾千報文大小。

  TX ring 在網路驅動和網卡之間,也是一個傳輸請求的隊列。

  網卡作為物理設備工作在物理層,主要工作是把要發送的報文保存到內部的緩存中,併發送出去。

  接下來再看接收端,報文首先到達網卡,由網卡保存在自己的接收緩存中,接下來報文被髮送至網路驅動和網卡之間的 RX ring,網路驅動從 RX ring 獲取報文 ,然後把報文發送到上層。

  這裡值得註意的是,網路驅動和上層之間沒有緩存,因為網路驅動使用 Napi 進行數據傳輸。因此,可以認為上層直接從 RX ring 中讀取報文。

  最後,報文的數據保存在套接字接收緩存中,應用程式從套接字接收緩存中讀取數據。

  這就是數據流從應用程式發送端,一直到應用程式接收端的整個歷程,你看懂了嗎?

  上面的任何一個環節稍有積壓,都會對程式性能產生影響。但好消息是,內核和網路設備供應商已經幫我們把一切都打點好了,我們看到和用到的,其實只是冰山上的一角而已。

TIME_WAIT 的作用

  TIME_WAIT 停留持續時間是固定的,是最長分節生命期 MSL(maximum segment lifetime)的兩倍,一般稱之為 2MSL。和大多數 BSD 派生的系統一樣,Linux 系統里有一個硬編碼的欄位,名稱為TCP_TIMEWAIT_LEN,其值為 60 秒。也就是說,Linux 系統停留在 TIME_WAIT 的時間為固定的 60 秒。

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-       WAIT state, about 60 seconds   */

 

  TCP 在設計的時候,做了充分的容錯性設計,比如,TCP 假設報文會出錯,需要重傳。在這裡,如果圖中主機 1 的 ACK 報文沒有傳輸成功,那麼主機 2 就會重新發送 FIN 報文。

  如果主機 1 沒有維護 TIME_WAIT 狀態,而直接進入 CLOSED 狀態,它就失去了當前狀態的上下文,只能回覆一個 RST 操作,從而導致被動關閉方出現錯誤。

  現在主機 1 知道自己處於 TIME_WAIT 的狀態,就可以在接收到 FIN 報文之後,重新發出一個 ACK 報文,使得主機 2 可以進入正常的 CLOSED 狀態。

  第二個理由和連接“化身”和報文迷走有關係,為了讓舊連接的重覆分節在網路中自然消失。

  我們知道,在網路中,經常會發生報文經過一段時間才能到達目的地的情況,產生的原因是多種多樣的,如路由器重啟,鏈路突然出現故障等。如果迷走報文到達時,發現 TCP 連接四元組(源 IP,源埠,目的 IP,目的埠)所代表的連接不復存在,那麼很簡單,這個報文自然丟棄。

close 和 shutdown 的差別

  第一個差別:close 會關閉連接,並釋放所有連接對應的資源,而 shutdown 並不會釋放掉套接字和所有的資源。

  第二個差別:close 存在引用計數的概念,並不一定導致該套接字不可用;shutdown 則不管引用計數,直接使得該套接字不可用,如果有別的進程企圖使用該套接字,將會受到影響。

  第三個差別:close 的引用計數導致不一定會發出 FIN 結束報文,而 shutdown 則總是會發出 FIN 結束報文,這在我們打算關閉連接通知對端的時候,是非常重要的。

 

TCP 是一種流式協議

  在發送端,當我們調用 send 函數完成數據“發送”以後,數據並沒有被真正從網路上發送出去,只是從應用程式拷貝到了操作系統內核協議棧中,至於什麼時候真正被髮送,取決於發送視窗、擁塞視窗以及當前發送緩衝區的大小等條件。也就是說,我們不能假設每次 send 調用發送的數據,都會作為一個整體完整地被髮送出去。

  如果我們考慮實際網路傳輸過程中的各種影響,假設發送端陸續調用 send 函數先後發送 network 和 program 報文

  接收端緩衝區保留了沒有被取走的數據,隨著應用程式不斷從接收端緩衝區讀出數據,接收端緩衝區就可以容納更多新的數據。如果我們使用 recv 從接收端緩衝區讀取數據,發送端緩衝區的數據是以位元組流的方式存在的,無論發送端如何構造 TCP 分組,接收端最終受到的位元組流總是像下麵這樣:

xxxxxxxxxxxxxxxxxnetworkprogramxxxxxxxxxxxx

  關於接收端位元組流,有兩點需要註意:

  第一,這裡 netwrok 和 program 的順序肯定是會保持的,也就是說,先調用 send 函數發送的位元組,總在後調用 send 函數發送位元組的前面,這個是由 TCP 嚴格保證的;

  第二,如果發送過程中有 TCP 分組丟失,但是其後續分組陸續到達,那麼 TCP 協議棧會緩存後續分組,直到前面丟失的分組到達,最終,形成可以被應用程式讀取的數據流。

  保證網路位元組序一致,POSIX 標準提供瞭如下的轉換函數:

uint16_t htons (uint16_t hostshort)
uint16_t ntohs (uint16_t netshort)
uint32_t htonl (uint32_t hostlong)
uint32_t ntohl (uint32_t netlong)

 

這裡函數中的 n 代表的就是 network,h 代表的是 host,s 表示的是 short,l 表示的是 long,分別表示 16 位和 32 位的整數。

socket的行為

  發送端通過調用 send 函數之後,數據流並沒有馬上通過網路傳輸出去,而是存儲在套接字的發送緩衝區中,由網路協議棧決定何時發送、如何發送。當對應的數據發送給接收端,接收端回應 ACK,存儲在發送緩衝區的這部分數據就可以刪除了,但是,發送端並無法獲取對應數據流的 ACK 情況,也就是說,發送端沒有辦法判斷對端的接收方是否已經接收發送的數據流,如果需要知道這部分信息,就必須在應用層自己添加處理邏輯,例如顯式的報文確認機制。

  從接收端來說,也沒有辦法保證 ACK 過的數據部分可以被應用程式處理,因為數據需要接收端程式從接收緩衝區中拷貝,可能出現的狀況是,已經 ACK 的數據保存在接收端緩衝區中,接收端處理程式突然崩潰了,這部分數據就沒有辦法被應用程式繼續處理。

 

  TCP 連接建立之後,能感知 TCP 鏈路的方式是有限的,一種是以 read 為核心的讀操作,另一種是以 write 為核心的寫操作。

 

網路中斷造成的對端無 FIN 包

  很多原因都會造成網路中斷,在這種情況下,TCP 程式並不能及時感知到異常信息。除非網路中的其他設備,如路由器發出一條 ICMP 報文,說明目的網路或主機不可達,這個時候通過 read 或 write 調用就會返回 Unreachable 的錯誤。

  在沒有 ICMP 報文的情況下,TCP 程式並不能理解感應到連接異常。如果程式是阻塞在 read 調用上,那麼很不幸,程式無法從異常中恢復。這顯然是非常不合理的,不過,我們可以通過給 read 操作設置超時來解決

  如果程式先調用了 write 操作發送了一段數據流,接下來阻塞在 read 調用上,結果會非常不同。Linux 系統的 TCP 協議棧會不斷嘗試將發送緩衝區的數據發送出去,大概在重傳 12 次、合計時間約為 9 分鐘之後,協議棧會標識該連接異常,這時,阻塞的 read 調用會返回一條 TIMEOUT 的錯誤信息。如果此時程式還執著地往這條連接寫數據,寫操作會立即失敗,返回一個 SIGPIPE 信號給應用程式。

系統崩潰造成的對端無 FIN 包

  當系統突然崩潰,如斷電時,網路連接上來不及發出任何東西。這裡和通過系統調用殺死應用程式非常不同的是,沒有任何 FIN 包被髮送出來。

  這種情況和網路中斷造成的結果非常類似,在沒有 ICMP 報文的情況下,TCP 程式只能通過 read 和 write 調用得到網路連接異常的信息,超時錯誤是一個常見的結果。

  不過還有一種情況需要考慮,那就是系統在崩潰之後又重啟,當重傳的 TCP 分組到達重啟後的系統,由於系統中沒有該 TCP 分組對應的連接數據,系統會返回一個 RST 重置分節,TCP 程式通過 read 或 write 調用可以分別對 RST 進行錯誤處理。

  如果是阻塞的 read 調用,會立即返回一個錯誤,錯誤信息為連接重置(Connection Resest)。

  如果是一次 write 操作,也會立即失敗,應用程式會被返回一個 SIGPIPE 信號。

對端有 FIN 包發出

  對端如果有 FIN 包發出,可能的場景是對端調用了 close 或 shutdown 顯式地關閉了連接,也可能是對端應用程式崩潰,操作系統內核代為清理所發出的。從應用程式角度上看,無法區分是哪種情形。

  阻塞的 read 操作在完成正常接收的數據讀取之後,FIN 包會通過返回一個 EOF 來完成通知,此時,read 調用返回值為 0。這裡強調一點,收到 FIN 包之後 read 操作不會立即返回。你可以這樣理解,收到 FIN 包相當於往接收緩衝區里放置了一個 EOF 符號,之前已經在接收緩衝區的有效數據不會受到影響。

TCP並不總是“可靠”的?

 

https://www.cnblogs.com/jiu0821/p/7229568.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本教程說明如何使用OWIN自托管Web API框架,在控制台應用程式中托管ASP.NET Web API。 .NET開放Web界面(OWIN)定義了.NET Web伺服器和Web應用程式之間的抽象。OWIN將Web應用程式與伺服器分離,這使OWIN成為在IIS之外以自己的進程自托管Web應用程式的理 ...
  • UWP已經有好幾個Bilibili的客戶端,最近又多了一個: "嗶哩 Microsoft Store" 作者雲之幻是一位很擅長設計的UWP開發者,我也從他那裡學到了很多設計方面的技巧。它還是一位Bilibili的Up主,主打PowerPoint和UWP教學。 "雲之幻的個人空間 嗶哩嗶哩 ( ゜ ゜ ...
  • using System; using System.Collections.Generic; using System.IO.Ports; using System.Text; //串口通訊類 public class SerialPortManager { //聲明一個靜態的串口資源 priva ...
  • 在資料庫的數據日積月累的積累下,業務資料庫中的單表數據想必也越來越大,大到百萬、千萬、甚至上億級別的數據,這個時候就很有必要進行資料庫讀寫分離、以及單表分多表進行存儲,提高性能 ...
  • 更安全的rm命令,保護重要數據 網上流傳的安全的rm,幾乎都是提供一個rm的"垃圾"回收站,在伺服器環境上來說,這實非良方。 我想,提供一個安全的rm去保護一些重要的文件或目錄不被刪除,避免出現重要數據誤刪的悲劇,或許才是更佳方案。 我寫了一個腳本:https://github.com/malong ...
  • Linux常用命令 一、控制台相關命令 控制台命令就是指通過字元界面輸入的可以操作系統的命令。我們現在要瞭解的是基於Linux操作系統的基本控制台命令。不同於圖形模式的一種類似文本編輯器的運行命令的環境。在遠程登陸控制或是操作沒有圖形環境的Linux系統時,控制台命令就有很大的用途了,建議大家一定要 ...
  • 本文主要講解如何安裝VM插件實現高級功能(下篇) 上篇主要講WndowsOS下利用VM虛擬機部署MACOS ...
  • 這裡分享嵌入式領域有用有趣的項目/工具以及一些熱點新聞,農曆年分二十四節氣,希望在每個交節之日準時發佈一期。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...