TCP通信流程解析

原文地址:點擊打開原文

B/S通信簡述

整個計算機網絡的實現體現爲協議的實現,TCP/IP協議是Internet的核心協議,HTTP協議是比TCP更高層次的應用層協議。

HTTP(HyperText Transfer Protocol,超文本傳輸協議)是互聯網上應用最爲廣泛的一種網絡協議。所有的WWW文件都必須遵守這個標準。設計HTTP的初衷是爲了提供一種發佈和接收HTML頁面的方法。

瀏覽器(Web Browser)負責與服務器建立連接下載網頁(包括資源文件及JS腳本文件)到本地,並最終渲染出頁面。JS腳本文件運行在客戶端,負責客戶端一些行爲響應或預處理,例如提交表單前的數據校驗、鼠標事件處理等交互。由此可見,瀏覽器(Browser)一方面充當了C/S通信架構C角色,另一方面它是HTML/JavaScript解析渲染引擎(Analyze Render Engine)。

當在瀏覽器地址欄敲入“http://www.baidu.com/”並按下回車鍵時,瀏覽器中將呈現出百度搜索引擎首頁。這樣一種情景我們再熟悉不過,本文通過wireshark抓取這一過程的數據包,結合TCP協議分析HTTP通信的基本流程。

MTU和MSS

本文用到的抓包工具爲wireshark,它的前身是赫赫有名的Ethereal。wireshark以太網幀的封包格式爲:

----------------------------------------------------------------------------------------------------

Frame=Ethernet Header +IP Header +TCP Header +TCP Segment Data

----------------------------------------------------------------------------------------------------

(1)Ethernet Header =14 Byte =Dst Physical Address(6 Byte)+ Src Physical Address(6 Byte)+Type(2 Byte),以太網幀頭以下稱之爲數據幀。

(2)IP Header =20 Byte(without options field),數據在IP層稱爲Datagram,分片稱爲Fragment

(3)TCP Header = 20 Byte(without options field),數據在TCP層稱爲Stream,分段稱爲Segment(UDP中稱爲Message)。

(4)54個字節後爲TCP數據負載部分(Data Portion),即應用層用戶數據。

Ethernet Header以下的IP數據報最大傳輸單位爲MTU(Maximum Transmission Unit,Effect of short board),對於大多數使用以太網的局域網來說,MTU=1500。

TCP數據包每次能夠傳輸的最大數據分段爲MSS,爲了達到最佳的傳輸效能,在建立TCP連接時雙方將協商MSS值——雙方提供的MSS值中的最小值爲這次連接的最大MSS值。MSS往往基於MTU計算出來,通常MSS=MTU-sizeof(IP Header)-sizeof(TCP Header)=1500-20-20=1460。

這樣,數據經過本地TCP層分段後,交給本地IP層,在本地IP層就不需要分片了。但是在下一跳路由(Next Hop)的鄰居路由器上可能發生IP分片!因爲路由器的網卡的MTU可能小於需要轉發的IP數據報的大小。這時候,在路由器上可能發生兩種情況:

(1)如果源發送端設置了這個IP數據包可以分片(May FragmentDF=0),路由器將IP數據報分片後轉發。

(2)如果源發送端設置了這個IP數據報不可以分片(Don’t FragmentDF=1),路由器將IP數據報丟棄,併發送ICMP分片錯誤消息給源發送端。

關於MTU的探測,參考《Path MTU discovery》。我們可以通過基於ICMP協議的ping命令來探測從本機出發到目標機器上路由上的MTU,詳見下文。

TCP和UDP

在基於傳輸層(TCP/UDP)的應用開發中,爲了最後的程序優化,應避免端到端的任何一個節點上出現IP分片。TCP的MSS協商機制加上序列號確認機制,基本上能夠保證數據的可靠傳輸。

UDP協議在IP協議的基礎上,只增加了傳輸層的端口(Source Port+Destination Port)、UDP數據包長(Length = Header+Data)以及檢驗和(Checksum)。因此,基於UDP開發應用程序時,數據包需要結合IP分片情況考慮。對於以太局域網,往往取UDP數據包長Length<=MTU-sizeof(IP Header)=1480,故UDP數據負載量小於或等於1472(Length-UDP Header);對於公網,ipv4最小MTU爲576,UDP數據負載量小於或等於548

