TCP傳輸過程詳解

1 概述

TCP(Transmission Control Protocol)傳輸控制協(xié)議是一種面向連接的、可靠的、基于字節(jié)流的傳輸層協(xié)議
TCP是面向連接的通信協(xié)議,通過三次握手建立連接,通訊完成時(shí)要拆除連接,由于TCP是面向連接的所以只能用于端到端的通訊。
TCP提供的是一種可靠的數(shù)據(jù)流服務(wù),采用“帶重傳的肯定確認(rèn)”技術(shù)來實(shí)現(xiàn)傳輸?shù)目煽啃?。TCP還采用一種稱為“滑動(dòng)窗口”的方式進(jìn)行流量控制,所謂窗口實(shí)際表示接收能力,用以限制發(fā)送方的發(fā)送速度。
如果IP數(shù)據(jù)包中有已經(jīng)封好的TCP數(shù)據(jù)包,那么IP將把它們向‘上’傳送到TCP層。TCP將包排序并進(jìn)行錯(cuò)誤檢查,同時(shí)實(shí)現(xiàn)虛電路間的連接。TCP數(shù)據(jù)包中包括序號(hào)和確認(rèn),所以未按照順序收到的包可以被排序,而損壞的包可以被重傳。
本文將通過實(shí)驗(yàn)的方式介紹三次握手和數(shù)據(jù)傳輸?shù)倪^程。

2 測(cè)試代碼

為了能夠抓包,這里選擇java在電腦上運(yùn)行。開發(fā)環(huán)境是eclipse。

2.1 服務(wù)端代碼

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer extends Thread {

    public void run() {
            System.out.println("TcpServer start");
            ServerSocket server=null;
            try{
                //創(chuàng)建一個(gè)ServerSocket在端口55672監(jiān)聽客戶請(qǐng)求
                server=new ServerSocket(55672);

                }catch(Exception e) {
                    System.out.println("TcpServer can not listen to:"+ e);
                    e.printStackTrace();

                }

                    Socket socket=null;

                    try{
                        //使用accept()阻塞等待客戶請(qǐng)求,有客戶
                        //請(qǐng)求到來則產(chǎn)生一個(gè)Socket對(duì)象,并繼續(xù)執(zhí)行
                        socket=server.accept();
                        String hostip =  socket.getInetAddress().getHostAddress();
                        System.out.println("TcpServer host Ip . "+ hostip);

                    }catch(Exception e) {
                        System.out.println("TcpServer Error."+e);
                    }
                    
                String line;
                try{
                BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
                PrintWriter os=new PrintWriter(socket.getOutputStream());
                BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
                
                System.out.println("TcpServer Client is read 111: "+is.readLine());
                line=sin.readLine();
                while(!line.equals("bye")){
                    os.println(line);
                    os.flush();
                    System.out.println("TcpServer Server:"+line);
                    System.out.println("TcpServer Client is read 22 : "+is.readLine());
                    line=sin.readLine();
                } 
                os.close(); //關(guān)閉Socket輸出流
                is.close(); //關(guān)閉Socket輸入流
                socket.close(); //關(guān)閉Socket
                server.close(); //關(guān)閉ServerSocket
                
                System.out.println("TcpServer end ");
                } catch(Exception e) {
                    System.out.println("TcpServer Error:" + e);
                }

                System.out.println("TcpServer end");
    }
}

2.2 客戶端代碼

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Client {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        sendMsg("Msg send by client");
    }

    private static String sendMsg(String msg) {
        Socket socket = null;
        PrintWriter os = null;
        BufferedReader is = null;

        String retMsg = null;
        try {
            //向本機(jī)的55672端口發(fā)出客戶請(qǐng)求
            socket = new Socket("192.168.1.49", 55672);

            //由系統(tǒng)標(biāo)準(zhǔn)輸入設(shè)備構(gòu)造BufferedReader對(duì)象
            os = new PrintWriter(socket.getOutputStream());

            //由Socket對(duì)象得到輸出流,并構(gòu)造PrintWriter對(duì)象
            is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            os.println(msg);

            //將從系統(tǒng)標(biāo)準(zhǔn)輸入讀入的字符串輸出到Server
            os.flush();

            retMsg = is.readLine();
            System.out.println("Server retMsg:" + retMsg);

            os.close(); //關(guān)閉Socket輸出流
            is.close(); //關(guān)閉Socket輸入流
            socket.close(); //關(guān)閉Socket

            System.out.println("clinet sendMsg end ");

        } catch (Exception e) {
            System.out.println("MainClass new  1111 Exception end " + e);
            e.printStackTrace();
        }

        return retMsg;
    }
}

3 抓包分析

3.1抓包工具

抓包工具是使用的wireshark,安裝方式可以參考:
https://jingyan.baidu.com/article/bad08e1e87d68209c9512153.html
也可以針對(duì)于自己電腦型號(hào)系統(tǒng)安裝。

3.2 操作步驟

1、啟動(dòng)wireshark;
2、選擇一個(gè)網(wǎng)絡(luò)接口


圖3-1

