TCP协议-TCP连接管理
一、TCP概述
TCP協議是 TCP/IP 協議族中一個非常重要的協議。它是一種面向連接、提供可靠服務、面向字節流的傳輸層通信協議。
TCP(Transmission Control Protocol,傳輸控制協議)。
1.1 TCP協議的特點
(1)TCP 是面向連接的傳輸層協議。這就是說,通信雙方在使用TCP協議進行通信之前,必須先建立TCP連接。在通信結束后,必須釋放已經建立的TCP連接。這就好比打電話,通話前要先撥號建立連接,通話結束后要掛機釋放連接。
(2)TCP 是點對點(一對一)的連接。每一條TCP連接只能有兩個通信端點(endpoint)。所以基于廣播和多播(通信目標是多個主機地址)的應用程序不能使用TCP連接。
(3)TCP 提供可靠交付的通信服務。通過TCP連接傳遞的數據,無差錯、不丟失、不重復,并且按序到達。
(4)TCP 提供全雙工通信。TCP允許通信雙方在任何時候都能發送和接收數據。TCP連接的兩端都設有發送緩存和接收緩存,用來臨時存放雙向通信的數據。在發送時,應用程序在把數據發送給TCP的發送緩存后,就可以做自己的事,而TCP在合適的時候把數據通過網卡發送出去。在接收時,TCP把收到的數據先放入接收緩存,應用層的應用進程在合適的時候再讀取緩存中的數據。
(5)TCP 是面向字節流的。TCP中的“流”(stream)指的是流入到進程或從進程流出的字節序列。“面向字節流”的含義是:雖然應用程序和TCP的交互是一次一個數據塊(大小不等),但TCP把上層應用程序交下來的數據僅僅看成是一連串無結構的字節序列(就像水流一樣)。TCP并不知道所傳送的字節流的含義。TCP不保證接收方應用程序所收到的數據塊和發送方應用程序所發出的數據塊具有對應大小的關系(例如,發送方應用程序交給發送方的TCP共10個數據塊,但接收方的TCP可能只用了4個數據塊就把收到的字節流交付上層的應用程序)。但是,接收方應用程序收到的字節流必須和發送方應用程序發出的字節流完全相同。因此,接收方的應用程序必須有能力識別接收到的字節流,把它還原成有意義的應用層數據。下圖的示意圖解釋了TCP面向字節流的含義:
TCP面向字節流的概念TCP和UDP在發送報文時所采用的方式完全不同。TCP并不關心上層的應用程序一次把多長的報文發送到TCP的緩存中,而是根據對方給出的窗口值和當前網絡擁塞的程度來決定一個報文段應該包含多少個字節(UDP發送的報文段是上層的應用程序給出的)。如果應用程序傳送到TCP緩存的數據塊太大,TCP就可以把它劃分短一些再傳送。如果應用進程一次只發來一個字節,TCP也可以等待積累有足夠多的字節后再構成報文段發送出去。關于TCP報文段的長度問題,會在下面內容中進行討論。
1.2 TCP的連接概念
每一條TCP連接都有兩個端點。TCP連接的端點叫做套接字(socket)。根據RFC 793的定義:端口號拼接到(concatenated with)IP地址即構成了套接字。因此,套接字的表示方法為:
套接字 socket = (IP地址:端口號)
例如,若IP地址是 192.168.1.112,而端口號是 80,那么得到的套接字就是(192.168.1.112: 80)。
每一條TCP連接唯一地被通信兩端的兩個端點(即兩個套接字)所確定。即:
TCP連接 ::= {socket1, socket2} = {(IP1: port1), (IP2: port2)}
?IP1 和 IP2 分別是兩個端點主機的IP地址,而port1 和 port2 分別是兩個端點主機中的端口號。TCP連接的兩個套接字就是socket1 和 socket2。
二、TCP報文段的首部(頭部)結構
TCP雖然是面向字節流的,但是TCP傳送的數據單元卻是報文段。一個TCP報文段分為首部和數據兩部分,而TCP的全部功能都體現在它首部中各字段的作用。因此,只有弄清楚TCP首部各字段的作用才能掌握TCP的工作原理。
這里再講一下“面向字節流”的含義,是指應用層上的應用程序將數據(這些數據可能是有結構層次的)傳遞給傳輸層的TCP時,TCP只把這些數據看做是一連串的字節流,而不會去關心這些數據是什么結構的。
TCP報文段首部的前20個字節是固定的,后面有4n字節是根據需要而增加的選項(n是整數)。因此TCP的首部的最小長度是20字節。
下圖顯示了TCP首部的數據格式。如果不計選項字段,TCP首部的大小通常為20字節。
每個TCP首部都包含源端和目的端的端口號,大小均為16bit,用于尋找發送端和接收端應用進程。這兩個值+IP首部中的源端IP地址和目的端IP地址,就可以唯一確定一條TCP連接。
TCP報文段的首部格式?2.1 TCP首部固定部分各字段的含義
(1)源端口和目的端口:各占2字節,分別寫入源端口號和目的端口號。和UDP的首部類似。
(2)序號:占4字節。序號范圍是 [0, 2^32-1],共2^32(即4 294 967 296)個序號。序號增加到(2^32-1)后,下一個序號就又回到0。也就是說,序號使用 mod 2^32 運算。TCP是面向字節流的,在一個TCP連接中傳送的字節流中的每一個數據字節都按順序編號。整個要傳送的字節流的起始序號必須在TCP連接建立時設置。首部中序號字段值指的是本報文段所發送的數據的第一個字節的序號。
例如,某個TCP報文段的首部序號字段值是301,而這個報文段攜帶的數據共有100字節,那么最后一個字節的序號是400。顯然,下一個報文段(如果還有的話)的數據序號應當從401開始,即下一個報文段的序號字段值是401。這個字段的名稱就叫做“報文段序號”。
(3)確認號:占4字節。是期望收到對方下一個報文段的第一個數據字節的序號值。
例如,B正確收到了A發送過來的一個報文段,其序號字段值是501,而數據長度是200字節(序號501~700)。這表明B正確收到了A發送的到需要700為止的數據。因此,B期望收到A的下一個數據字節序號是701,于是B在發送給A的確認報文段中把確認號設置為701。請注意,現在的確認號不是501,也不是700,而是701。總之,應當記住:
若確認號 = N,則表明:到序號 N-1 為止的所有數據都已正確收到。
由于序號字段有32位長,可對4GB的數據進行編號。在一般情況下,可保證當序號重復使用,舊序號的數據早已通過網絡到達終點了。
(4)數據偏移:占 4 bit。這個字段用于記錄TCP報文段的首部長度。由于TCP報文段的首部結構中還有長度不確定的選項字段,因此數據偏移字段是必要的。當應注意的是,“數據偏移”的單位是32位字(即為4字節的字長為計算單位)。由于4位二進制能夠表示的最大十進制數是15,因此數據偏移的最大值是60字節,這也是TCP報文段首部的最大長度(即選項字段長度不能超過40字節)。一般情況下,TCP首部的長度為20字節。
(5)保留:占6位。保留為今后使用,但目前應置為0。
(6)6個控制位,用來說明本報文段的性質,它們各自的含義如下:
- 緊急URG:(Urgent Pointer)緊急指針字段。當URG=1時,有效。它告訴系統此報文段有緊急數據,應盡快傳送(相對于高級優先的數據),而不要按原來的排隊順序來傳送。當 URG置1時,發送應用進程就告訴發送方的TCP有緊急數據要傳送。于是發送方TCP就把緊急數據插入到本報文段數據部分的最前面,而在緊急數據后面的數據仍是普通數據。這時要與首部中緊急指針(Urgent Pointer)字段配合使用。
例如,已經發送了很長的一個程序要在遠地的主機上運行。但后來發現了一些問題,需要取消該程序的運行。因此用戶從鍵盤發出中斷命令(ctrl + c),如果不使用緊急數據,那么這兩個字符將存儲在接收TCP的緩存末尾。只有在所有的數據都被處理完畢后這兩個字符才被交付給接收方的應用進程,這樣做就浪費了許多時間。
- 確認ACK:(ACKnowledgment) 當 ACK=1時,確認號字段才有效。當ACK=0時,確認號無效。TCP規定,在連接建立后所有傳送的報文段都必須把ACK置1。
- 推送PSH:(Push) 當兩個應用進程進行交互式的通信時,有時在一端的應用進程希望在鍵入一個命令后立即就能夠收到對方的響應。在這種情況下,TCP就可以使用推送(push)操作。這時,發送方TCP把 PSH 置 1,并立即創建一個報文段發送出去。接收方TCP收到 PSH=1 的報文段后,就盡快地交付給接收方的應用進程,而不再等到整個緩存都填滿后再向上交付。
雖然應用程序可以選擇推送操作,但推送操作很少使用。
- 復位RST:(ReSeT) 當RST=1時,表明TCP連接出現嚴重差錯(如由于主機崩潰或其他原因),必須釋放連接,然后再重新建立TCP連接。RST置1,還可用來拒絕一個非法的報文段或拒絕打開一個TCP連接。RST也可稱為重建位或重置位。我們將攜帶RST標志的TCP報文段稱為復位報文段。
- 同步SYN:(SYNchronization)? 在TCP連接建立時用來同步序號。當 SYN=1,ACK=0時,表明這是一個連接請求報文段。對方若同意建立連接,則應在響應報文段中使用 SYN=1,ACK=1。因此,SYN置為1,就表示這是一個連接請求或連接接受報文段。我們將攜帶SYN標志的TCP報文段稱為同步報文段。關于TCP連接的建立和釋放,會在下面的部分進行詳細的討論。
- 終止FIN:(FINish)? 原來關閉一個TCP連接。當 FIN=1 時,表明此報文段的發送方的數據已經發送完畢,通知對方本端要關閉連接了。我們將攜帶FIN標志的TCP報文段稱為結束報文段。
(7)窗口:占2字節。窗口值是 [0, 2^16-1]之間的整數值。窗口字段指的是發送本報文段的一方的接收窗口(Receiver Window, RWND)(而不是自己的發送窗口)。窗口值告訴對方:本端的TCP接收緩沖區還能容納多少字節的數據,這樣對方就可以控制發送數據的速度。之所以要有這個限制,是因為接收方的數據緩存空間是有限的。總之,窗口值是接收方目前允許發送方發送的數據量(以字節為單位),作為發送方設置其發送窗口的依據。
例如,發送了一個TCP報文段,其確認號是701,窗口字段值是1000。這就是告訴對方:從701號算起,我方(即發送此報文段的一方)的接收緩存空間還可接收1000個字節的數據(字節序號是701~1700),你在給我發送數據時,必須考慮到這一點。
總之,記住:窗口字段明確指出了 現在允許對方發送的數據量。窗口值經常在動態變化著。
(8)校驗和:占2個字節。由發送方填充該字段,校驗和字段檢驗的范圍包括首部和數據這兩部分。接收端對TCP報文段執行CRC算法以檢驗TCP報文段在傳輸過程是否有損壞,校驗和檢測也是TCP提供可靠傳輸的一個重要保障。和UDP數據報一樣,在計算校驗和時,要在TCP數據報的前面加上12字節的偽首部。TCP偽首部的格式和UDP用戶數據報的偽首部格式一樣,但應把偽首部第4個字段中的17改為6,(TCP的協議號是6),把第5個字段的UDP長度改成TCP長度。接收方收到此報文段后,仍要加上這個偽首部來計算校驗和。若使用IPV6,則相應的偽首部也要改變。
UDP用戶數據報的首部和偽首部(9)緊急指針:占2個字節。緊急指針字段僅在 URG=1 時才有意義。它指出本報文段中緊急數據的字節數(緊急數據都是放在TCP報文段數據部分的前面,緊急數據結束后才是普通數據)。該字段值和序號字段相加表示最后一個緊急數據的下一個字節的序號值。當所有緊急數據都處理完時,TCP就告訴應用程序恢復到正常操作。值得注意的是,即使窗口值為0時也可發送緊急數據。
(10)選項:TCP頭部最后一個選項(options)字段是可變長的可選信息,最長可達40字節,因此TCP首部最長是60字節。當沒有使用“選項”字段時,TCP首部長度是20字節。
2.2 TCP首部選項字段結構
TCP首部結構的最后一個選項(Options)字段是一個可變長的可選信息。
上面我們已經知道,在TCP報文段的首部有一個“數據偏移”字段,占 4 bit位,最大能表示的十進制數為 15,單位為32位字(也就是4字節),因此數據偏移字段的最大值是60字節。該字段的含義是TCP報文段的首部長度。
因此,TCP首部選項字段的最大長度 = 60字節 - 首部固定大小的20字節 = 40字節。
典型的TCP首部選型字段的結構示意圖如下所示:
TCP首部選項字段的一般結構- ?kind:占1個字節。該字段是說明選項的類型。有的TCP選項沒有后面兩個字段,僅包含1字節的kind字段。
- length:占1個字節。指定該選項的總長度。該長度包括kind字段和length字段占據的2個字節。
- info:是選項的具體信息。常見的TCP選項有7種,如下圖所示:
(1)kind=0:是選項表結束選項。
(2)kind=1:是空操作(nop)選項,沒有特殊含義,一般用于將TCP選項的總長度填充為4字節的整數倍。
(3)kind=2:是最大報文段長度(Maximum Segment Size,MSS)選型。MSS是每一個TCP報文段中的數據部分的最大長度。數據部分加上TCP首部才等于整個的TCP報文段,所以MSS并不是整個TCP報文段的最大長度,而是TCP報文段長度減去TCP首部長度。TCP模塊通常將MSS設置為(MTU-40)字節(減掉的這40字節包括20字節的TCP首部長度和20字節的IP首部長度)。這樣攜帶TCP報文段的IP數據報的長度就不會超過MTU(假設TCP報文段首部和IP數據報首部都不包含選項字段,當然這是一般情況),從而本機發生IP分片。對于以太網而言,MSS的值是1460(1500-40)字節。
為什么要規定一個最大報文段長度MSS呢?這并不是考慮接收方的接收緩存可能放不下TCP報文段中的數據,實際上,MSS與接收窗口值沒有關系。主要原因是為了提高網絡的利用率。因為TCP在傳送數據時,是以報文段為單位發送數據的。而待傳送的數據是以 MSS 的大小為單位進行分割的,然后加上TCP首部,就組裝成一個完整的TCP報文段。進行重發時,也是以 MSS 為單位。
如果MSS設置太低了,網絡的利用率就會降低;如果MSS設置太高了,又會增加網絡處理IP分片的開銷。因此,設置一個合理的MSS值是很有必要的,MSS的值應盡可能大些,只要在IP層傳輸時不需要再分片就行,最理想的情況是,最大TCP報文段長度正好是IP數據報中不會被分片處理的最大數據長度。
在建立TCP連接建立的過程中,雙方都把自己能夠支持的MSS填入這一字段,以后就按照這個數值傳送數據,兩個傳送方向可以有不同的MSS值。若主機未填寫這一項,則MSS的默認值是536字節長度。因此,所有在互聯網上的主機都應能接受報文段長度是 536+20(TCP報文段固定首部長度)=556字節。
<備注1> MTU(Maximum Transmission Unit,最大傳輸單元) 它屬于數據鏈路層上的概念,表示的是數據鏈層幀的數據部分的最大長度。
<備注2> 有一種流行的說法:在TCP連接建立的階段“雙方協商MSS值”,但這是錯誤的,因為這里并不存在任何的協商,而只是一方把MSS值設定好以后通知另一方而已。
(4)kind=3:窗口擴大因子選項。該選項是為了擴大窗口。我們知道,TCP首部的窗口字段長度是16位,因此最大的窗口大小為 64K(65 535) 字節。由上圖可知,窗口擴大選項占3個字節,其中有一個字節表示移位值M。假設TCP首部中的接收窗口字段值為 N,窗口擴大選項中的移位值為 M,那么TCP報文段的實際接收窗口大小為:N * 2^M,或者說N左移M位。注意,M的取值范圍是[0, 14]的整數值。我們可以通過修改 Linux系統中的 /proc/sys/ipv4/tcp_window_scaling 內核變量來啟動或關閉窗口擴大因子選項。
和MSS選項一樣,窗口擴大因子選項只能出現在同步報文段中,否則將被忽略。但同步報文段本身不執行窗口擴大操作,即同步報文段首部的接收窗口字段值就是該TCP報文段的實際接收窗口大小。當TCP連接建立好之后,每個數據傳輸方向的窗口擴大因子就確定下來了。如果連接一方實現了窗口擴大,當它不再需要擴大其窗口時,可發送M = 0的選型,使窗口大小回到16。
(5)kind=4:選擇確認(Selective Acknowledgment, SACK)選項。TCP通信時,如果某個TCP報文段丟失,則TCP模塊會重傳最后被接收方確認的TCP報文段后續的所有報文段,這樣原先已經正確傳輸的TCP報文段也可能重復發送,從而降低了TCP性能。SACK選項正是為了改善這種情況而產生的,它使TCP模塊只重新發送丟失的TCP報文段,而不用發送所有未被確認的TCP報文段。選擇確認選項用在TCP連接建立過程中,表示是否支持SACK選項。我么可以通過修改Linux系統的 /proc/sys/net/ipv4/tcp_sack 內核變量來啟用或關閉選擇確認選項。
(6)kind=5:是SACK實際工作的選項。該選項的參數告訴發送方,本端已經接收到的不連續的數據塊,從而讓發送端據此檢查并重發丟失的數據塊。每個塊邊沿(end of block)參數包含一個4字節的序號。其中,塊左邊沿表示不連續塊的第一個數據字節的序號,而塊右邊沿則表示不連續塊的最后一個數據字節的下一個字節序號。這樣一對參數(塊左邊沿和塊右邊沿)之間的數據就是沒有收到的。因為一個塊信息占8字節,所有TCP首部選型中實際上最多可以包含4個這樣的不連續數據塊(考慮到選項類型的長度占用的2個字節)。
(7)kind=8:是時間戳選項,占10個字節。時間戳選項有以下兩個功能:
第一,用來計算通信雙方之間的回路時間(Round Trip Time,RTT)。發送方在發送TCP報文段時把當前時間值放入時間戳字段,接收方在確認該報文段時把時間戳字段值復制到時間戳回顯應答字段。因此,發送方在收到確認報文后,可以準確地計算出RTT來。
第二,用于處理TCP序號超過2^32(4,294,967,296)的情況,這又稱為防止序號繞回PAWS(Protect Against Wrapped Sequence numbers)。我們知道,TCP報文段的序號只有32位,而每增加2^32個序號后就會重新從0開始編號。當使用高速網絡時,在一次TCP連接的數據傳送中序號很可能會被重復使用。例如,當使用 1.5Mbit/s的速率發送TCP報文段時,序號重復要6小時以上。但若使用2.5Gbit/s的速率發送數據報時,則不到14秒鐘序號就會重復。為了使接收方能夠把新的報文段和遲到很久的報文段(序號相同的情況下)區分開,可以在報文段中加上時間戳選項。我們可以通過修改Linux系統的 /proc/sys/net/ipv4/tcp_timestamps 內核變量來啟用和關閉時間戳選項。
示例1,我們使用Wireshark軟件查看一個TCP連接的SYN同步報文段信息。
SYN同步報文段信息?由上圖可以看到,TCP報文段首部各個字段的信息,該報文段的首部長度是40字節,其中包括固定的首部長度20字節+選項字段20字節長度。標志位Flags中,只有 SYN=1,其他標志位均為0。初始序號值為0,確認號為1,接收窗口值為5840,檢驗和字段為0x5574,緊急指針字段為0。
?我們展開其Options選項的內容,如下圖所示:
SYN同步報文段Options選項信息?由上圖可知,在Options選項中,設置了如下的幾種TCP選項:
- kind=2,最大報文段MSS選項,MSS的值為1460。
- kind=4,選擇確認SACK選項
- kind=8,時間戳timestamps選項
- kind=1,無操作NOP選項
- kind=3,窗口擴大(Window scale)選項,移位值為7,可以擴大2^7=128倍。
三、TCP的連接管理
TCP是面向連接的傳輸層協議。TCP連接的建立和釋放是每一次面向連接的通信中必不可少的過程。因此TCP通信過程有3個階段,即:連接建立、數據傳輸和連接釋放。這里我們主要將TCP連接的建立和釋放的過程。
3.1 TCP連接的建立
TCP連接的建立采用客戶-服務器方式。主動發起連接建立的應用進程叫做客戶端(client),而被動等待連接建立的應用進程叫做服務器(server)。
TCP建立連接的過程叫做握手,握手的過程需要在客戶端和服務器之間交換3個TCP報文段,因此我們形象地將TCP連接的建立過程稱為“三次握手”。
下圖是TCP三次握手建立連接的過程,假定主機A運行的是TCP客戶程序,主機B運行的是TCP服務器程序。最初兩端的TCP進程都處于關閉(Closed)狀態。注意:A客戶端主動打開連接,B服務器被動打開連接。
?描述整個過程:
【1. 服務器初始化狀態】
主機B的TCP服務器進程先創建傳輸控制塊(TCB),這時socket(),bind() 函數已經執行完畢,服務器進程準備接受客戶進程的連接請求。然后服務器進程調用listen()函數,此時服務器進程處于監聽(listen)狀態,緊接著調用accept()函數,等待客戶的連接請求到來。如有,即作出響應。
服務端進程調用函數順序:socket—>bind—>listen—>accept。當執行到accept()函數時,服務器進程會一直處于阻塞狀態,直到有客戶連接請求到達才返回。
【2. 客戶端發起連接請求,發送SYN同步報文段,第一次握手】
主機A的客戶進程也是首先創建傳輸控制塊(TCB),然后向主機B發出連接請求報文段,這時請求報文段的首部的同步位SYN=1,同時選擇一個初始序號seq=x,這個初始序號x就是隨機產生的整數ISN。TCP規定,SYN報文段(即SYN=1的TCP報文段)不能攜帶數據,但要消耗一個序號。這時,TCP客戶進程進入 SYN-SENT(同步已發送) 狀態。
客戶進程調用函數順序:socket——>connect。當客戶進程調用connect()函數時,客戶進程就會向服務器進程發出連接請求的SYN同步報文段。
前面我們已經講過,攜帶SYN標志的TCP報文段稱為同步報文段。此時,同步標志位 SYN = 1。
ISN(Initial Sequence Number) 初始序列號
【3. 服務器同意建立連接,回復確認信息,第二次握手】
主機B的服務器進程收到連接請求報文段后,如同意建立連接,則向主機A的客戶進程發送確認報文段。在確認報文段的首部中,SYN=1,ACK=1,確認號=x+1(沒有數據,所以長度為0,直接seq+1即可),同時也為自己選擇一個初始序號seq = y。請注意,這個確認報文段也不能攜帶數據,但同樣要消耗一個序號。這時,TCP服務器進程進入 SYN-RCVD(同步收到) 狀態。
【4. 客戶端確認連接,發送確認連接信息,第三次握手】
主機A的客戶進程收到主機B的服務器進程的確認報文段后,還要向主機B的服務器進程的SYN報文段給出確認。在確認報文段的首部中,ACK=1,確認號ack=y+1,而自己的序號seq=x+1。TCP的標準規定,ACK報文段可以攜帶數據。但如果不攜帶數據,則不消耗序號。在這種情況下,客戶進程的下一個數據報文段的序號仍是seq=x+1。這是,TCP連接已經建立,主機A的客戶進程也進入 ESTABLISHED(已建立連接)狀態。當主機B的服務器進程接收到主機A的客戶進程發來的確認報文段后,也進入ESTABLISHED(已建立連接)狀態。
上面給出的TCP連接建立過程叫做“三次握手”。請注意,在上圖中,B發送給A的報文段,也可以拆成兩個報文段分兩次發送。即先發送對A的連接請求同步報文段的確認報文段(ACK=1, ack=x=1),接著再發送B自己的連接請求的同步報文段(SYN=1, seq=y)給A。A收到B的同步報文段后,再給B回復一個確認報文段。那么,這樣的過程就變成了“四次握手”,但效果是一樣的。
?3.1.1 TCP連接建立相關問題
問題1:TCP連接的建立為什么需要3次握手,而不是兩次握手?
在謝希仁編寫的《計算機網絡》一書中,給出的理由是:防止已失效的連接請求報文段突然又傳送到了服務器端,因而產生錯誤。但是這個解釋是不夠準確的,不是最根本原因。首先,我們要清楚一點,TCP是全雙工通信的。三次握手的一個重要作用是客戶端和服務端交換彼此的ISN,以便讓對方知道接下來接收數據的時候如何按序列號重新組裝TCP報文段。如果只有兩次握手,至多只有連接發起方的起始序列號被確認,而另一方的起始序列號卻得不到對方的確認。因為只有TCP通信雙方的SYN同步報文段的首部中有本端的起始序列號,所以每一方都要對對方的SYN同步報文段回復一個ACK確認報文段,用來表明我方已經確認收到你方發送的同步報文段了。
結論:TCP連接的建立過程中的三次握手,握的就是通信雙方的起始序列號。三次握手實質上是三報文握手。
下面,我們來討論一下謝希仁先生給出的理由的這種情況的分析。
(1)“已失效的連接請求報文”是如何產生的。現假定出現一種異常情況,即 客戶端A發出的第一個連接請求報文段并沒有丟失,而是在某個網絡結點長時間滯留了,以致延誤到連接釋放以后的某個時間才到達B。本來這是一個早已失效的連接請求報文段,但服務端B收到此失效的連接請求報文段后,就誤以為是客戶端A又發出一次新的連接請求。于是就向A發出確認報文段,同意建立連接。如果只有兩次握手,那么只要B發出確認報文段,B就認為新的連接建立了,B進入ESTABLISHED(已建立連接)狀態。
(2)由于現在A并沒有發出建立連接的請求,因此不會理睬B的確認,也不會向B發送數據。但B卻以為新的連接是已經建立了的,并一直在等待A發來數據。也就是說,B此時一直處于忙等狀態,其結果是浪費了B的資源開銷。
結論:兩次握手,可能導致服務器需要維護許多不成功的TCP連接,造成服務器資源的浪費。
需要注意的是,在TCP連接建立的過程中,只要服務器進程收到了連接請求的SYN同步報文段,服務器就會為這個連接請求創建傳輸控制塊TCB數據結構,這就需要開辟內存空間來維護這個數據結構。
問題2:在TCP連接建立過程中,如果服務器一直收不到客戶端的ACK確認報文段,會發生什么?
操作系統會給每個處于 SYN-RCVD狀態的服務器進程都設定一個定時器,如果超時時間還沒有收到客戶端第三次握手的ACK確認報文段,將會重新發送第二次握手的報文段,直到重發達到一定次數時才會放棄。
問題3:初始序列號ISN為什么要隨機初始化?
seq序號表示的是發送的TCP報文段數據部分的起始字節位置,服務器/客戶端可以通過序號正確讀取數據。如果不是隨機分配起始序列號,那么黑客就會很容易獲取到客戶端與服務器之間TCP通信的初始序列號,然后通過偽造序列號讓通信主機讀取到攜帶病毒的TCP報文段,發起網絡攻擊。
問題4:在TCP連接建立的過程中,可能會出現什么攻擊?如何解決?
SYN flood 攻擊:又稱為SYN泛洪攻擊。下面介紹一下泛洪攻擊是如何發生的。
(1)攻擊者在短時間內偽造大量不存在的IP地址,向服務器不斷地發送連接請求的SYN同步報文段,當服務端收到這些連接請求的報文段后,就會為該連接請求創建傳輸控制塊來保存客戶端的信息。當有大量的連接請求時,服務器會消耗掉大量的內存資源,直至內存資源被耗盡。
(2)服務器同時需要為每條連接請求回復ACK確認報文段,并等待客戶端的確認。但是客戶端的IP地址是虛假的,也就不會向服務器回復確認報文段,那么服務器需要不斷地重發第2次握手的報文段直至超時。同時,這些偽造的連接請求SYN同步報文段還將長時間占用未連接隊列(Linux默認的限制一般是256個),導致正常客戶端的連接請求SYN同步報文段被丟棄,目標系統運行緩慢,嚴重者引起網絡阻塞甚至服務器系統癱瘓。
我們可以通過下面這個圖來直觀了解一下SYN泛洪攻擊的過程:
SYN泛洪攻擊?解決的辦法:
方法1:縮短SYN Timeout 時間。由于 SYN Flood 攻擊的效果取決于服務器上保持的半連接數,這個值=SYN攻擊頻度 x SYN Timeout,所以通過縮短從接收到SYN報文段到確定這個報文段無效并丟棄該連接的時間。例如,設置為20秒以下(過低的SYN Timeout 設置可能會影響客戶的正常訪問),可以成倍地降低服務器的載荷。
方法2:設置 SYN Cookie。就是給每一個連接請求的IP地址分配一個Cookie,如果短時間內連續收到某個IP地址的大量重復SYN報文段,就認定是收到了攻擊。以后從這個IP地址來的報文段會被丟棄。
方法3:使用防火墻。SYN Flood攻擊很容易就能被防火墻攔截。
3.1.2 連接超時問題
前面介紹的是TCP連接正常建立的過程。如果客戶端訪問一個距離它很遠的服務器或者由于網絡繁忙,導致服務器對于客戶端發出的同步報文段沒有應答,此時客戶端程序將產生什么樣的行為呢?顯然,對于提供可靠服務的TCP來說,它必然是先進行重連(可能執行多次),如果重連仍然無效,則通知應用程序連接超時,建立TCP連接失敗。
發起TCP連接請求的客戶端,當發送了同步報文段后,會開啟一個重傳定時器,當超時時間到達后,如果沒有收到確認報文段,會重發一次同步報文段,并將重連的超時時間增加一倍,直到達到規定的重連次數為止。TCP重連次數是由 /proc/sys/net/ipv4/tcp_syn_retries 內核變量定義的,默認值是6,它表示的含義是建立TCP連接時SYN同步報文段重發的次數。
可以通過sysctl 命令來查看:(Linux系統:CentOS-8.3)
# sysctl -a | grep tcp_syn_retries net.ipv4.tcp_syn_retries = 6?在Linux系統中,連接超時典型為2分7秒,而對于一些Client來說,這是一個非常長的時間,所以在實際網絡編程中,可以使用非阻塞模式來實現。例如:使用 select(2)、poll(2)、epoll(2)等系統調用來實現多路復用。
下來來分析一下,這個2分7秒的時間間隔是怎樣來的。
2分7秒 即 127秒,剛好是 2 的 7 次冪減1,如果TCP連接建立的SYN報文段超時時間間隔是按照2的冪來遞增的話,那么:
第 1 次發送 SYN 報文后等待 1s(2 的 0 次冪),如果超時,則重發 第 2 次發送后等待 2s(2 的 1 次冪),如果超時,則重發 第 3 次發送后等待 4s(2 的 2 次冪),如果超時,則重發 第 4 次發送后等待 8s(2 的 3 次冪),如果超時,則重發 第 5 次發送后等待 16s(2 的 4 次冪),如果超時,則重發 第 6 次發送后等待 32s(2 的 5 次冪),如果超時,則重發 第 7 次發送后等待 64s(2 的 6 次冪),如果超時,則超時失敗綜上,1+2+4+8+16+32+64=127?上面總的超時時間剛好是127秒。也就是說,Linux內核在嘗試建立TCP連接時,最多會嘗試7次,重發6次。
修改重連次數和超時時間
可以通過以下命令修改該值,例如將其改為5。
sysctl -w net.ipv4.tcp_syn_retries=5如果希望重啟系統后仍生效,可以在 /etc/sysctl.conf 文件中添加如下內容:
net.ipv4.tcp_syn_retries=5在應用程序中,我們可以通過設置socket選項中的 SO_SNDTIMEO 選項,然后調用connect()函數。具體代碼如下:
//設置connect超時時間 int timeout_connect(const char *ip, int port, int timeout) {int ret = 0;struct sockaddr_in cli_addr;bzero(&cli_addr, sizeof(cli_addr));cli_addr.sin_family = AF_INET;inet_pton(AF_INET, ip, &cli_addr.sin_addr);cli_addr.sin_port = htons(port);int sockfd = socket(AF_INET, SOCK_STREAM, 0);assert(sockfd >= 0)//通過選項SO_SNDTIMEO所設置的超時時間類型是timeval,這和select()系統調用的超時參數類型相同struct timeval timeout;timeout.tv_sec = time;timeout.tv_usec = 0;socklen_t len = sizeof(timeout);ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);assert(ret != 1);ret = connect(sockfd, (struct sockaddr*)&cli_addr, sizeof(cli_addr));if(ret == -1){//超時對應的錯誤號是EINPROGRESS。下面這個條件如果成立,我們就可以處理定時任務了if(errno == EINPROGRESS){printf("connection timeout, process timeout logic\n");return -1;}printf("error occur when connecting to server\n");return -1;}return sockfd; }3.2 TCP連接的釋放
TCP連接的釋放可以用“四次揮手”的過程來描述。數據傳輸結束后,通信的雙方都可釋放連接。現在 客戶端A和服務器B都處于 ESTABLISHED 狀態。釋放連接的過程如下圖所示:
?描述整個過程:
【1. A客戶端主動斷開連接,發送釋放連接的FIN報文段,第一次揮手】
A客戶端進程先向B服務器進程發送釋放連接的FIN結束報文段,并停止再發送數據,主動關閉TCP連接。在結束報文段的首部中,終止控制位FIN=1,其序號字段seq=u,它等于前面已發送過的數據的最后一個字節的序號加1。此時,A客戶端進程進入 FIN-WAIT-1(終止等待1)狀態,等待B服務器進程的確認。請注意,TCP規定,FIN報文段即使不攜帶數據,它也要消耗掉一個序號。這點和SYN報文段是一樣的。
【2. B服務器收到A客戶端的結束報文段,發出確認報文段,第二次揮手】
B服務器進程收到A客戶端進程發來的釋放連接的FIN結束報文段后,立即發出確認報文段,確認號ack=u+1,而這個報文段自己的序號seq=v,等于B服務器前面已發送過的數據的最后一個字節的序號加1。然后B服務器進程進入 CLOSE-WAIT(關閉等待)狀態。TCP服務器進程這時通知高層的應用進程,那么從 A 到 B 這個方向的連接就釋放了,此時TCP連接處于半關閉(half-close)狀態,即A客戶端已經沒有數據要發送了,但B服務器若發送數據,A客戶端仍要接收。也就是說,從 B 到 A 這個方向的連接并未關閉,這個狀態可能會持續一段時間。
A客戶端收到B服務器的確認報文段后,就進入 FIN-WAIT-2(終止等待2)狀態,等待B服務器發出的FIN結束報文段。
【3. B服務器釋放連接,發出連接釋放的結束報文段,第三次揮手】
當B服務器已經沒有要向A客戶端發送的數據時,其應用進程就通知TCP釋放連接,向A客戶端發送釋放連接的結束報文段。在這個結束報文段的首部中,終止控制位FIN置1,假定其序號字段為w(在半關閉狀態中,B服務器可能又發送了一些數據),同時還必須重復上次已發送過的確認號ack=u+1。這時,B服務器進程就進入 LAST-ACK(最后確認)狀態,等待A客戶端的確認。
【4. A客戶端收到B服務器釋放連接的結束報文段,發出確認報文段,第四次揮手】
A客戶端在收到B服務器的釋放連接的結束報文段后,必須對此發出確認,即向B服務端發送一個確認報文段。在確認報文段的首部中,控制位ACK=1,確認號字段ack=w+1,而自己的序號字段seq=u+1(根據TCP標準,前面發送過的FIN報文段是要消耗一個序號的)。然后A客戶端進入到TIME-WAIT(時間等待)狀態。
請注意,此時TCP連接還沒有釋放掉,必須經過時間等待計時器(TIME-WAIT timer)設置的時間2MSL后,A才進入到 CLOSED 狀態。時間MSL(Maximum Segment Lifetime,最長報文段壽命) 即一個TCP報文段存活的最長時間。RFC793建議設為2分鐘,現在可以根據情況使用更小的MSL值。因此從A客戶端進入到 TIME-WAIT 狀態后,要經過4分鐘才能進入到CLOSED狀態,才可以建立下一個新的連接,當A客戶端撤銷相應的傳輸控制塊TCB后,就結束了這次的TCP連接。
B服務器只要收到了A客戶端發出的確認報文段,就進入 CLOSED狀態。同樣,B服務器在撤銷相應的傳輸控制塊TCB后,就結束了此次的TCP連接。
可以發現,B服務器結束TCP連接的時間要比A客戶端早一些。
上述內容就是TCP連接釋放的過程,俗稱“四次揮手”過程,其實質上是四報文揮手。
3.2.1 TCP連接釋放相關問題
問題1:為什么建立連接是三次握手,而關閉連接卻是四次揮手?
在建立TCP連接時,當服務器收到客戶端發來的連接請求的SYN同步報文段后,可以直接發送一個ACK+SYN的報文段給客戶端,其中ACK控制位是用來確認的,SYN控制位是用來同步的。但是在關閉連接時,當服務器收到客戶端發來的FIN結束報文段時,自己這邊可能還有數據沒有發送完,因此只能先回復一個ACK確認報文,告訴客戶端,“你發來的FIN報文我收到了”。只有等到服務器所有的數據都發送完了,服務器才會向客戶端發送一個FIN結束報文段,最后客戶端回復一個確認報文,總共就是四次揮手過程。也就是說,在關閉連接的第二次揮手階段,服務器不能將控制位ACK+FIN 同時放在一個報文段中回復給客戶端。
注意:發送了FIN結束報文段,只是表示本端不能再繼續發送數據了,但是還可以接受數據。TCP通信它是全雙工的,收到一個FIN報文段,只是關閉了一個方向上的連接,而另一個方向仍能發送數據,此時TCP處于半關閉狀態。
問題2:為什么客戶端在 TIME-WAIT 狀態時,必須等待2MSL的時間才能進入到 CLOSED狀態呢?
第一,為了保證客戶端發送的最后一個ACK報文段能夠到達對端,即保證可靠地終止TCP連接。因為如果出現網絡擁塞,這個ACK報文段是有可能丟失的,因而使處于LAST-ACK狀態的服務器收不到客戶端對自己已發送過的FIN+ACK報文段的確認。那么,服務器會超時重傳這個FIN+ACK報文段,而客戶端就能在2MSL時間內收到這個重傳的FIN+ACK報文段。接著客戶端重傳一次確認,重新啟動2MSL計時器。最后,客戶端和服務器都正常進入到CLOSED狀態。如果客戶端在 TIME-WAIT 狀態時不等待一個2MSL時間,而是在發送完ACK確認報文段后立即釋放連接,進入到CLOSED狀態,那么就無法收到服務器重傳的FIN+ACK報文段,因而也不會再發送一次確認報文段。這樣,服務器就無法按照正常步驟進入到CLOSED狀態。
第二,防止已失效的連接請求報文段出現在本次TCP連接中。客戶端在發送完最后一個ACK報文段后,再經過2MSL的時間后,就可以使本次TCP連接持續的時間內所產生的所有報文段都從網絡上消失。這樣就可以使下一個新的TCP連接中不會出現之前舊的連接請求報文段。
問題3:為什么是2MSL,這個時間是如何得來的?
我們知道,MSL是TCP報文段的最大生存時間,2MSL的時間是從客戶端發出最后一個ACK報文段開始計時的,考慮到了重傳的因素。
客戶端—[ACK報文段]—>服務器 ?????????? 1MSL
服務器—[FIN+ACK報文段]—>客戶端??? 1MSL (如果服務器在超時時間內沒有收到客戶端的ACK報文段,就重傳FIN+ACK報文段)
保證在TCP的兩個傳輸方向上,那些尚未被接收或遲到的報文段都消失,理論上保證最后一個ACK報文段可靠到達。
問題4:TIME-WAIT 狀態何時出現?TIME-WAIT會帶來哪些問題?
TIME-WAIT狀態是主動關閉連接的一方收到了對方發來的FIN結束報文段并且本端發送ACK確認報文段后的狀態。
TIME-WAIT的引入是為了讓TCP報文段得以自然消失,同時為了讓被動關閉的一方能夠正常關閉連接。
- 服務器主動關閉連接,短時間內關閉了大量的客戶端連接,會造成服務器上出現大量的 TIME-WAIT狀態的連接,占據大量的tuple(源IP地址、目的IP地址、協議號、源端口、目的端口),嚴重消耗著服務器的資源。
- 客戶端主動關閉連接,短時間內大量的短連接,會大量消耗客戶端主機的端口號,畢竟端口號只有65535個,斷開耗盡了,后續就無法啟用新的TCP連接了。
問題5:解決 TIME-WAIT 狀態引起的bind()函數執行失敗的問題?
問題場景:我在編寫一個基于TCP連接的socket服務器程序并反復調試的時候,發現了一個讓人無比心煩的情況:每次kill掉該服務器進程并重新啟動的時候,都會出現bind錯誤:error:98,Address already in use。然而再kill掉該進程,再次重新啟動的時候,就bind成功了。
問題原因:當我kill掉服務器進程的時候,系統并沒有馬上完全釋放掉socket的TCP連接資源,此時socket處于TIME-WAIT狀態,當我使用 netstat 命令查看該進程的端口號時,發現該進程處于TIME-WAIT狀態,需要等待2MSL時間后,整個TCP連接才算真正結束。這就是我的服務器進程被殺死后,不能馬上重新啟動的原因(錯誤提示為:“Address already in use”)。Linux系統中,一個端口釋放后需要等待兩分鐘才能再次被使用。
問題解決:我們可以使用setsockopt()函數設置socket描述符的SO_REUSEADDR選項,該socket選項可以讓端口被釋放后立即就能被再次使用,表示允許創建端口號相同但是IP地址不同的多個socket描述符。
代碼描述如下:
int opt = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));問題6:TIME-WAIT 和 CLOSE-WAIT 的區別?
- TIME-WAIT 狀態是主動關閉TCP連接的一方在本端已經關閉的前提下,收到對端的關閉請求并將ACK確認報文段發送過去后所處的狀態。
這種狀態表示:通信雙方都已完成工作,只是為了保證本次TCP連接能夠順利正常的關閉,即可靠地終止TCP連接。
- CLOSE-WAIT 狀態是被動關閉TCP連接的一方在接收到對端的關閉請求(FIN結束報文段)并且將ACK確認報文段發送出去后所處的狀態。
這種狀態表示:收到了對端關閉連接的請求,但是本端還沒有完成工作,還未關閉本端的TCP連接。
問題7:半連接、半打開、半關閉的區別?
半連接:在TCP連接建立的三次握手過程中,主動發起連接請求的一方不發最后一次的ACK確認報文,使得服務器端阻塞在 SYN-RCVD(同步收到)狀態。
半打開:如果TCP通信一方異常關閉(如斷網、斷電、進程被kill掉),而通信對端并不知情,此時TCP連接處于半打開狀態,如果雙方不進行數據通信,是無法發現問題的。解決的辦法是引入心跳機制,設置一個保活計時器(keepalive timer),以檢測半打開狀態,檢測到了就發送RST復位報文段,重新建立連接。
設想有這樣的情況:客戶端已主動與服務器建立了TCP連接。但是后來客戶端的主機突然發生故障,客戶端的TCP服務進程被kill掉了。顯然,服務器以后就不能再收到客戶端發來的數據。因此,應當有必要的措施使得服務器不要再白白地等待下去。解決的辦法就是使用保活計時器。服務器每收到一次客戶端的數據,就重新設置保活計時器,時間的設置通常是兩小時。若兩小時沒有收到客戶端發來的數據,服務器就發送一個探測報文段,以后每隔75秒鐘發送一次。若一連發送10個探測報文段后仍無客戶的響應,服務器就認為客戶端出了故障,接著就關閉這個TCP連接。
半關閉:主動發起連接關閉請求的一方A發送了FIN結束報文段,對端B回復了ACK確認報文段后,B并沒有立即發送本端的FIN結束報文段給A。此時A端處于FIN-WAIT-2(結束等待2)狀態,A仍然可以接收B發送過來的數據,但是A已經不能再向B發送數據了。這時的TCP連接為半關閉狀態。
半打開和半關閉的區別:半打開是指TCP通信的一端由于異常關閉,通信雙方已經無法進行正常的數據傳輸了;半關閉是指TCP通信的其中一個方向上的連接已經關閉了,而另一個方向的連接還是正常的,仍然可以進行數據傳輸。
四、TCP狀態轉換
為了更清晰地看出TCP連接的各種狀態之間的關系,下圖給出了TCP的狀態轉換示意圖。
說明:紫色框框是TCP狀態,紅色是服務器進程的正常狀態轉換,藍色是客戶端進程的正常狀態轉換,黑色是異常變遷,即出現問題時的狀態轉換。
TCP的狀態轉換?4.1 服務器正常狀態轉換
?服務器狀態轉換示意圖如下所示:
1. TCP連接建立的三次握手階段
- CLOSED —> LISTEN:服務器進程調用listen()后進入LISTEN狀態,被動等待客戶端發起連接。
- LISTEN?? —> SYN-RCVD:服務器進程一旦收到客戶端發來的連接請求SYN同步報文段,就會將該連接放入內核中的等待連接隊列中,并向客戶端發送ACK+SYN確認報文段,服務器進程進入 SYN-RCVD狀態。
- SYN-RCVD —> ESTABLISHED:服務器一旦接收到客戶端發來的ACK確認報文段,就進入 ESTABLISHED 狀態,此時TCP連接建立成功,可以進行數據傳輸了。
2. TCP連接釋放的四次揮手階段
- ESTABLISHED —> CLOSE-WAIT:當客戶端主動發起連接關閉,服務器收到客戶端發來的FIN結束報文段,服務器向客戶端返回確認報文段,服務器就進入了 CLOSE-WAIT狀態,此時TCP處于半關閉狀態。
- CLOSE-WAIT —> LAST-ACK:當服務器向客戶端發送關閉連接的FIN結束報文段后,服務器就進入 LAST-ACK 狀態。
- LAST-ACK —> CLOSED:當服務器收到客戶端對自己發出的FIN結束報文段的確認報文段后,服務器關閉TCP連接,進入 CLOSED 狀態。
4.2 客戶端正常狀態轉換
?客戶端狀態轉換示意圖如下所示:
1. TCP連接建立的三次握手階段
- CLOSED —> SYN-SENT: 客戶端調用connect()向服務器發送連接請求的同步SYN報文段,表示想與服務器建立TCP連接,自己進入 SYN-SENT 狀態,等待服務器的響應。
- SYN-SENT —> ESTABLISHED: connnect()調用成功,客戶端收到服務器的ACK確認報文段,此時客戶端進入 ESTABLISHED 狀態。
2. TCP連接釋放的四次揮手階段
- ESTABLISHED —> FIN-WAIT-1:?客戶端主動調用close(),向服務器發送斷開連接的FIN結束報文段,自己進入 FIN-WAIT-1 階段,等待服務器的響應。
- FIN-WAIT-1 —> FIN-WAIT-2:?客戶端收到服務器對自己發出的FIN結束報文段的確認報文段后,進入 FIN-WAIT-2 階段,等待服務器的結束報文段。
- FIN-WAIT-2 —> TIME-WAIT:?客戶端收到服務器發來的FIN結束報文段,發送ACK確認報文段給服務器,自己則進入 TIME-WAIT 狀態。
- TIME-WAIT —> CLOSED:?客戶端要等待2MSL,即2個報文最大生存時間,才進入CLOSED狀態。這是為了保證客戶端發送的最后一個ACK確認報文段能夠到達服務器,即保證可靠地終止TCP連接,以及防止已失效的連接請求報文段出現在本連接中。
?五、基于TCP協議的socket通信流程
????????Socket 又稱 ”套接字”,是系統提供的用于網絡通信的方法。它并不是一種協議,沒有規定計算機應當怎么傳遞信息,只是給開發人員提供了一個發送和接收消息的接口。開發人員能使用這個接口提供的方法,發送與接收消息。Socket描述了一個 IP 和 端口號。它簡化了開發員的操作,知道了對方 IP 以及 port,就可以給對方發送消息,再由服務器處理發送過來的這些消息,Socket包含了通信的雙方,即客戶端與服務器。
TCP協議的socket通信流程,如圖所示:
5.1 TCP網絡編程—服務器編程步驟
1、socket():創建一個套接字,創建成功返回一個套接字文件描述符 listen_fd。
1-1、setsockopt():可選,設置socket屬性。
2、bind():綁定本地的 IP 地址和端口號信息到socket上。
3、listen():開啟監聽。
4、accept():接收客戶端的連接請求。連接成功,返回一個連接文件描述符 conn_fd。
5、recv()、send():收發數據。或者使用 read()、write()。
6、close(conn_fd):關閉網絡連接。
7、close(listen_fd):關閉監聽。
5.2 TCP網絡編程—客戶端編程步驟
1、socket():創建一個套接字,創建成功返回一個套接字文件描述符 client_fd。
1-1、setsockopt():可選,設置socket屬性。
1-2、bind():可選,綁定本地的 IP 地址和端口號信息到socket上。
2、connect():請求連接到服務器端。
3、recv()、send():收發數據。或者使用 read()、write()。
4、close(client_fd):關閉網絡連接。
參考
《計算機網絡(第7版-謝希仁)》第5章
《Linux高性能服務器編程》第3章
《UNIX網絡編程卷1:套接字聯網API(第3版)》第1部分
四、TCP三次握手、四次揮手詳解
TCP連接的狀態詳解以及故障排查
TCP 為什么三次握手而不是兩次握手(正解版)
詳解TCP三次握手/四次揮手
?近 40 張圖解被問千百遍的 TCP 三次握手和四次揮手面試題
總結
以上是生活随笔為你收集整理的TCP协议-TCP连接管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 尽量干净地卸载360
- 下一篇: psp记忆棒测试软件,psp记忆棒修复工