“向外”NAT在內網和公網之間提供了一個“不對稱”橋的映射。“向外”NAT在默認情況下只允許向外的session穿越NAT:從外向內的的數據包都會被丟棄掉,除非NAT設備事先已經定義了這些從外向內的數據包是已存在的內網session的一部分。對於一方在LAN,一方在WAN的UDP通信,鑑於UDP通信不事先建立虛擬鏈路,NAT後面的LAN通信方需先發送消息給WAN通信方以洞穿NAT,然後纔可以進行雙向通信,這即是常提到的“UDP打洞(Hole Punching)”問題。

TCP連接百度過程解析

1.wireshark抓包

下文對百度的完整抓包建立在不使用緩存的基礎上。如若主機存有百度站點的cookie和脫機緩存(Offline Cache),則不會再請求地址欄圖標favicon.ico;請求/js/bdsug.js?v=1.0.3.0可能迴應“HTTP/1.1 304 Not Modified”。可在瀏覽器打開百度首頁後,Ctrl+F5強制刷新,不使用緩存,也可參考《瀏覽器清除緩存方法》。

以下爲訪問百度過程,wireshark抓包數據。對於直接通過Ethernet聯網的機器,Wireshark Capture Filter爲"host www.baidu.com";對於通過PPP over Ethernet(PPPoE)聯網的機器,Wireshark Capture Filter爲"pppoes and host www.baidu.com"。以下抓包示例直接通過Ethernet聯網訪問百度的過程。可點擊下面的圖片超鏈接下載pcap文件,使用wireshark軟件打開查看。

爲方便起見,以下將客戶端(瀏覽器)簡稱爲C,將服務器(百度後臺)簡稱爲S。

 

2.TCP三次握手建立連接

“http://”標識WWW訪問協議爲HTTP,根據規則,只有底層協議建立連接之後才能進行更高層協議的連接。在瀏覽器地址欄輸入地址後按下回車鍵的瞬間,C建立與S(機器名爲www.baidu.com,DNS解析出來的IP爲220.181.6.175)的TCP 80連接(HTTP默認使用TCP 80端口)。

以下爲三次握手建立TCP連接的數據包(Packet1-Packet3)。

  /****************************************************************************************************

1  192.168.89.125:5672→220.181.6.175:80   TCP(協議) 62(以太網幀長)

amqp > http [SYN] Seq=0 Win=65535 Len=0 MSS=1460 SACK_PERM=1

2  220.181.6.175:80→192.168.89.125:5672 TCP 62

http > amqp [SYN, ACK] Seq=0 Ack=1 Win=8192 Len=0 MSS=1460 SACK_PERM=1

3  192.168.89.125:5672→220.181.6.175:80   TCP 54

amqp > http [ACK] Seq=1 Ack=1 Win=65535 Len=0

****************************************************************************************************/

三次握手建立TCP連接的流程如下:

    C(Browser)                                    S(www.baidu.com)

 1. CLOSED                                             LISTEN

 2. SYN-SENT    →<SEQ=0><CTL=SYN>              → SYN-RECEIVED

 3. ESTABLISHED← <SEQ=0><ACK=1><CTL=SYN,ACK> ← SYN-RECEIVED

 4. ESTABLISHED→ <SEQ=1><ACK=1><CTL=ACK>      → ESTABLISHED

3-Way Handshake for Connection Synchronization

2.1 三次握手的socket層執行邏輯

S調用socket的listen函數進入監聽狀態;C調用connect函數連接S:[SYN],S調用accept函數接受C的連接併發起與C方向上的連接:[SYN,ACK]。C發送[ACK]完成三次握手,connect函數返回;S收到C發送的[ACK]後,accept函數返回。

2.2 關於Seq和Ack

Seq即Sequence Number,爲源端(source)的發送序列號;Ack即Acknowledgment Number,爲目的端(destination)的接收確認序列號。在Wireshark Display Filter中,可使用tcp.seq或tcp.ack過濾。

