計算機網路再次整理————tcp例子[五]

敖毛毛發表於2022-02-05

前言

本文介紹一些tcp的例子,然後不斷完善一下。

正文

服務端:

// See https://aka.ms/new-console-template for more information

using System.Net;
using System.Net.Sockets;

var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var ipAddress = IPAddress.Parse("127.0.0.1");
EndPoint endPoint = new IPEndPoint(ipAddress, 8888);
socket.Bind(endPoint);
socket.Listen();
while (true)
{
    Console.WriteLine("開始接收");
    var clientSocket = socket.Accept();
    Console.WriteLine("接收到訊息");
    var receiveMessage = new Byte[1000];
    clientSocket.Receive(receiveMessage);
    Console.WriteLine("receive message is:"+System.Text.Encoding.UTF8.GetString(receiveMessage));
    clientSocket.Close();
}

客戶端:

using System.Net;
using System.Net.Sockets;

var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var ipAddress = IPAddress.Parse("127.0.0.1");
EndPoint endPoint = new IPEndPoint(ipAddress, 8888);
socket.Connect(endPoint);
socket.Send(System.Text.Encoding.UTF8.GetBytes("hello service"));
socket.Send(System.Text.Encoding.UTF8.GetBytes("hello service2"));
Console.WriteLine("傳送成功");
Console.ReadLine();

服務端列印:

可以看客戶端傳送了兩次,但是服務端一次就讀取出來了,這就是著名的粘包。

為什麼會有粘包這個現象呢?首先來分析一下這個粘包的原因,抓包即可。

看到第一次傳送的包:

看到第二次傳送的包:

看來跟客戶端沒有關係了。

這是因為接收方先把收到的資料放在系統接收緩衝區,使用者程式從該緩衝區取資料,若下一包資料到達時前一包資料尚未被使用者程式取走,則下一包資料放到系統接收緩衝區時就接到前一包資料之後,而使用者程式根據預先設定的緩衝區大小從系統接收緩衝區取資料,這樣就一次取到了多包資料。

那麼有沒有可能粘包是客戶端造成的,或者說傳送方造成的?當然也是很多有可能的,這裡就不作演示,單純的理論一下。

TCP 協議是面向連線的、可靠的、基於位元組流的傳輸層通訊協議,應用層交給 TCP 協議的資料並不會以訊息為單位向目的主機傳輸,這些資料在某些情況下會被組合成一個資料段傳送給目標的主機。

比如說nagle 演算法:

Nagle 演算法是一種通過減少資料包的方式提高 TCP 傳輸效能的演算法。因為網路 頻寬有限,它不會將小的資料塊直接傳送到目的主機,而是會在本地緩衝區中等待更多待傳送的資料,這種批量傳送資料的策略雖然會影響實時性和網路延遲,但是能夠降低網路擁堵的可能性並減少額外開銷。

在早期的網際網路中,Telnet 是被廣泛使用的應用程式,然而使用 Telnet 會產生大量只有 1 位元組負載的有效資料,每個資料包都會有 40 位元組的額外開銷,頻寬的利用率只有 ~2.44%,Nagle 演算法就是在當時的這種場景下設計的。

當應用層協議通過 TCP 協議傳輸資料時,實際上待傳送的資料先被寫入了 TCP 協議的緩衝區,如果使用者開啟了 Nagle 演算法,那麼 TCP 協議可能不會立刻傳送寫入的資料,它會等待緩衝區中資料超過最大資料段(MSS)或者上一個資料段被 ACK 時才會傳送緩衝區中的資料。

除了 Nagle 演算法之外,TCP 協議棧中還有另一個用於延遲傳送資料的選項 TCP_CORK,如果我們開啟該選項,那麼當傳送的資料小於 MSS 時,TCP 協議就會延遲 200ms 傳送該資料或者等待緩衝區中的資料超過 MSS。

無論是 TCP_NODELAY 還是 TCP_CORK,它們都會通過延遲傳送資料來提高頻寬的利用率,它們會對應用層協議寫入的資料進行拆分和重組,而這些機制和配置能夠出現的最重要原因是 — TCP 協議是基於位元組流的協議,其本身沒有資料包的概念,不會按照資料包傳送資料。

好了,現在知道了粘包問題了。

上面這段程式碼還有另外一個問題,那就是比如我們傳輸1個G的資料,那麼作業系統會幫我們分成很多個包進行拆分。

比如1g資料分成了100個包了,接收方沒有接收完,就呼叫了讀取。這時候又該怎麼處理呢?我是讀取到了50個包的時候完成了,還是讀取80個包的時候完成了。

這些就是應用協議應該解決的問題了。

我們知道資料鏈路層通過再資料前後增加識別符號來變成幀來識別一段資料的。

而網路層,如果是比較大的包,那麼路由器會進行拆包,但是包裡面標記了序號,同樣標記了每個包的大小,和總包的大小。(ip報文分片)

首先tcp是按順序傳輸的,這樣呢,我們就不用標記序號了,那麼就有兩個選擇了:

  1. 在資料前後標記,這樣便於分割或者組合
  2. 在資料裡面增加訊息的大小,比如說有快取了100個位元組,前面4個位元組表示後面訊息的大小

要真正的寫好這兩個還是比較多code的,後面找個時間補充。而且網上例子挺多的。

可以去搜tcp資料無邊界問題,不一定只搜粘包問題,比如說傳送1G資料,到底什麼時候才算收完,這就不叫粘包了,但是屬於tcp無資料邊界問題。

當然了粘包給出的方案也解決了資料無邊界的問題。

下一節,簡單介紹一下udp吧。很多問題,其實在以前的網路程式設計系列中提及到了,後面依然會整理,可能會反覆整理幾次。

相關文章