動手學習TCP:數據傳輸


前面的文章介紹了TCP狀態變遷,以及TCP狀態變遷圖中的一些特殊狀態。

本文主要看看TCP數據傳輸過程中需要了解的一些重要點:

  • MSS(Maximum Segment Size)
  • Seq號和Ack號的計算
  • TCP半連接

TCP數據傳輸實驗

在開始介紹上面列出的內容之前,先看看實驗程序的運行。

本文的例子代碼是基於"動手學習TCP:客戶端狀態變遷"文章中的例子。

首先,修改了"BuildTcpPacket"這個函數,增加了兩個功能:

  1. 正常情況下TCP首部是20個字節,但是TCP首部支持一些特殊"Options"(MSS就是其中一個);所以,第一個改動就是支持創建帶特殊選項的TCP包
  2. 第二個改動是可以通過參數設置為TCP包增加Payload,這樣就可以通過TCP包傳輸數據了。
public static Packet BuildTcpPacket(EndPointInfo endPointInfo, TcpControlBits tcpControlBits, List<TcpOption> tcpOptionList = null, bool withPayload = false, string payloadData = "")
{
    EthernetLayer ethernetLayer =
        new EthernetLayer
        {
            Source = new MacAddress(endPointInfo.SourceMac),
            Destination = new MacAddress(endPointInfo.DestinationMac),
            EtherType = EthernetType.None, // Will be filled automatically.
        };

    IpV4Layer ipV4Layer =
        new IpV4Layer
        {
            Source = new IpV4Address(endPointInfo.SourceIp),
            CurrentDestination = new IpV4Address(endPointInfo.DestinationIp),
            Fragmentation = IpV4Fragmentation.None,
            HeaderChecksum = null, // Will be filled automatically.
            Identification = 123,
            Options = IpV4Options.None,
            Protocol = null, // Will be filled automatically.
            Ttl = 10,
            TypeOfService = 0,
        };

    TcpLayer tcpLayer =
        new TcpLayer
        {
            SourcePort = endPointInfo.SourcePort,
            DestinationPort = endPointInfo.DestinationPort,
            Checksum = null, // Will be filled automatically.
            SequenceNumber = seqNum,
            AcknowledgmentNumber = ackNum,
            ControlBits = tcpControlBits,
            Window = windowSize,
            UrgentPointer = 0,
            Options = (tcpOptionList == null) ? TcpOptions.None : new TcpOptions(tcpOptionList),
        };

    PacketBuilder builder;

    if (withPayload)
    {
        PayloadLayer payloadLayer = new PayloadLayer
        {
            Data = new Datagram(System.Text.Encoding.ASCII.GetBytes(payloadData)),
        };

        builder = new PacketBuilder(ethernetLayer, ipV4Layer, tcpLayer, payloadLayer);

        return builder.Build(DateTime.Now);
    }

    builder = new PacketBuilder(ethernetLayer, ipV4Layer, tcpLayer);

    return builder.Build(DateTime.Now);
}

代碼其余的改動發生在"PacketHandler"函數中:

private static void PacketHandler(PacketCommunicator communicator, EndPointInfo endPointInfo, bool clientToSendFin = true)

增加了對於"ESTABLISHED"狀態下收到數據包的處理,主要作用就是發送一個[ACK]包對收到的數據包進行確認。

case TcpControlBits.Acknowledgment:
    if (tcpStatus == TCPStatus.FIN_WAIT_1)
    {
        tcpStatus = TCPStatus.FIN_WAIT_2;
        Utils.PacketInfoPrinter(packet, tcpStatus);
    }
    else if (tcpStatus == TCPStatus.LAST_ACK)
    {
        tcpStatus = TCPStatus.CLOSED;
        Utils.PacketInfoPrinter(packet, tcpStatus);

        running = false;
    }
    else if (tcpStatus == TCPStatus.ESTABLISHED)
    {
        //print the data received from server
        Console.WriteLine(packet.Ethernet.IpV4.Tcp.Payload.ToString());
        communicator.SendPacket(Utils.BuildTcpResponsePacket(packet, TcpControlBits.Acknowledgment));

    }
    break;
case (TcpControlBits.Acknowledgment | TcpControlBits.Push):
    if (tcpStatus == TCPStatus.ESTABLISHED)
    {
        //print the data received from server
        Console.WriteLine(packet.Ethernet.IpV4.Tcp.Payload.ToString());
        communicator.SendPacket(Utils.BuildTcpResponsePacket(packet, TcpControlBits.Acknowledgment));

    }
    break;

運行效果

代碼修改好之后,運行代碼。

通過console端可以看到,在連接為"ESTABLISHED"狀態下,客戶端收到的來自服務端的字節數。

通過Wireshark抓包可以看到,在連接建立請求包[SYN]中增加了MSS的設置,並且以后的數據傳出中,TCP數據包的payload長度最大就是MSS的值。

下面就開始介紹上面實驗中涉及的TCP數據傳輸的知識點。