在Packet1中,C:5672向S:80發送SYN握手包,Seq=0(relative sequence number);在Packet2中,S:80向C:5672發送ACK握手迴應包,Ack=1(relativesequence number),同時發送SYN握手包,Seq=0(relative sequence number);在Packet3中,C:5672向S:80發送ACK握手迴應包,Seq=1,Ack=1。

至此,Seq=1爲C的ISN(Initial Sequence Number),後期某一時刻的Seq=ISN+累計發送量(cumulative sent);Ack=1爲C的IAN(Initial Acknowledge Number),後期某一時刻的Ack=IAN+累計接收量(cumulative received)。對於S而言,Seq和Ack情同此理。

參考:《TCP Analyze Sequence Numbers》、《Understanding TCP Sequence and Acknowledgement Numbers

3.TCP獲取網站數據流程

連接建立後,下一步發送(“GET / HTTP/1.1”)請求(Request)HTML頁面,這裏“/”表示S的默認首頁,“GET”爲HTTP Request Method;“/”爲Request-URI,這裏爲相對地址;HTTP/1.1表示使用的HTTP協議版本號爲1.1。

以下爲HTTP GET請求數據包(Packet4)。

/****************************************************************************************************

4  192.168.89.125:5672220.181.6.175:80 HTTP 417

GET / HTTP/1.1

****************************************************************************************************/

HTTP GET報文長=417-54=363個字節,其中Next sequence number: 364(relative sequence number)表示,若在規定的時間內收到S響應Ack=364,表明該報文發送成功,可以發送下一個報文(Seq=364);否則重傳(TCP Retransmitssion)。序列號確認機制是TCP可靠性傳輸的保障。

S(http)收到HTTP GET報文(共363個字節),向C(amqp)發送TCP確認報文(Packet5)。

/****************************************************************************************************

5  220.181.6.175:80→ 192.168.89.125:5672 TCP 60

http > amqp [ACK] Seq=1 Ack=364 Win=6432 Len=0

****************************************************************************************************/

這裏Seq=1,爲S的ISN,意爲已發送過SYN。Packet2中,Ack=1爲S的IAN。這裏的Ack-IAN=364-1=363表示S已經從C接收到363個字節,即HTTP GET報文。同時,Ack=364也是S期待C發送的下一個TCP報文序列號(上面分析的Next sequence number)。

接下來,S向C發送Http Response,根據HTTP協議,先發響應頭(Response Header),再發百度首頁HTML文件。

Http Response Header報文(Packet6)如下。

/****************************************************************************************************

6  220.181.6.175:80→ 192.168.89.125:5672 TCP 465

[TCP segment of a reassembled PDU]

****************************************************************************************************/

其部分內容如下:

======================================

HTTP/1.1 200 OK

……

Content-Length: 2139

Content-Type: text/html;charset=gb2312

Content-Encoding: gzip

======================================

S響應C的“GET / HTTP/1.1”請求,先發送帶[PSH]標識的411個字節的Http Response Header(Packet 6)。

TCP頭部[PSH]標識置位,敦促C將緩存的數據推送給應用程序,即先處理Http Response Header,實際上是一種“截流”通知。相應C的socket調用send時在IPPROTO_TCP選項級別設置TCP_NODELAYTRUE禁用Nagle算法可以“保留髮送邊界”,以防粘連。

儘管握手協商的MSS爲1460,但服務器或者代理平衡服務器,每次發送過來的TCP數據最多隻有1420個字節。可以使用ping -f -l size target_name命令向指定目標target_name發送指定字節量的ICMP報文,其中-l size指定發送緩衝區的大小;-f則表示在IP數據報中設置不分片DF(Don’t Fragment),這樣便可探測出到目標路徑上的MTU。

執行“ping -f -l 1452 www.baidu.com”的結果如下:

220.181.6.18的 Ping統計信息:

   數據包:已發送 = 4,已接收 = 4,丟失 = 0 (0%丟失)

執行“ping -f -l 1453 www.baidu.com”的結果如下:

需要拆分數據包但是設置 DF。

220.181.6.18的 Ping統計信息:

   數據包:已發送 = 4,已接收 = 0,丟失 = 4 (100%丟失)

從以上ping結果可知,在不分片時,從本機出發到百度的路由上能通過的最大數據量爲1452,由此推算出MTU{local,baidu}=sizeof(IP Header)+ sizeof(ICMP Header)+sizeof(ICMP Data Portion)=20+8+1452=1480。

S調用socket的send函數發送2139個字節的Http Response Content(Packet 7、Packet 9),在TCP層將分解爲兩段(segment)後再發出去。

/****************************************************************************************************

7  220.181.6.175:80→ 192.168.89.125:5672 TCP 1474

[TCP segment of a reassembled PDU]

----------------------------------------------------------------------------------------------------

由“Content-Length: 2139”可知,HTML文件還有2139-(1474-54)=719個字節。但此時,C已經發送了確認報文(Packet8)。

/****************************************************************************************************

8  192.168.89.125:5672→  220.181.6.175:80 TCP 54

amqp > http [ACK] Seq=364 Ack=1832 Win=65535 Len=0

****************************************************************************************************/

Seq-ISN=364-1=363,表示C已經發出了363個字節,上邊已經收到了S的確認。Ack-IAN=1832-1=(465-54)+(1474-54),表示C至此已經接收到S發來的1831個字節。

接下來,C收到HTML文件剩餘的719個字節,報文(Packet9)如下。

/****************************************************************************************************

9  220.181.6.175:80→ 192.168.89.125:5672 HTTP  773

HTTP/1.1 200 OK

****************************************************************************************************/

至此,C收到S發送過來的全部HTTP響應報文,即百度首頁HTML內容(text/html)。

Packet6、Packet7和Packet9的ACK都是364,這是因爲這三個segment都是針對Packet4的TCP響應。S將百度首頁HTML文件(一個完整的HTTP報文)按照MSS分段提交給TCP層。在Wireshark中可以看到Packet9的報文中有以下reassemble信息:

[Reassembled TCP segments (2555 bytes): #6(411),#7(1420),#9(719)]

[Frame: 6, payload: 0-410(411 bytes)]

[Frame: 7, payload: 411-1830(1420 bytes)]

[Frame: 9, payload: 1831-2549(719 bytes)]

C(amqp)接收到百度首頁的HTML文件後,開始解析渲染。在解析過程中,發現頁面中含有百度的logo資源baidu_logo.gif,並且需要bdsug.js腳本

<img src="http://www.baidu.com/img/baidu_logo.gif" width="270" height="129" usemap="#mp">

{d.write('<script src=http://www.baidu.com/js/bdsug.js?v=1.0.3.0><//script>')}

於是上面那個連接(C:5672)繼續向S請求logo圖標資源,報文(Packet10)如下。

/****************************************************************************************************

10 192.168.89.125:5672→  220.181.6.175:80 HTTP 492

GET /img/baidu_logo.gif HTTP/1.1

****************************************************************************************************/

與此同時,C(jms)新建一個連接(TCP 5673)向S請求js腳本文件。報文(Packet11)如下。

/****************************************************************************************************

11 192.168.89.125:5673→  220.181.6.175:80 TCP 62

jms > http [SYN] Seq=0 Win=65535 Len=0 MSS=1460 SACK_PERM=1

****************************************************************************************************/

(Packet12)Packet13、Packet14、Packet16和Packet17爲對Packet10的TCP響應(它們的Ack=802),在邏輯上它們是一個完整的TCP報文。其Http Response Content爲圖片文件baidu_logo.gif。我們在Wireshark中可以看到Packet17的報文中有以下reassemble信息:

[Reassembled TCP segments (1801 bytes): #13(312),#14(1420),#16(28) ,#17(41)]

[Frame: 13, payload: 0-311(312 bytes)]

[Frame: 14, payload: 312-1731(1420 bytes)]

[Frame: 16, payload: 1732-1759(28 bytes)]

[Frame: 17, payload: 1760-1800(41 bytes)]

Packet11-Packet19-Packet20完成新連接的三次握手。然後,C(jms)發送“GET /js/bdsug.js?v=1.0.3.0 HTTP/1.1”報文(Packet21),以獲取bdsug.js腳本文件。

/****************************************************************************************************

21 192.168.89.125:5673→  220.181.6.175:80 HTTP 465

GET /js/bdsug.js?v=1.0.3.0 HTTP/1.1

****************************************************************************************************/

(Packet22)Packet23、Packet24、Packet26和Packet27爲對Packet21的TCP響應(它們的Ack=412),在邏輯上它們是一個完整的TCP報文。其Http Response Content爲腳本文件bdsug.js。我們在Wireshark中可以看到Packet27的報文中有以下reassemble信息:

[Reassembled TCP segments (3897 bytes): #23(310),#24(1420),#26(1420) ,#27(747)]

[Frame: 23, payload: 0-309(310 bytes)]

[Frame: 24, payload: 310-1729(1420 bytes)]

[Frame: 26, payload: 1730-3149(1420 bytes)]

[Frame: 27, payload: 3150-3896(747 bytes)]

通常,瀏覽器會自動的搜索網站的根目錄,只要它發現了favicon.ico這個文件,就把它下載下來作爲網站地址欄圖標。於是,C(amqp)還將發起“GET /favicon.ico HTTP/1.1”請求網站地址欄圖標,見報文Packet29。

4.TCP四次揮手關閉連接

經Packet28確認收到了完整的japplication/javascript文件後,鏈路1(本地端口5673)使命結束,S關閉該鏈路,進入四次揮手關閉雙向連接。

(Packet30)Packet31和Packet32爲對Packet29的TCP響應(它們的Ack=1201)。經Packet33確認收到了完整的image/x-icon文件後,鏈路2(本地端口5672)使命結束,S關閉該鏈路,進入四次揮手關閉雙向連接。

   爲什麼握手是三次,而揮手是四次呢?這是因爲握手時,服務器往往在答應建立連接時,也建立與客戶端的連接,即所謂的雙向連接。所以,在Packet2中,服務器將ACK和SYN打包發出。揮手,即關閉連接,往往只是表明揮手方不再發送數據(無數據可發),而接收通道依然有效(依然可以接受數據)。當對方也揮手時,則表明對方也無數據可發了,此時雙向連接真正關閉。



TCP(Transmission Control Protocol,傳輸控制協議)是面向連接的協議,也就是說,在收發數據前,必須和對方建立可靠的連接。一個TCP連接必須要經過三次“對話”才能建立起來,其中的過程非常複雜,只簡單的描述下這三次對話的簡單過程:主機A向主機B發出連接請求數據包:“我想給你發數據,可以嗎?”,這是第一次對話;主機B向主機A發送同意連接和要求同步(同步就是兩臺主機一個在發送,一個在接收,協調工作)的數據包:“可以,你什麼時候發?”,這是第二次對話;主機A再發出一個數據包確認主機B的要求同步:“我現在就發,你接着吧!”,這是第三次對話。三次“對話”的目的是使數據包的發送和接收同步,經過三次“對話”之後,主機A才向主機B正式發送數據。

詳細點說就是:

TCP三次握手過程
1 主機A通過向主機B 發送一個含有同步序列號的標誌位的數據段給主機B ,向主機B 請求建立連接,通過這個數據段,
主機A告訴主機B 兩件事:我想要和你通信;你可以用哪個序列號作爲起始數據段來回應我.
2 主機B 收到主機A的請求後,用一個帶有確認應答(ACK)和同步序列號(SYN)標誌位的數據段響應主機A,也告訴主機A兩件事:
我已經收到你的請求了,你可以傳輸數據了;你要用哪佧序列號作爲起始數據段來回應我
3 主機A收到這個數據段後,再發送一個確認應答,確認已收到主機B 的數據段:"我已收到回覆,我現在要開始傳輸實際數據了

這樣3次握手就完成了,主機A和主機B 就可以傳輸數據了.
3次握手的特點
沒有應用層的數據
SYN這個標誌位只有在TCP建產連接時纔會被置1
握手完成後SYN標誌位被置0


TCP建立連接要進行3次握手,而斷開連接要進行4次

 

1 當主機A完成數據傳輸後,將控制位FIN置1,提出停止TCP連接的請求
2  主機B收到FIN後對其作出響應,確認這一方向上的TCP連接將關閉,將ACK置1
3 由B 端再提出反方向的關閉請求,將FIN置1
4 主機A對主機B的請求進行確認,將ACK置1,雙方向的關閉結束.
由TCP的三次握手和四次斷開可以看出,TCP使用面向連接的通信方式,大大提高了數據通信的可靠性,使發送數據端
和接收端在數據正式傳輸前就有了交互,爲數據正式傳輸打下了可靠的基礎
名詞解釋
ACK  TCP報頭的控制位之一,對數據進行確認.確認由目的端發出,用它來告訴發送端這個序列號之前的數據段
都收到了.比如,確認號爲X,則表示前X-1個數據段都收到了,只有當ACK=1時,確認號纔有效,當ACK=0時,確認號無效,這時會要求重傳數據,保證數據的完整性.
SYN  同步序列號,TCP建立連接時將這個位置1
FIN  發送端完成發送任務位,當TCP完成數據傳輸需要斷開時,提出斷開連接的一方將這位置1

TCP的包頭結構:
源端口 16位
目標端口 16位
序列號 32位
迴應序號 32位
TCP頭長度 4位
reserved 6位
控制代碼 6位
窗口大小 16位
偏移量 16位
校驗和 16位
選項  32位(可選) 
這樣我們得出了TCP包頭的最小長度,爲20字節。

UDP(User Data Protocol,用戶數據報協議)

(1) UDP是一個非連接的協議,傳輸數據之前源端和終端不建立連接,當它想傳送時就簡單地去抓取來自應用程序的數據,並儘可能快地把它扔到網絡上。在發送端,UDP傳送數據的速度僅僅是受應用程序生成數據的速度、計算機的能力和傳輸帶寬的限制;在接收端,UDP把每個消息段放在隊列中,應用程序每次從隊列中讀一個消息段。

(2) 由於傳輸數據不建立連接,因此也就不需要維護連接狀態,包括收發狀態等,因此一臺服務機可同時向多個客戶機傳輸相同的消息。

(3) UDP信息包的標題很短,只有8個字節,相對於TCP的20個字節信息包的額外開銷很小。

(4) 吞吐量不受擁擠控制算法的調節,只受應用軟件生成數據的速率、傳輸帶寬、源端和終端主機性能的限制。

(5)UDP使用盡最大努力交付,即不保證可靠交付,因此主機不需要維持複雜的鏈接狀態表(這裏面有許多參數)。

(6)UDP是面向報文的。發送方的UDP對應用程序交下來的報文,在添加首部後就向下交付給IP層。既不拆分,也不合並,而是保留這些報文的邊界,因此,應用程序需要選擇合適的報文大小。

我們經常使用“ping”命令來測試兩臺主機之間TCP/IP通信是否正常,其實“ping”命令的原理就是向對方主機發送UDP數據包,然後對方主機確認收到數據包,如果數據包是否到達的消息及時反饋回來,那麼網絡就是通的。

UDP的包頭結構:
源端口 16位
目的端口 16位
長度 16位
校驗和 16位

 

小結TCP與UDP的區別:

1.基於連接與無連接; 
2.對系統資源的要求(TCP較多,UDP少); 
3.UDP程序結構較簡單; 
4.流模式與數據報模式 ;

5.TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證。


TCP協議和UDP協議特性區別總結:
     1. TCP協議在傳送數據段的時候要給段標號;UDP協議不
     2. TCP協議可靠;UDP協議不可靠
     3. TCP協議是面向連接;UDP協議採用無連接
     4. TCP協議負載較高,採用虛電路;UDP採用無連接
     5. TCP協議的發送方要確認接收方是否收到數據段(3次握手協議)
     6. TCP協議採用窗口技術和流控制



發佈了64 篇原創文章 · 獲贊 10 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章