3、設(shè)置過濾規(guī)則tcp.port == 55672(我的測(cè)試代碼端口設(shè)置的55672),點(diǎn)擊橫箭頭執(zhí)行


圖3-2

4、 運(yùn)行服務(wù)端代碼;
5、 運(yùn)行客戶端代碼;
6、 下圖就是客戶端TCP連接服務(wù)端,客戶端向服務(wù)端發(fā)送一條消息,服務(wù)端回復(fù)一條消息的截圖。


圖3-3

3.3 TCP三次握手

第一次握手:建立連接時(shí),客戶端發(fā)送syn包(syn=j)到服務(wù)器,并進(jìn)入SYN_SENT狀態(tài),等待服務(wù)器確認(rèn);SYN:同步序列編號(hào)(Synchronize Sequence Numbers)。

第二次握手:服務(wù)器收到syn包,必須確認(rèn)客戶的SYN(ack=j+1),同時(shí)自己也發(fā)送一個(gè)SYN包(syn=k),即SYN+ACK包,此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài);

第三次握手:客戶端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認(rèn)包ACK(ack=k+1),此包發(fā)送完畢,客戶端和服務(wù)器進(jìn)入ESTABLISHED(TCP連接成功)狀態(tài),完成三次握手
完成三次握手,客戶端和服務(wù)端開始傳送數(shù)據(jù)

抓包結(jié)果如下圖:
圖3-4

3.4 TCP可靠性傳輸

TCP通過下列方式來提供可靠性:
1、應(yīng)用數(shù)據(jù)被分割成TCP認(rèn)為最適合發(fā)送的數(shù)據(jù)塊。這和UDP完全不同,應(yīng)用程序產(chǎn)生的數(shù)據(jù)報(bào)長(zhǎng)度將保持不變。 (將數(shù)據(jù)截?cái)酁楹侠淼拈L(zhǎng)度)
2、當(dāng)TCP發(fā)出一個(gè)段后,它啟動(dòng)一個(gè)定時(shí)器,等待目的端確認(rèn)收到這個(gè)報(bào)文段。如果不能及時(shí)收到一個(gè)確認(rèn),將重發(fā)這個(gè)報(bào)文段。 (超時(shí)重發(fā))
3、當(dāng)TCP收到發(fā)自TCP連接另一端的數(shù)據(jù),它將發(fā)送一個(gè)確認(rèn)。這個(gè)確認(rèn)不是立即發(fā)送,通常將推遲幾分之一秒 。 (對(duì)于收到的請(qǐng)求,給出確認(rèn)響應(yīng)) (之所以推遲,可能是要對(duì)包做完整校驗(yàn))


圖3-5

4、 TCP將保持它首部和數(shù)據(jù)的檢驗(yàn)和。這是一個(gè)端到端的檢驗(yàn)和,目的是檢測(cè)數(shù)據(jù)在傳輸過程中的任何變化。如果收到段的檢驗(yàn)和有差錯(cuò),TCP將丟棄這個(gè)報(bào)文段和不確認(rèn)收到此報(bào)文段。 (校驗(yàn)出包有錯(cuò),丟棄報(bào)文段,不給出響應(yīng),TCP發(fā)送數(shù)據(jù)端,超時(shí)時(shí)會(huì)重發(fā)數(shù)據(jù))
5、既然TCP報(bào)文段作為IP數(shù)據(jù)報(bào)來傳輸,而IP數(shù)據(jù)報(bào)的到達(dá)可能會(huì)失序,因此TCP報(bào)文段的到達(dá)也可能會(huì)失序。如果必要,TCP將對(duì)收到的數(shù)據(jù)進(jìn)行重新排序,將收到的數(shù)據(jù)以正確的順序交給應(yīng)用層。 (對(duì)失序數(shù)據(jù)進(jìn)行重新排序,然后才交給應(yīng)用層)
6、既然IP數(shù)據(jù)報(bào)會(huì)發(fā)生重復(fù),TCP的接收端必須丟棄重復(fù)的數(shù)據(jù)。(對(duì)于重復(fù)數(shù)據(jù),能夠丟棄重復(fù)數(shù)據(jù))
7、TCP還能提供流量控制。TCP連接的每一方都有固定大小的緩沖空間。TCP的接收端只允許另一端發(fā)送接收端緩沖區(qū)所能接納的數(shù)據(jù)。這將防止較快主機(jī)致使較慢主機(jī)的緩沖區(qū)溢出。(TCP可以進(jìn)行流量控制,防止較快主機(jī)致使較慢主機(jī)的緩沖區(qū)溢出)TCP使用的流量控制協(xié)議是可變大小的滑動(dòng)窗口協(xié)議。

3.5 TCP四次揮手結(jié)束連接釋放

1、客戶端A發(fā)送一個(gè)FIN,用來關(guān)閉客戶A到服務(wù)器B的數(shù)據(jù)傳送
2、服務(wù)器B收到這個(gè)FIN,它發(fā)回一個(gè)ACK,確認(rèn)序號(hào)為收到的序號(hào)加1。和SYN一樣,一個(gè)FIN將占用一個(gè)序號(hào)。
3、服務(wù)器B關(guān)閉與客戶端A的連接,發(fā)送一個(gè)FIN給客戶端A
4、客戶端A發(fā)回ACK報(bào)文確認(rèn),并將確認(rèn)序號(hào)設(shè)置為收到序號(hào)加1


圖3-6

4 報(bào)文分析

4.1 數(shù)據(jù)報(bào)層次分解

應(yīng)用層由用戶進(jìn)程提供(后面將介紹如何使用socket API編寫應(yīng)用程序),應(yīng)用程序?qū)νㄓ崝?shù)據(jù)的含義進(jìn)行解釋,而傳輸層及其以下處理通訊的細(xì)節(jié),將數(shù)據(jù)從一臺(tái)計(jì)算機(jī)通過一定的路徑發(fā)送到另一臺(tái)計(jì)算機(jī)。應(yīng)用層數(shù)據(jù)通過協(xié)議棧發(fā)到網(wǎng)絡(luò)上時(shí),每層協(xié)議都要加上一個(gè)數(shù)據(jù)首部(header),稱為封裝(Encapsulation)。封裝過程如圖4-1


圖4-1

這里選取第4段報(bào)文(客戶端向服務(wù)端發(fā)送數(shù)據(jù))為例分析:


圖4-2

其中綠色部分是以太網(wǎng)首部,紅色部分是IP首部,紫色部分是TCP報(bào)頭,剩下部分是數(shù)據(jù)“hello I am from APP”。其中紅綠色部分文本不做詳解。

4.2 TCP報(bào)文解析

圖4-3是TCP數(shù)據(jù)報(bào)的格式:


圖4-3

圖4-4是wireshark的TCP字段解析


圖4-4

源端口號(hào)(2字節(jié)):
94 71(38001)

目的端口號(hào)(2字節(jié)):
d9 78(55672)

TCP報(bào)頭中的源端口號(hào)和目的端口號(hào)同IP數(shù)據(jù)報(bào)中的源IP與目的IP唯一確定一條TCP連接

序號(hào)(4字節(jié)):
d8 13 c2 18
用來標(biāo)識(shí)TCP發(fā)端向TCP收端發(fā)送的數(shù)據(jù)字節(jié)流

確認(rèn)序號(hào)(4字節(jié)):
83 53 61 6b

首部長(zhǎng)度(4位):報(bào)文頭長(zhǎng)度(單位:位)/32
0101(轉(zhuǎn)化為10進(jìn)制為5,532/8 =20,該報(bào)文報(bào)頭長(zhǎng)度為20個(gè)字節(jié))
存在該字段是因?yàn)門CP報(bào)頭中任選字段長(zhǎng)度可變
報(bào)頭不包含任何任選字段則長(zhǎng)度為20字節(jié);4位所能表示的最大值為1111,轉(zhuǎn)化為10進(jìn)制為15,15
32/8 = 60,故報(bào)頭最大長(zhǎng)度為60字節(jié)

標(biāo)志位(12位):
0x018 轉(zhuǎn)化成2進(jìn)制 0000 0001 1000
Reserved:
0000 00~~ ~~~~

Control Bits:

~~~~ ~~0~ ~~~~ = U / Urgent:緊急指針有效性標(biāo)志

~~~~ ~~~1 ~~~~ = A / Acknowledgment:確認(rèn)序號(hào)有效性標(biāo)志,一旦一個(gè)連接建立起來,該標(biāo)志總被置為1,即除了請(qǐng)求建立連接報(bào)文(僅設(shè)置Syn標(biāo)志位為1),其它所有報(bào)文的該標(biāo)志總為1

~~~~ ~~~~ 1~~~ = P / Push:Push標(biāo)志(接收方應(yīng)盡快將報(bào)文段提交至應(yīng)用層)

~~~~ ~~~~ ~0~~ = R / Reset:重置連接標(biāo)志

~~~~ ~~~~ ~~0~ = S / Syn:同步序號(hào)標(biāo)志

~~~~ ~~~~ ~~~0 = F / Fin:傳輸數(shù)據(jù)結(jié)束標(biāo)志

窗口大小(2字節(jié)):TCP流量控制通過連接的每一端聲明窗口大小進(jìn)行控制(接收緩沖區(qū)大?。?br> 01 57= 343
由于2字節(jié)能夠表示的最大正整數(shù)為65535,故窗口最大值為65535

檢驗(yàn)和(2字節(jié)):檢驗(yàn)和覆蓋整個(gè)TCP報(bào)文段;強(qiáng)制字段,由發(fā)送端計(jì)算存儲(chǔ),由接收端進(jìn)行驗(yàn)證
d5 fc

緊急指針(2字節(jié)):當(dāng)Urgent標(biāo)志置1時(shí),緊急指針才有效
00 00

5 引用

TCP三次握手百度百科
https://baike.baidu.com/item/%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B/5111559

TCP的三次握手與四次揮手(詳解+動(dòng)圖)
https://blog.csdn.net/qzcsu/article/details/72861891

TCP/IP協(xié)議
https://baike.baidu.com/item/TCP%2FIP%E5%8D%8F%E8%AE%AE
http://www.networksorcery.com/enp/protocol/tcp.htm

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。