TCP分段和IP分片

在網絡上傳輸的數據包是有大小限制,這里就需要知道TCP分段和IP分片的概念了。

跟這兩個概念緊密相關的就是MSS(Maximum Segment Size)和MTU(Maximum Transmission Unit)這兩個指標了,這兩個指標的值大小直接決定了TCP分段和IP分片。

下面分別看看MSS和MTU。

MTU

首先來看看MTU。

以太網和802.3對數據幀的長度都有一個限制,最大值分別是1500和1492個字節。鏈路層的這個指標稱作MTU(注意MTU是鏈路層的概念),不同類型的網絡大多數都有一個上限。

如果網絡層(IP層)有一個數據報需要傳輸,且數據的長度比鏈路層的 MTU還大,那么網絡層(IP層)就要進行分片(fragmentation),把數據報分成若干片,保證每一個分片都小於MTU;目的端的網絡層(IP層)會對收到的分片進行重新組裝。

也就是說,分片和重新組裝過程發生在網絡層(IP層),所以對運輸層(TCP/UDP)是透明的。

下面看看通過ping命令演示IP分片,ping命令屬於ICMP(Internet Control Messages Protocol)協議:

Wireshark的結果為下,這5000個字節的數據被分別放在了四個IP分片中,每個分片(最后一個分片除外)中的數據長度等於1480(1500 – 20[IP header]):

IP分片的問題:IP分片有一個很大的問題,由於IP層本身沒有超時重傳機制,即使只丟失一片數據也要重新傳整個數據報。也就是說,對於上面截圖中的4個Frame,任何一個丟失了,另外3個都需要進行重傳。

使用UDP和ICMP的時候很容易導致IP分片,因為UDP和ICMP是不考慮MTU和分片的,而是把這些工作都丟給了網絡層(IP層);但是,為了減少IP分片對TCP的影響,在TCP中提出了MSS來試圖避免IP分片。

MSS

MSS就是TCP數據包每次能夠傳輸的最大數據分段。

為了達到最佳的傳輸效能TCP協議在建立連接的時候通常要協商雙方的MSS值,這個值TCP協議在實現的時候往往用MTU值代替(需要減去IP數據包首部的大小20Bytes和TCP數據段的首部20Bytes),所以往往MSS為1460。通訊雙方會根據雙方提供的MSS值得最小值確定為這次連接的最大MSS值。

回到本文開始的例子,在建立TCP連接的時候,客戶端指定了MSS為800,服務端指定的MSS為1460。經過協商后,雙方采用了較小的MSS,所以以后的數據包長度最到為800字節。TCP就是通過這種方式來避免IP分片的。

再看一個MSS的例子,通過Wireshark抓取了一段HTTP請求,通過GET方法請求jquery的一組數據包。

通過下面可以看到,當應用層有一個超過MSS的數據需要發送的時候,TCP會把應用層的數據分成多個TCP分段然后發送出去。每一個分段包都包含TCP首部,然后傳遞給網絡層進一步增加IP首部。

區別

通過上面可以看到TCP分段和IP分片有下面的主要區別:

  1. TCP分段發生在傳輸層,分段的依據是MSS;IP分片發生在網絡層,分片的依據是MTU
  2. TCP分段是在傳輸層完成,並在傳輸層進行重組;IP分片由網絡層完成,也在網絡層進行重組

再看Seq和Ack號

TCP傳輸的可靠性是通過Seq和Ack號來進行保證的,所以可以看出Seq和Ack號的重要性。

文章開始的實驗中,另一個需要注意的地方就是Seq和Ack號的變化。

在前面TCP連接的相關文章中已經介紹了連接建立和終止時候Seq和Ack號的變化,可以總結得到下面公式:

確認包的Ack = 待確認包(特殊標志包)的Seq + 1

從Wireshark的截圖中可以看到在數據傳輸中Seq和Ack號的變化。

對於數據包的確認,可以使用下面的方式進行計算:

確認包的Ack = 待確認數據包的Seq + 待確認數據包的數據長度(Len)

關於TCP半連接

在介紹TCP終止連接的時候,提到了由於TCP是全雙工的,所以需要經過四次揮手才能關閉TCP連接。

TCP中有一個半連接的概念,就是TCP連接的一端在結束它的發送后,還能接收來自另一端數據。

還是回到文章開始的例子,服務端發出了終止TCP連接的請求[FIN, ACK],客戶端進行了確認,到此服務端到客戶端方向的TCP連接就關閉了。

但是,隨后客戶端向服務端發送了一段長度為480字節的數據,然后才關閉客戶端到服務端方向的TCP連接。

總結

本文主要介紹了TCP數據傳輸中的幾個重要的概念。

  • MSS(Maximum Segment Size)
  • Seq號和Ack號
  • TCP半連接

通過這篇文章,一定能很好的認識TCP分段和IP分片的區別,以及MSS和MTU的關系。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM