40張圖帶你搞懂TCP和UDP
前言
拿下計(jì)網(wǎng)協(xié)議后,我就是公園里最靚的仔
TCP/IP 基礎(chǔ)知識(shí)總結(jié)
計(jì)算機(jī)網(wǎng)絡(luò)基礎(chǔ)知識(shí)總結(jié)
那么下面就開(kāi)始我們本篇文章,文章組織脈絡(luò)如下
運(yùn)輸層
位于應(yīng)用層和網(wǎng)絡(luò)層之間,是 OSI 分層體系中的第四層,同時(shí)也是網(wǎng)絡(luò)體系結(jié)構(gòu)的重要部分。運(yùn)輸層主要負(fù)責(zé)網(wǎng)絡(luò)上的端到端通信。
運(yùn)輸層為運(yùn)行在不同主機(jī)上的應(yīng)用程序之間的通信起著至關(guān)重要的作用。下面我們就來(lái)一起探討一下關(guān)于運(yùn)輸層的協(xié)議部分
運(yùn)輸層概述
計(jì)算機(jī)網(wǎng)絡(luò)的運(yùn)輸層非常類(lèi)似于高速公路,高速公路負(fù)責(zé)把人或者物品從一端運(yùn)送到另一端,而計(jì)算機(jī)網(wǎng)絡(luò)的運(yùn)輸層則負(fù)責(zé)把報(bào)文從一端運(yùn)輸?shù)搅硪欢?,這個(gè)端指的就是?端系統(tǒng)
。在計(jì)算機(jī)網(wǎng)絡(luò)中,任意一個(gè)可以交換信息的介質(zhì)都可以稱(chēng)為端系統(tǒng),比如手機(jī)、網(wǎng)絡(luò)媒體、電腦、運(yùn)營(yíng)商等。
在運(yùn)輸層運(yùn)輸報(bào)文的過(guò)程中,會(huì)遵守一定的協(xié)議規(guī)范,比如一次傳輸?shù)臄?shù)據(jù)限制、選擇什么樣的運(yùn)輸協(xié)議等。運(yùn)輸層實(shí)現(xiàn)了讓兩個(gè)互不相關(guān)的主機(jī)進(jìn)行邏輯通信
的功能,看起來(lái)像是讓兩個(gè)主機(jī)相連一樣。
運(yùn)輸層協(xié)議是在端系統(tǒng)中實(shí)現(xiàn)的,而不是在路由器中實(shí)現(xiàn)的。路由只是做識(shí)別地址并轉(zhuǎn)發(fā)的功能。這就比如快遞員送快遞一樣,當(dāng)然是要由地址的接受人也就是 xxx 號(hào)樓 xxx 單元 xxx 室的這個(gè)人來(lái)判斷了!
TCP 如何判斷是哪個(gè)端口的呢?
還記得數(shù)據(jù)包的結(jié)構(gòu)嗎,這里來(lái)回顧一下
數(shù)據(jù)包經(jīng)過(guò)每層后,該層協(xié)議都會(huì)在數(shù)據(jù)包附上包首部,一個(gè)完整的包首部圖如上所示。
在數(shù)據(jù)傳輸?shù)竭\(yùn)輸層后,會(huì)為其附上 TCP 首部,首部包含著源端口號(hào)和目的端口號(hào)。
在發(fā)送端,運(yùn)輸層將從發(fā)送應(yīng)用程序進(jìn)程接收到的報(bào)文轉(zhuǎn)化成運(yùn)輸層分組
,分組在計(jì)算機(jī)網(wǎng)絡(luò)中也稱(chēng)為?報(bào)文段(segment)
。運(yùn)輸層一般會(huì)將報(bào)文段進(jìn)行分割,分割成為較小的塊,為每一塊加上運(yùn)輸層首部并將其向目的地發(fā)送。
在發(fā)送過(guò)程中,可選的運(yùn)輸層協(xié)議(也就是交通工具) 主要有?TCP
?和?UDP
?,關(guān)于這兩種運(yùn)輸協(xié)議的選擇及其特性也是我們著重探討的重點(diǎn)。
TCP 和 UDP 前置知識(shí)
在 TCP/IP 協(xié)議中能夠?qū)崿F(xiàn)傳輸層功能的,最具代表性的就是 TCP 和 UDP。提起 TCP 和 UDP ,就得先從這兩個(gè)協(xié)議的定義說(shuō)起。
TCP 叫做傳輸控制協(xié)議(TCP,Transmission Control Protocol)
,通過(guò)名稱(chēng)可以大致知道 TCP 協(xié)議有控制傳輸?shù)墓δ?,主要體現(xiàn)在其可控,可控就表示著可靠,確實(shí)是這樣的,TCP 為應(yīng)用層提供了一種可靠的、面向連接的服務(wù),它能夠?qū)⒎纸M可靠的傳輸?shù)椒?wù)端。
UDP 叫做?用戶(hù)數(shù)據(jù)報(bào)協(xié)議(UDP,User Datagram Protocol)
,通過(guò)名稱(chēng)可以知道 UDP 把重點(diǎn)放在了數(shù)據(jù)報(bào)上,它為應(yīng)用層提供了一種無(wú)需建立連接就可以直接發(fā)送數(shù)據(jù)報(bào)的方法。
怎么計(jì)算機(jī)網(wǎng)絡(luò)中的術(shù)語(yǔ)對(duì)一個(gè)數(shù)據(jù)的描述這么多???
在計(jì)算機(jī)網(wǎng)絡(luò)中,在不同層之間會(huì)有不同的描述。我們上面提到會(huì)將運(yùn)輸層的分組稱(chēng)為報(bào)文段,除此之外,還會(huì)將 TCP 中的分組也稱(chēng)為報(bào)文段,然而將 UDP 的分組稱(chēng)為數(shù)據(jù)報(bào),同時(shí)也將網(wǎng)絡(luò)層的分組稱(chēng)為數(shù)據(jù)報(bào)
但是為了統(tǒng)一,一般在計(jì)算機(jī)網(wǎng)絡(luò)中我們統(tǒng)一稱(chēng) TCP 和 UDP 的報(bào)文為?報(bào)文段
,這個(gè)就相當(dāng)于是約定,到底如何稱(chēng)呼不用過(guò)多糾結(jié)啦。
套接字
在 TCP 或者 UDP 發(fā)送具體的報(bào)文信息前,需要先經(jīng)過(guò)一扇?門(mén)
,這個(gè)門(mén)就是套接字(socket)
,套接字向上連接著應(yīng)用層,向下連接著網(wǎng)絡(luò)層。在操作系統(tǒng)中,操作系統(tǒng)分別為應(yīng)用和硬件提供了接口(Application Programming Interface)
。而在計(jì)算機(jī)網(wǎng)絡(luò)中,套接字同樣是一種接口,它也是有接口 API 的。
使用 TCP 或 UDP 通信時(shí),會(huì)廣泛用到套接字的 API,使用這套 API 設(shè)置 IP 地址、端口號(hào),實(shí)現(xiàn)數(shù)據(jù)的發(fā)送和接收。
現(xiàn)在我們知道了, Socket 和 TCP/IP 沒(méi)有必然聯(lián)系,Socket 的出現(xiàn)只是方便了 TCP/IP 的使用,如何方便使用呢?你可以直接使用下面 Socket API 的這些方法。
套接字類(lèi)型
套接字的主要類(lèi)型有三種,下面我們分別介紹一下
數(shù)據(jù)報(bào)套接字(Datagram sockets)
:數(shù)據(jù)報(bào)套接字提供一種無(wú)連接
的服務(wù),而且并不能保證數(shù)據(jù)傳輸?shù)目煽啃?。?shù)據(jù)有可能在傳輸過(guò)程中丟失或出現(xiàn)數(shù)據(jù)重復(fù),且無(wú)法保證順序地接收到數(shù)據(jù)。數(shù)據(jù)報(bào)套接字使用UDP( User DatagramProtocol)協(xié)議
進(jìn)行數(shù)據(jù)的傳輸。由于數(shù)據(jù)報(bào)套接字不能保證數(shù)據(jù)傳輸?shù)目煽啃?,?duì)于有可能出現(xiàn)的數(shù)據(jù)丟失情況,需要在程序中做相應(yīng)的處理。流套接字(Stream sockets)
:流套接字用于提供面向連接、可靠的數(shù)據(jù)傳輸服務(wù)。能夠保證數(shù)據(jù)的可靠性、順序性。流套接字之所以能夠?qū)崿F(xiàn)可靠的數(shù)據(jù)服務(wù),原因在于其使用了傳輸控制協(xié)議,即?TCP(The Transmission Control Protocol)協(xié)議
原始套接字(Raw sockets)
: 原始套接字允許直接發(fā)送和接收 IP 數(shù)據(jù)包,而無(wú)需任何特定于協(xié)議的傳輸層格式,原始套接字可以讀寫(xiě)內(nèi)核沒(méi)有處理過(guò)的 IP 數(shù)據(jù)包。
套接字處理過(guò)程
在計(jì)算機(jī)網(wǎng)絡(luò)中,要想實(shí)現(xiàn)通信,必須至少需要兩個(gè)端系統(tǒng),至少需要一對(duì)兩個(gè)套接字才行。下面是套接字的通信過(guò)程。
socket 中的 API 用于創(chuàng)建通信鏈路中的端點(diǎn),創(chuàng)建完成后,會(huì)返回描述該套接字的
套接字描述符
。
就像使用文件描述符來(lái)訪問(wèn)文件一樣,套接字描述符用來(lái)訪問(wèn)套接字。
當(dāng)應(yīng)用程序具有套接字描述符后,它可以將唯一的名稱(chēng)綁定在套接字上,服務(wù)器必須綁定一個(gè)名稱(chēng)才能在網(wǎng)絡(luò)中訪問(wèn)
在為服務(wù)端分配了 socket 并且將名稱(chēng)使用 bind 綁定到套接字上后,將會(huì)調(diào)用 listen api。
listen
?表示客戶(hù)端愿意等待連接的意愿,listen 必須在 accept api 之前調(diào)用。客戶(hù)端應(yīng)用程序在流套接字(基于 TCP)上調(diào)用?
connect
?發(fā)起與服務(wù)器的連接請(qǐng)求。服務(wù)器應(yīng)用程序使用
accept
API 接受客戶(hù)端連接請(qǐng)求,服務(wù)器必須先成功調(diào)用 bind 和 listen 后,再調(diào)用 accept api。在流套接字之間建立連接后,客戶(hù)端和服務(wù)器就可以發(fā)起 read/write api 調(diào)用了。
當(dāng)服務(wù)器或客戶(hù)端要停止操作時(shí),就會(huì)調(diào)用?
close
?API 釋放套接字獲取的所有系統(tǒng)資源。
雖然套接字 API 位于應(yīng)用程序?qū)雍蛡鬏攲又g的通信模型中,但是套接字 API 不屬于通信模型。套接字 API 允許應(yīng)用程序與傳輸層和網(wǎng)絡(luò)層進(jìn)行交互。
在往下繼續(xù)聊之前,我們先播放一個(gè)小插曲,簡(jiǎn)單聊一聊 IP。
聊聊 IP
IP
?是Internet Protocol(網(wǎng)際互連協(xié)議)
的縮寫(xiě),是 TCP/IP 體系中的網(wǎng)絡(luò)層
協(xié)議。設(shè)計(jì) IP 的初衷主要想解決兩類(lèi)問(wèn)題
提高網(wǎng)絡(luò)擴(kuò)展性:實(shí)現(xiàn)大規(guī)模網(wǎng)絡(luò)互聯(lián)
對(duì)應(yīng)用層和鏈路層進(jìn)行解藕,讓二者獨(dú)立發(fā)展。
IP 是整個(gè) TCP/IP 協(xié)議族的核心,也是構(gòu)成互聯(lián)網(wǎng)的基礎(chǔ)。為了實(shí)現(xiàn)大規(guī)模網(wǎng)絡(luò)的互通互聯(lián),IP 更加注重適應(yīng)性、簡(jiǎn)潔性和可操作性,并在可靠性做了一定的犧牲。IP 不保證分組的交付時(shí)限和可靠性,所傳送分組有可能出現(xiàn)丟失、重復(fù)、延遲或亂序等問(wèn)題。
我們知道,TCP 協(xié)議的下一層就是 IP 協(xié)議層,既然 IP 不可靠,那么如何保證數(shù)據(jù)能夠準(zhǔn)確無(wú)誤地到達(dá)呢?
這就涉及到 TCP 傳輸機(jī)制的問(wèn)題了,我們后面聊到 TCP 的時(shí)候再說(shuō)。
端口號(hào)
在聊端口號(hào)前,先來(lái)聊一聊文件描述以及 socket 和端口號(hào)的關(guān)系
為了方便資源的使用,提高機(jī)器的性能、利用率和穩(wěn)定性等等原因,我們的計(jì)算機(jī)都有一層軟件叫做操作系統(tǒng),它用于幫我們管理計(jì)算機(jī)可以使用的資源,當(dāng)我們的程序要使用一個(gè)資源的時(shí)候,可以向操作系統(tǒng)申請(qǐng),再由操作系統(tǒng)為我們的程序分配和管理資源。通常當(dāng)我們要訪問(wèn)一個(gè)內(nèi)核設(shè)備或文件時(shí),程序可以調(diào)用系統(tǒng)函數(shù),系統(tǒng)就會(huì)為我們打開(kāi)設(shè)備或文件,然后返回一個(gè)文件描述符fd(或稱(chēng)為ID,是一個(gè)整數(shù)),我們要訪問(wèn)該設(shè)備或文件,只能通過(guò)該文件描述符??梢哉J(rèn)為該編號(hào)對(duì)應(yīng)著打開(kāi)的文件或設(shè)備。
而當(dāng)我們的程序要使用網(wǎng)絡(luò)時(shí),要使用到對(duì)應(yīng)的操作系統(tǒng)內(nèi)核的操作和網(wǎng)卡設(shè)備,所以我們可以向操作系統(tǒng)申請(qǐng),然后系統(tǒng)會(huì)為我們創(chuàng)建一個(gè)套接字 Socket,并返回這個(gè) Socket 的ID,以后我們的程序要使用網(wǎng)絡(luò)資源,只要向這個(gè) Socket 的編號(hào) ID 操作即可。而我們的每一個(gè)網(wǎng)絡(luò)通信的進(jìn)程至少對(duì)應(yīng)著一個(gè) Socket。向 Socket 的 ID 中寫(xiě)數(shù)據(jù),相當(dāng)于向網(wǎng)絡(luò)發(fā)送數(shù)據(jù),向 Socket 中讀數(shù)據(jù),相當(dāng)于接收數(shù)據(jù)。而且這些套接字都有唯一標(biāo)識(shí)符——文件描述符 fd。
端口號(hào)是?16
?位的非負(fù)整數(shù),它的范圍是 0 - 65535 之間,這個(gè)范圍會(huì)分為三種不同的端口號(hào)段,由 Internet 號(hào)碼分配機(jī)構(gòu) IANA 進(jìn)行分配
周知/標(biāo)準(zhǔn)端口號(hào),它的范圍是 0 - 1023
注冊(cè)端口號(hào),范圍是 1024 - 49151
私有端口號(hào),范圍是 49152 - 6553
一臺(tái)計(jì)算機(jī)上可以運(yùn)行多個(gè)應(yīng)用程序,當(dāng)一個(gè)報(bào)文段到達(dá)主機(jī)后,應(yīng)該傳輸給哪個(gè)應(yīng)用程序呢?你怎么知道這個(gè)報(bào)文段就是傳遞給 HTTP 服務(wù)器而不是 SSH 服務(wù)器的呢?
是憑借端口號(hào)嗎?當(dāng)報(bào)文到達(dá)服務(wù)器時(shí),是端口號(hào)來(lái)區(qū)分不同應(yīng)用程序的,所以應(yīng)該借助端口號(hào)來(lái)區(qū)分。
舉個(gè)例子反駁一下 cxuan,假如到達(dá)服務(wù)器的兩條數(shù)據(jù)都是由 80 端口發(fā)出的你該如何區(qū)分呢?或者說(shuō)到達(dá)服務(wù)器的兩條數(shù)據(jù)端口一樣,協(xié)議不同,該如何區(qū)分呢?
所以?xún)H憑端口號(hào)來(lái)確定某一條報(bào)文顯然是不夠的。
互聯(lián)網(wǎng)上一般使用?源 IP 地址、目標(biāo) IP 地址、源端口號(hào)、目標(biāo)端口號(hào)?來(lái)進(jìn)行區(qū)分。如果其中的某一項(xiàng)不同,就被認(rèn)為是不同的報(bào)文段。這些也是多路分解和多路復(fù)用
?的基礎(chǔ)。
確定端口號(hào)
在實(shí)際通信之前,需要先確定一下端口號(hào),確定端口號(hào)的方法分為兩種:
標(biāo)準(zhǔn)既定的端口號(hào)
標(biāo)準(zhǔn)既定的端口號(hào)是靜態(tài)分配的,每個(gè)程序都會(huì)有自己的端口號(hào),每個(gè)端口號(hào)都有不同的用途。端口號(hào)是一個(gè) 16 比特的數(shù),其大小在 0 - 65535 之間,0 - 1023 范圍內(nèi)的端口號(hào)都是動(dòng)態(tài)分配的既定端口號(hào),例如 HTTP 使用 80 端口來(lái)標(biāo)識(shí),F(xiàn)TP 使用 21 端口來(lái)標(biāo)識(shí),SSH 使用 22 來(lái)標(biāo)識(shí)。這類(lèi)端口號(hào)有一個(gè)特殊的名字,叫做?周知端口號(hào)(Well-Known Port Number)
。
時(shí)序分配的端口號(hào)
第二種分配端口號(hào)的方式是一種動(dòng)態(tài)分配法,在這種方法下,客戶(hù)端應(yīng)用程序可以完全不用自己設(shè)置端口號(hào),憑借操作系統(tǒng)進(jìn)行分配,操作系統(tǒng)可以為每個(gè)應(yīng)用程序分配互不沖突的端口號(hào)。這種動(dòng)態(tài)分配端口號(hào)的機(jī)制即使是同一個(gè)客戶(hù)端發(fā)起的 TCP 連接,也能識(shí)別不同的連接。
多路復(fù)用和多路分解
我們上面聊到了在主機(jī)上的每個(gè)套接字都會(huì)分配一個(gè)端口號(hào),當(dāng)報(bào)文段到達(dá)主機(jī)時(shí),運(yùn)輸層會(huì)檢查報(bào)文段中的目的端口號(hào),并將其定向到相應(yīng)的套接字,然后報(bào)文段中的數(shù)據(jù)通過(guò)套接字進(jìn)入其所連接的進(jìn)程。下面我們來(lái)聊一下什么是多路復(fù)用和多路分解的概念。
多路復(fù)用和多路分解分為兩種,即無(wú)連接
的多路復(fù)用(多路分解)和面向連接
的多路復(fù)用(多路分解)
無(wú)連接的多路復(fù)用和多路分解
開(kāi)發(fā)人員會(huì)編寫(xiě)代碼確定端口號(hào)是周知端口號(hào)還是時(shí)序分配的端口號(hào)。假如主機(jī) A 中的一個(gè) 10637 端口要向主機(jī) B 中的 45438 端口發(fā)送數(shù)據(jù),運(yùn)輸層采用的是?UDP
?協(xié)議,數(shù)據(jù)在應(yīng)用層產(chǎn)生后,會(huì)在運(yùn)輸層中加工處理,然后在網(wǎng)絡(luò)層將數(shù)據(jù)封裝得到 IP 數(shù)據(jù)報(bào),IP 數(shù)據(jù)包通過(guò)鏈路層盡力而為的交付給主機(jī) B,然后主機(jī) B 會(huì)檢查報(bào)文段中的端口號(hào)判斷是哪個(gè)套接字的,這一系列的過(guò)程如下所示
UDP 套接字就是一個(gè)二元組,二元組包含目的 IP 地址和目的端口號(hào)。
所以,如果兩個(gè) UDP 報(bào)文段有不同的源 IP 地址和/或相同的源端口號(hào),但是具有相同的目的 IP 地址和目的端口號(hào),那么這兩個(gè)報(bào)文會(huì)通過(guò)套接字定位到相同的目的進(jìn)程。
這里思考一個(gè)問(wèn)題,主機(jī) A 給主機(jī) B 發(fā)送一個(gè)消息,為什么還需要知道源端口號(hào)呢?比如我給妹子表達(dá)出我對(duì)你有點(diǎn)意思的信息,妹子還需要知道這個(gè)信息是從我的哪個(gè)器官發(fā)出的嗎?知道是我這個(gè)人對(duì)你有點(diǎn)意思不就完了?實(shí)際上是需要的,因?yàn)槊米尤绻磉_(dá)出她對(duì)你也有點(diǎn)意思,她是不是可能會(huì)親你一口,那她得知道往哪親吧?
這就是,在 A 到 B 的報(bào)文段中,源端口號(hào)會(huì)作為?返回地址
?的一部分,即當(dāng) B 需要回發(fā)一個(gè)報(bào)文段給 A 時(shí),B 需要從 A 到 B 中的源端口號(hào)取值,如下圖所示
面向連接的多路復(fù)用與多路分解
如果說(shuō)無(wú)連接的多路復(fù)用和多路分解指的是 UDP 的話(huà),那么面向連接的多路復(fù)用與多路分解指的是 TCP 了,TCP 和 UDP 在報(bào)文結(jié)構(gòu)上的差別是,UDP 是一個(gè)二元組而 TCP 是一個(gè)四元組,即源 IP 地址、目標(biāo) IP 地址、源端口號(hào)、目標(biāo)端口號(hào)?,這個(gè)我們上面也提到了。當(dāng)一個(gè) TCP 報(bào)文段從網(wǎng)絡(luò)到達(dá)一臺(tái)主機(jī)時(shí),這個(gè)主機(jī)會(huì)根據(jù)這四個(gè)值拆解到對(duì)應(yīng)的套接字上。
上圖顯示了面向連接的多路復(fù)用和多路分解的過(guò)程,圖中主機(jī) C 向主機(jī) B 發(fā)起了兩個(gè) HTTP 請(qǐng)求,主機(jī) A 向主機(jī) C 發(fā)起了一個(gè) HTTP 請(qǐng)求,主機(jī) A、B、C 都有自己唯一的 IP 地址,當(dāng)主機(jī) C 發(fā)出 HTTP 請(qǐng)求后,主機(jī) B 能夠分解這兩個(gè) HTTP 連接,因?yàn)橹鳈C(jī) C 發(fā)出請(qǐng)求的兩個(gè)源端口號(hào)不同,所以對(duì)于主機(jī) B 來(lái)說(shuō),這是兩條請(qǐng)求,主機(jī) B 能夠進(jìn)行分解。對(duì)于主機(jī) A 和主機(jī) C 來(lái)說(shuō),這兩個(gè)主機(jī)有不同的 IP 地址,所以對(duì)于主機(jī) B 來(lái)說(shuō),也能夠進(jìn)行分解。
UDP
終于,我們開(kāi)始了對(duì) UDP 協(xié)議的探討,淦起!
UDP 的全稱(chēng)是?用戶(hù)數(shù)據(jù)報(bào)協(xié)議(UDP,User Datagram Protocol)
,UDP 為應(yīng)用程序提供了一種無(wú)需建立連接
就可以發(fā)送封裝的 IP 數(shù)據(jù)包的方法。如果應(yīng)用程序開(kāi)發(fā)人員選擇的是 UDP 而不是 TCP 的話(huà),那么該應(yīng)用程序相當(dāng)于就是和 IP 直接打交道的。
從應(yīng)用程序傳遞過(guò)來(lái)的數(shù)據(jù),會(huì)附加上多路復(fù)用/多路分解的源和目的端口號(hào)字段,以及其他字段,然后將形成的報(bào)文傳遞給網(wǎng)絡(luò)層,網(wǎng)絡(luò)層將運(yùn)輸層報(bào)文段封裝到 IP 數(shù)據(jù)報(bào)中,然后盡力而為的交付給目標(biāo)主機(jī)。最關(guān)鍵的一點(diǎn)就是,使用 UDP 協(xié)議在將數(shù)據(jù)報(bào)傳遞給目標(biāo)主機(jī)時(shí),發(fā)送方和接收方的運(yùn)輸層實(shí)體間是沒(méi)有握手
的。正因?yàn)槿绱?,UDP 被稱(chēng)為是無(wú)連接
的協(xié)議。
UDP 特點(diǎn)
UDP 協(xié)議一般作為流媒體應(yīng)用、語(yǔ)音交流、視頻會(huì)議所使用的傳輸層協(xié)議,我們大家都知道的 DNS 協(xié)議底層也使用了 UDP 協(xié)議,這些應(yīng)用或協(xié)議之所以選擇 UDP 主要是因?yàn)橐韵逻@幾點(diǎn)
速度快
,采用 UDP 協(xié)議時(shí),只要應(yīng)用進(jìn)程將數(shù)據(jù)傳給 UDP,UDP 就會(huì)將此數(shù)據(jù)打包進(jìn) UDP 報(bào)文段并立刻傳遞給網(wǎng)絡(luò)層,然后 TCP 有擁塞控制的功能,它會(huì)在發(fā)送前判斷互聯(lián)網(wǎng)的擁堵情況,如果互聯(lián)網(wǎng)極度阻塞,那么就會(huì)抑制 TCP 的發(fā)送方。使用 UDP 的目的就是希望實(shí)時(shí)性。無(wú)須建立連接
,TCP 在數(shù)據(jù)傳輸之前需要經(jīng)過(guò)三次握手的操作,而 UDP 則無(wú)須任何準(zhǔn)備即可進(jìn)行數(shù)據(jù)傳輸。因此 UDP 沒(méi)有建立連接的時(shí)延。如果使用 TCP 和 UDP 來(lái)比喻開(kāi)發(fā)人員:TCP 就是那種凡事都要設(shè)計(jì)好,沒(méi)設(shè)計(jì)不會(huì)進(jìn)行開(kāi)發(fā)的工程師,需要把一切因素考慮在內(nèi)后再開(kāi)干!所以非常靠譜
;而 UDP 就是那種上來(lái)直接干干干,接到項(xiàng)目需求馬上就開(kāi)干,也不管設(shè)計(jì),也不管技術(shù)選型,就是干,這種開(kāi)發(fā)人員非常不靠譜
,但是適合快速迭代開(kāi)發(fā),因?yàn)榭梢择R上上手!無(wú)連接狀態(tài)
,TCP 需要在端系統(tǒng)中維護(hù)連接狀態(tài)
,連接狀態(tài)包括接收和發(fā)送緩存、擁塞控制參數(shù)以及序號(hào)和確認(rèn)號(hào)的參數(shù),在 UDP 中沒(méi)有這些參數(shù),也沒(méi)有發(fā)送緩存和接受緩存。因此,某些專(zhuān)門(mén)用于某種特定應(yīng)用的服務(wù)器當(dāng)應(yīng)用程序運(yùn)行在 UDP 上,一般能支持更多的活躍用戶(hù)分組首部開(kāi)銷(xiāo)小
,每個(gè) TCP 報(bào)文段都有 20 字節(jié)的首部開(kāi)銷(xiāo),而 UDP 僅僅只有 8 字節(jié)的開(kāi)銷(xiāo)。
這里需要注意一點(diǎn),并不是所有使用 UDP 協(xié)議的應(yīng)用層都是
不可靠
的,應(yīng)用程序可以自己實(shí)現(xiàn)可靠的數(shù)據(jù)傳輸,通過(guò)增加確認(rèn)和重傳機(jī)制。所以使用 UDP 協(xié)議最大的特點(diǎn)就是速度快。
UDP 報(bào)文結(jié)構(gòu)
下面來(lái)一起看一下 UDP 的報(bào)文結(jié)構(gòu),每個(gè) UDP 報(bào)文分為 UDP 報(bào)頭和 UDP 數(shù)據(jù)區(qū)兩部分。報(bào)頭由 4 個(gè) 16 位長(zhǎng)(2 字節(jié))字段組成,分別說(shuō)明該報(bào)文的源端口、目的端口、報(bào)文長(zhǎng)度和校驗(yàn)值。
源端口號(hào)(Source Port)
?:這個(gè)字段占據(jù) UDP 報(bào)文頭的前 16 位,通常包含發(fā)送數(shù)據(jù)報(bào)的應(yīng)用程序所使用的 UDP 端口。接收端的應(yīng)用程序利用這個(gè)字段的值作為發(fā)送響應(yīng)的目的地址。這個(gè)字段是可選項(xiàng),有時(shí)不會(huì)設(shè)置源端口號(hào)。沒(méi)有源端口號(hào)就默認(rèn)為 0 ,通常用于不需要返回消息的通信中。目標(biāo)端口號(hào)(Destination Port)
: 表示接收端端口,字段長(zhǎng)為 16 位長(zhǎng)度(Length)
: 該字段占據(jù) 16 位,表示 UDP 數(shù)據(jù)報(bào)長(zhǎng)度,包含 UDP 報(bào)文頭和 UDP 數(shù)據(jù)長(zhǎng)度。因?yàn)?UDP 報(bào)文頭長(zhǎng)度是 8 個(gè)字節(jié),所以這個(gè)值最小為 8,最大長(zhǎng)度為 65535 字節(jié)。校驗(yàn)和(Checksum)
:UDP 使用校驗(yàn)和來(lái)保證數(shù)據(jù)安全性,UDP 的校驗(yàn)和也提供了差錯(cuò)檢測(cè)功能,差錯(cuò)檢測(cè)用于校驗(yàn)報(bào)文段從源到目標(biāo)主機(jī)的過(guò)程中,數(shù)據(jù)的完整性是否發(fā)生了改變。發(fā)送方的 UDP 對(duì)報(bào)文段中的 16 比特字的和進(jìn)行反碼運(yùn)算,求和時(shí)遇到的位溢出都會(huì)被忽略,比如下面這個(gè)例子,三個(gè) 16 比特的數(shù)字進(jìn)行相加
這些 16 比特的前兩個(gè)和是
然后再將上面的結(jié)果和第三個(gè) 16 比特的數(shù)進(jìn)行相加
最后一次相加的位會(huì)進(jìn)行溢出,溢出位 1 要被舍棄,然后進(jìn)行反碼運(yùn)算,反碼運(yùn)算就是將所有的 1 變?yōu)?0 ,0 變?yōu)?1。因此?1000 0100 1001 0101?的反碼就是?0111 1011 0110 1010,這就是校驗(yàn)和,如果在接收方,數(shù)據(jù)沒(méi)有出現(xiàn)差錯(cuò),那么全部的 4 個(gè) 16 比特的數(shù)值進(jìn)行運(yùn)算,同時(shí)也包括校驗(yàn)和,如果最后結(jié)果的值不是 1111 1111 1111 1111 的話(huà),那么就表示傳輸過(guò)程中的數(shù)據(jù)出現(xiàn)了差錯(cuò)。
下面來(lái)想一個(gè)問(wèn)題,為什么 UDP 會(huì)提供差錯(cuò)檢測(cè)的功能?
這其實(shí)是一種?端到端
?的設(shè)計(jì)原則,這個(gè)原則說(shuō)的是要讓傳輸中各種錯(cuò)誤發(fā)生的概率降低到一個(gè)可以接受的水平。
文件從主機(jī)A傳到主機(jī)B,也就是說(shuō)AB主機(jī)要通信,需要經(jīng)過(guò)三個(gè)環(huán)節(jié):首先是主機(jī)A從磁盤(pán)上讀取文件并將數(shù)據(jù)分組成一個(gè)個(gè)數(shù)據(jù)包packet,,然后數(shù)據(jù)包通過(guò)連接主機(jī)A和主機(jī)B的網(wǎng)絡(luò)傳輸?shù)街鳈C(jī)B,最后是主機(jī)B收到數(shù)據(jù)包并將數(shù)據(jù)包寫(xiě)入磁盤(pán)。在這個(gè)看似簡(jiǎn)單其實(shí)很復(fù)雜的過(guò)程中可能會(huì)由于某些原因而影響正常通信。比如:磁盤(pán)上文件讀寫(xiě)錯(cuò)誤、緩沖溢出、內(nèi)存出錯(cuò)、網(wǎng)絡(luò)擁擠等等這些因素都有可能導(dǎo)致數(shù)據(jù)包的出錯(cuò)或者丟失,由此可見(jiàn)用于通信的網(wǎng)絡(luò)是不可靠的。
由于實(shí)現(xiàn)通信只要經(jīng)過(guò)上述三個(gè)環(huán)節(jié),那么我們就想是否在其中某個(gè)環(huán)節(jié)上增加一個(gè)檢錯(cuò)糾錯(cuò)機(jī)制來(lái)用于對(duì)信息進(jìn)行把關(guān)呢?
網(wǎng)絡(luò)層肯定不能做這件事,因?yàn)榫W(wǎng)絡(luò)層的最主要目的是增大數(shù)據(jù)傳輸?shù)乃俾?,網(wǎng)絡(luò)層不需要考慮數(shù)據(jù)的完整性,數(shù)據(jù)的完整性和正確性交給端系統(tǒng)去檢測(cè)就行了,因此在數(shù)據(jù)傳輸中,對(duì)于網(wǎng)絡(luò)層只能要求其提供盡可能好的數(shù)據(jù)傳輸服務(wù),而不可能寄希望于網(wǎng)絡(luò)層提供數(shù)據(jù)完整性的服務(wù)。
UDP 不可靠的原因是它雖然提供差錯(cuò)檢測(cè)的功能,但是對(duì)于差錯(cuò)沒(méi)有恢復(fù)能力更不會(huì)有重傳機(jī)制。
TCP
UDP 是一種沒(méi)有復(fù)雜的控制,提供無(wú)連接通信服務(wù)的一種協(xié)議,換句話(huà)說(shuō),它將部分控制部分交給應(yīng)用程序去處理,自己只提供作為傳輸層協(xié)議最基本的功能。
而與 UDP 不同的是,同樣作為傳輸層協(xié)議,TCP 協(xié)議要比 UDP 的功能多很多。
TCP
?的全稱(chēng)是?Transmission Control Protocol
,它被稱(chēng)為是一種面向連接(connection-oriented)
?的協(xié)議,這是因?yàn)橐粋€(gè)應(yīng)用程序開(kāi)始向另一個(gè)應(yīng)用程序發(fā)送數(shù)據(jù)之前,這兩個(gè)進(jìn)程必須先進(jìn)行握手
,握手是一個(gè)邏輯連接,并不是兩個(gè)主機(jī)之間進(jìn)行真實(shí)的握手。
這個(gè)連接是指各種設(shè)備、線(xiàn)路或者網(wǎng)絡(luò)中進(jìn)行通信的兩個(gè)應(yīng)用程序?yàn)榱讼嗷鬟f消息而專(zhuān)有的、虛擬的通信鏈路,也叫做虛擬電路。
一旦主機(jī) A 和主機(jī) B 建立了連接,那么進(jìn)行通信的應(yīng)用程序只使用這個(gè)虛擬的通信線(xiàn)路發(fā)送和接收數(shù)據(jù)就可以保證數(shù)據(jù)的傳輸,TCP 協(xié)議負(fù)責(zé)控制連接的建立、斷開(kāi)、保持等工作。
TCP 連接是全雙工服務(wù)(full-duplex service)
?的,全雙工是什么意思?全雙工指的是主機(jī) A 與另外一個(gè)主機(jī) B 存在一條 TCP 連接,那么應(yīng)用程數(shù)據(jù)就可以從主機(jī) B 流向主機(jī) A 的同時(shí),也從主機(jī) A 流向主機(jī) B。
TCP 只能進(jìn)行?點(diǎn)對(duì)點(diǎn)(point-to-point)
?連接,那么所謂的多播
,即一個(gè)主機(jī)對(duì)多個(gè)接收方發(fā)送消息的情況是不存在的,TCP 連接只能連接兩個(gè)一對(duì)主機(jī)。
TCP 的連接建立需要經(jīng)過(guò)三次握手,這個(gè)我們下面再說(shuō)。一旦 TCP 連接建立后,主機(jī)之間就可以相互發(fā)送數(shù)據(jù)了,客戶(hù)進(jìn)程通過(guò)套接字傳送數(shù)據(jù)流。數(shù)據(jù)一旦通過(guò)套接字后,它就由客戶(hù)中運(yùn)行的 TCP 協(xié)議所控制。
TCP 會(huì)將數(shù)據(jù)臨時(shí)存儲(chǔ)到連接的發(fā)送緩存(send buffer)
?中,這個(gè) send buffer 是三次握手之間設(shè)置的緩存之一,然后 TCP 在合適的時(shí)間將發(fā)送緩存中的數(shù)據(jù)發(fā)送到目標(biāo)主機(jī)的接收緩存中,實(shí)際上,每一端都會(huì)有發(fā)送緩存和接收緩存,如下所示
主機(jī)之間的發(fā)送是以?報(bào)文段(segment)
?進(jìn)行的,那么什么是 Segement 呢?
TCP 會(huì)將要傳輸?shù)臄?shù)據(jù)流分為多個(gè)塊(chunk)
,然后向每個(gè) chunk 中添加 TCP 標(biāo)頭,這樣就形成了一個(gè) TCP 段也就是報(bào)文段。每一個(gè)報(bào)文段可以傳輸?shù)拈L(zhǎng)度是有限的,不能超過(guò)最大數(shù)據(jù)長(zhǎng)度(Maximum Segment Size)
,俗稱(chēng)?MSS
。在報(bào)文段向下傳輸?shù)倪^(guò)程中,會(huì)經(jīng)過(guò)鏈路層,鏈路層有一個(gè)?Maximum Transmission Unit
?,最大傳輸單元 MTU, 即數(shù)據(jù)鏈路層上所能通過(guò)最大數(shù)據(jù)包的大小,最大傳輸單元通常與通信接口有關(guān)。
那么 MSS 和 MTU 有啥關(guān)系呢?
因?yàn)橛?jì)算機(jī)網(wǎng)絡(luò)是分層考慮的,這個(gè)很重要,不同層的稱(chēng)呼不一樣,對(duì)于傳輸層來(lái)說(shuō),稱(chēng)為報(bào)文段而對(duì)網(wǎng)絡(luò)層來(lái)說(shuō)就叫做 IP 數(shù)據(jù)包,所以,MTU 可以認(rèn)為是網(wǎng)絡(luò)層能夠傳輸?shù)淖畲?IP 數(shù)據(jù)包,而 MSS(Maximum segment size)可以認(rèn)為是傳輸層的概念,也就是 TCP 數(shù)據(jù)包每次能夠傳輸?shù)淖畲罅?/strong>。
TCP 報(bào)文段結(jié)構(gòu)
在簡(jiǎn)單聊了聊 TCP 連接后,下面我們就來(lái)聊一下 TCP 的報(bào)文段結(jié)構(gòu),如下圖所示
TCP 報(bào)文段結(jié)構(gòu)相比 UDP 報(bào)文結(jié)構(gòu)多了很多內(nèi)容。但是前兩個(gè) 32 比特的字段是一樣的。它們是?源端口號(hào)
?和?目標(biāo)端口號(hào)
,我們知道,這兩個(gè)字段是用于多路復(fù)用和多路分解的。另外,和 UDP 一樣,TCP 也包含校驗(yàn)和(checksum field)
?,除此之外,TCP 報(bào)文段首部還有下面這些
32 比特的
序號(hào)字段(sequence number field)
?和 32 比特的確認(rèn)號(hào)字段(acknowledgment number field)
?。這些字段被 TCP 發(fā)送方和接收方用來(lái)實(shí)現(xiàn)可靠的數(shù)據(jù)傳輸。4 比特的
首部字段長(zhǎng)度字段(header length field)
,這個(gè)字段指示了以 32 比特的字為單位的 TCP 首部長(zhǎng)度。TCP 首部的長(zhǎng)度是可變的,但是通常情況下,選項(xiàng)字段為空,所以 TCP 首部字段的長(zhǎng)度是 20 字節(jié)。16 比特的?
接受窗口字段(receive window field)
?,這個(gè)字段用于流量控制。它用于指示接收方能夠/愿意接受的字節(jié)數(shù)量可變的
選項(xiàng)字段(options field)
,這個(gè)字段用于發(fā)送方和接收方協(xié)商最大報(bào)文長(zhǎng)度,也就是 MSS 時(shí)使用6 比特的?
標(biāo)志字段(flag field)
,?ACK
?標(biāo)志用于指示確認(rèn)字段中的值是有效的,這個(gè)報(bào)文段包括一個(gè)對(duì)已被成功接收?qǐng)?bào)文段的確認(rèn);RST
、SYN
、FIN
?標(biāo)志用于連接的建立和關(guān)閉;CWR
?和?ECE
?用于擁塞控制;PSH
?標(biāo)志用于表示立刻將數(shù)據(jù)交給上層處理;URG
?標(biāo)志用來(lái)表示數(shù)據(jù)中存在需要被上層處理的?緊急?數(shù)據(jù)。緊急數(shù)據(jù)最后一個(gè)字節(jié)由 16 比特的緊急數(shù)據(jù)指針字段(urgeent data pointer field)
?指出。一般情況下,PSH 和 URG 并沒(méi)有使用。
TCP 的各種功能和特點(diǎn)都是通過(guò) TCP 報(bào)文結(jié)構(gòu)來(lái)體現(xiàn)的,在聊完 TCP 報(bào)文結(jié)構(gòu)之后,我們下面就來(lái)聊一下 TCP 有哪些功能及其特點(diǎn)了。
序號(hào)、確認(rèn)號(hào)實(shí)現(xiàn)傳輸可靠性
TCP 報(bào)文段首部中兩個(gè)最重要的字段就是?序號(hào)
?和?確認(rèn)號(hào)
,這兩個(gè)字段是 TCP 實(shí)現(xiàn)可靠性的基礎(chǔ),那么你肯定好奇如何實(shí)現(xiàn)可靠性呢?要了解這一點(diǎn),首先我們得先知道這兩個(gè)字段里面存了哪些內(nèi)容吧?
一個(gè)報(bào)文段的序號(hào)就是數(shù)據(jù)流的字節(jié)編號(hào)?。因?yàn)?TCP 會(huì)把數(shù)據(jù)流分割成為一段一段的字節(jié)流,因?yàn)樽止?jié)流本身是有序的,所以每一段的字節(jié)編號(hào)就是標(biāo)示是哪一段的字節(jié)流。比如,主機(jī) A 要給主機(jī) B 發(fā)送一條數(shù)據(jù)。數(shù)據(jù)經(jīng)過(guò)應(yīng)用層產(chǎn)生后會(huì)有一串?dāng)?shù)據(jù)流,數(shù)據(jù)流會(huì)經(jīng)過(guò) TCP 分割,分割的依據(jù)就是 MSS,假設(shè)數(shù)據(jù)是 10000 字節(jié),MSS 是 2000 字節(jié),那么 TCP 就會(huì)把數(shù)據(jù)拆分成 0 - 1999 , 2000 - 3999 的段,依次類(lèi)推。
所以,第一個(gè)數(shù)據(jù) 0 - 1999 的首字節(jié)編號(hào)就是 0 ,2000 - 3999 的首字節(jié)編號(hào)就是 2000 。
然后,每個(gè)序號(hào)都會(huì)被填入 TCP 報(bào)文段首部的序號(hào)字段中。
至于確認(rèn)號(hào)的話(huà),會(huì)比序號(hào)要稍微麻煩一些。這里我們先拓展下幾種通信模型。
單工通信:?jiǎn)喂?shù)據(jù)傳輸只支持?jǐn)?shù)據(jù)在一個(gè)方向上傳輸;在同一時(shí)間只有一方能接受或發(fā)送信息,不能實(shí)現(xiàn)雙向通信,比如廣播、電視等。
雙工通信是一種點(diǎn)對(duì)點(diǎn)系統(tǒng),由兩個(gè)或者多個(gè)在兩個(gè)方向上相互通信的連接方或者設(shè)備組成。雙工通信模型有兩種:全雙工(FDX)和半雙工(HDX)
全雙工:在全雙工系統(tǒng)中,連接雙方可以相互通信,一個(gè)最常見(jiàn)的例子就是電話(huà)通信。全雙工通信是兩個(gè)單工通信方式的結(jié)合,它要求發(fā)送設(shè)備和接收設(shè)備都有獨(dú)立的接收和發(fā)送能力。
半雙工:在半雙工系統(tǒng)中,連接雙方可以彼此通信,但不能同時(shí)通信,比如對(duì)講機(jī),只有把按鈕按住的人才能夠講話(huà),只有一個(gè)人講完話(huà)后另外一個(gè)人才能講話(huà)。
單工、半雙工、全雙工通信如下圖所示
TCP 是一種全雙工的通信協(xié)議,因此主機(jī) A 在向主機(jī) B 發(fā)送消息的過(guò)程中,也在接受來(lái)自主機(jī) B 的數(shù)據(jù)。主機(jī) A 填充進(jìn)報(bào)文段的確認(rèn)號(hào)是期望從主機(jī) B 收到的下一字節(jié)的序號(hào)。稍微有點(diǎn)繞,我們來(lái)舉個(gè)例子看一下。比如主機(jī) A 收到了來(lái)自主機(jī) B 發(fā)送的編號(hào)為 0 - 999 字節(jié)的報(bào)文段,這個(gè)報(bào)文段會(huì)寫(xiě)入序號(hào)中,隨后主機(jī) A 期望能夠從主機(jī) B 收到 1000 - 剩下的報(bào)文段,因此,主機(jī) A 發(fā)送到主機(jī) B 的報(bào)文段中,它的確認(rèn)號(hào)就是 1000 。
累積確認(rèn)
這里再舉出一個(gè)例子,比如主機(jī) A 在發(fā)送 0 - 999 報(bào)文段后,期望能夠接受到 1000 之后的報(bào)文段,但是主機(jī) B 卻給主機(jī) A 發(fā)送了一個(gè) 1500 之后的報(bào)文段,那么主機(jī) A 是否還會(huì)繼續(xù)進(jìn)行等待呢?
答案顯然是會(huì)的,因?yàn)?TCP 只會(huì)確認(rèn)流中至第一個(gè)丟失字節(jié)為止的字節(jié),因?yàn)?1500 雖然屬于 1000 之后的字節(jié),但是主機(jī) B 沒(méi)有給主機(jī) A 發(fā)送 1000 - 1499 之間的字節(jié),所以主機(jī) A 會(huì)繼續(xù)等待。
在了解完序號(hào)和確認(rèn)號(hào)之后,我們下面來(lái)聊一下 TCP 的發(fā)送過(guò)程。下面是一個(gè)正常的發(fā)送過(guò)程
TCP 通過(guò)肯定的確認(rèn)應(yīng)答(ACK)
?來(lái)實(shí)現(xiàn)可靠的數(shù)據(jù)傳輸,當(dāng)主機(jī) A將數(shù)據(jù)發(fā)出之后會(huì)等待主機(jī) B 的響應(yīng)。如果有確認(rèn)應(yīng)答(ACK),說(shuō)明數(shù)據(jù)已經(jīng)成功到達(dá)對(duì)端。反之,則數(shù)據(jù)很可能會(huì)丟失。
如下圖所示,如果在一定時(shí)間內(nèi)主機(jī) A 沒(méi)有等到確認(rèn)應(yīng)答,則認(rèn)為主機(jī) B 發(fā)送的報(bào)文段已經(jīng)丟失,并進(jìn)行重發(fā)。
主機(jī) A 給主機(jī) B 的響應(yīng)可能由于網(wǎng)絡(luò)抖動(dòng)等原因無(wú)法到達(dá),那么在經(jīng)過(guò)特定的時(shí)間間隔后,主機(jī) A 將重新發(fā)送報(bào)文段。
主機(jī) A 沒(méi)有收到主機(jī) B 的響應(yīng)還可能是因?yàn)橹鳈C(jī) B 在發(fā)送給主機(jī) A 的過(guò)程中丟失。
如上圖所示,由主機(jī) B 返回的確認(rèn)應(yīng)答,由于網(wǎng)絡(luò)擁堵等原因在傳送的過(guò)程中丟失,并沒(méi)有到達(dá)主機(jī) A。主機(jī) A 會(huì)等待一段時(shí)間,如果在這段時(shí)間內(nèi)主機(jī) A 仍沒(méi)有等到主機(jī) B 的響應(yīng),那么主機(jī) A 會(huì)重新發(fā)送報(bào)文段。
那么現(xiàn)在就存在一個(gè)問(wèn)題,如果主機(jī) A 給主機(jī) B 發(fā)送了一個(gè)報(bào)文段后,主機(jī) B 接受到報(bào)文段發(fā)送響應(yīng),此刻由于網(wǎng)絡(luò)原因,這個(gè)報(bào)文段并未到達(dá),等到一段時(shí)間后主機(jī) A 重新發(fā)送報(bào)文段,然后此時(shí)主機(jī) B 發(fā)送的響應(yīng)在主機(jī) A 第二次發(fā)送后失序到達(dá)主機(jī) A,那么主機(jī) A 應(yīng)該如何處理呢?
TCP RFC 并未為此做任何規(guī)定,也就是說(shuō),我們可以自己決定如何處理失序到達(dá)的報(bào)文段。一般處理方式有兩種
接收方立刻丟棄失序的報(bào)文段
接收方接受失序到達(dá)的報(bào)文段,并等待后續(xù)的報(bào)文段
一般來(lái)說(shuō)通常采取的做法是第二種。
傳輸控制
利用窗口控制提高速度
前面我們介紹了 TCP 是以數(shù)據(jù)段的形式進(jìn)行發(fā)送,如果經(jīng)過(guò)一段時(shí)間內(nèi)主機(jī) A 等不到主機(jī) B 的響應(yīng),主機(jī) A 就會(huì)重新發(fā)送報(bào)文段,接受到主機(jī) B 的響應(yīng),再會(huì)繼續(xù)發(fā)送后面的報(bào)文段,我們現(xiàn)在看到,這一問(wèn)一答的形式還存在許多條件,比如響應(yīng)未收到、等待響應(yīng)等,那么對(duì)崇尚性能的互聯(lián)網(wǎng)來(lái)說(shuō),這種形式的性能應(yīng)該不會(huì)很高。
那么如何提升性能呢?
為了解決這個(gè)問(wèn)題,TCP 引入了?窗口
?這個(gè)概念,即使在往返時(shí)間較長(zhǎng)、頻次很多的情況下,它也能控制網(wǎng)絡(luò)性能的下降,聽(tīng)起來(lái)很牛批,那它是如何實(shí)現(xiàn)的呢?
如下圖所示
我們之前每次請(qǐng)求發(fā)送都是以報(bào)文段的形式進(jìn)行的,引入窗口后,每次請(qǐng)求都可以發(fā)送多個(gè)報(bào)文段,也就是說(shuō)一個(gè)窗口可以發(fā)送多個(gè)報(bào)文段。窗口大小就是指無(wú)需等待確認(rèn)應(yīng)答就可以繼續(xù)發(fā)送報(bào)文段的最大值。
在這個(gè)窗口機(jī)制中,大量使用了?緩沖區(qū)
?,通過(guò)對(duì)多個(gè)段同時(shí)進(jìn)行確認(rèn)應(yīng)答的功能。
如下圖所示,發(fā)送報(bào)文段中高亮部分即是我們提到的窗口,在窗口內(nèi),即使沒(méi)有收到確認(rèn)應(yīng)答也可以把請(qǐng)求發(fā)送出去。不過(guò),在整個(gè)窗口的確認(rèn)應(yīng)答沒(méi)有到達(dá)之前,如果部分報(bào)文段丟失,那么主機(jī) A 將仍會(huì)重傳。為此,主機(jī) A 需要設(shè)置緩存來(lái)保留這些需要重傳的報(bào)文段,直到收到他們的確認(rèn)應(yīng)答。
在滑動(dòng)窗口以外的部分是尚未發(fā)送的報(bào)文段和已經(jīng)接受到的報(bào)文段,如果報(bào)文段已經(jīng)收到確認(rèn)則不可進(jìn)行重發(fā),此時(shí)報(bào)文段就可以從緩沖區(qū)中清除。
在收到確認(rèn)的情況下,會(huì)將窗口滑動(dòng)到確認(rèn)應(yīng)答中確認(rèn)號(hào)的位置,如上圖所示,這樣可以順序的將多個(gè)段同時(shí)發(fā)送,用以提高通信性能,這種窗口也叫做?滑動(dòng)窗口(Sliding window)
。
窗口控制和重發(fā)
報(bào)文段的發(fā)送和接收,必然伴隨著報(bào)文段的丟失和重發(fā),窗口也是同樣如此,如果在窗口中報(bào)文段發(fā)送過(guò)程中出現(xiàn)丟失怎么辦?
首先我們先考慮確認(rèn)應(yīng)答沒(méi)有返回的情況。在這種情況下,主機(jī) A 發(fā)送的報(bào)文段到達(dá)主機(jī) B,是不需要再進(jìn)行重發(fā)的。這和單個(gè)報(bào)文段的發(fā)送不一樣,如果發(fā)送單個(gè)報(bào)文段,即使確認(rèn)應(yīng)答沒(méi)有返回,也要進(jìn)行重發(fā)。
窗口在一定程度上比較大時(shí),即使有少部分確認(rèn)應(yīng)答的丟失,也不會(huì)重新發(fā)送報(bào)文段。
我們知道,如果在某個(gè)情況下由于發(fā)送的報(bào)文段丟失,導(dǎo)致接受主機(jī)未收到請(qǐng)求,或者主機(jī)返回的響應(yīng)未到達(dá)客戶(hù)端的話(huà),會(huì)經(jīng)過(guò)一段時(shí)間重傳報(bào)文。那么在使用窗口的情況下,報(bào)文段丟失會(huì)怎么樣呢?
如下圖所示,報(bào)文段 0 - 999 丟失后,但是主機(jī) A 并不會(huì)等待,主機(jī) A 會(huì)繼續(xù)發(fā)送余下的報(bào)文段,主機(jī) B 發(fā)送的確認(rèn)應(yīng)答卻一直是 1000,同一個(gè)確認(rèn)號(hào)的應(yīng)答報(bào)文會(huì)被持續(xù)不斷的返回,如果發(fā)送端主機(jī)在連續(xù) 3 次收到同一個(gè)確認(rèn)應(yīng)答后,就會(huì)將其所對(duì)應(yīng)的數(shù)據(jù)重發(fā),這種機(jī)制要比之前提到的超時(shí)重發(fā)更加高效,這種機(jī)制也被稱(chēng)為?高速重發(fā)控制
。這種重發(fā)的確認(rèn)應(yīng)答也被稱(chēng)為?冗余 ACK(響應(yīng))
。
主機(jī) B 在沒(méi)有接收到自己期望序列號(hào)的報(bào)文段時(shí),會(huì)對(duì)之前收到的數(shù)據(jù)進(jìn)行確認(rèn)應(yīng)答。發(fā)送端則一旦收到某個(gè)確認(rèn)應(yīng)答后,又連續(xù)三次收到同樣的確認(rèn)應(yīng)答,那么就會(huì)認(rèn)為報(bào)文段已經(jīng)丟失。需要進(jìn)行重發(fā)。使用這種機(jī)制可以提供更為快速的重發(fā)服務(wù)。
流量控制
前面聊的是傳輸控制,下面 cxuan 再和你聊一下?流量控制
。我們知道,在每個(gè) TCP 連接的一側(cè)主機(jī)都會(huì)有一個(gè) socket 緩沖區(qū),緩沖區(qū)會(huì)為每個(gè)連接設(shè)置接收緩存和發(fā)送緩存,當(dāng) TCP 建立連接后,從應(yīng)用程序產(chǎn)生的數(shù)據(jù)就會(huì)到達(dá)接收方的接收緩沖區(qū)中,接收方的應(yīng)用程序并不一定會(huì)馬上讀取緩沖區(qū)的數(shù)據(jù),它需要等待操作系統(tǒng)分配時(shí)間片。如果此時(shí)發(fā)送方的應(yīng)用程序產(chǎn)生數(shù)據(jù)過(guò)快,而接收方讀取接受緩沖區(qū)的數(shù)據(jù)相對(duì)較慢的話(huà),那么接收方中緩沖區(qū)的數(shù)據(jù)將會(huì)溢出
。
但是還好,TCP 有?流量控制服務(wù)(flow-control service)
?用于消除緩沖區(qū)溢出的情況。流量控制是一個(gè)速度匹配服務(wù),即發(fā)送方的發(fā)送速率與接受方應(yīng)用程序的讀取速率相匹配。
TCP 通過(guò)使用一個(gè)?接收窗口(receive window)
?的變量來(lái)提供流量控制。接受窗口會(huì)給發(fā)送方一個(gè)指示到底還有多少可用的緩存空間。發(fā)送端會(huì)根據(jù)接收端的實(shí)際接受能力來(lái)控制發(fā)送的數(shù)據(jù)量。
接收端主機(jī)向發(fā)送端主機(jī)通知自己可以接收數(shù)據(jù)的大小,發(fā)送端會(huì)發(fā)送不超過(guò)這個(gè)限度的數(shù)據(jù),這個(gè)大小限度就是窗口大小,還記得 TCP 的首部么,有一個(gè)接收窗口,我們上面聊的時(shí)候說(shuō)這個(gè)字段用于流量控制。它用于指示接收方能夠/愿意接受的字節(jié)數(shù)量。
那么只知道這個(gè)字段用于流量控制,那么如何控制呢?
發(fā)送端主機(jī)會(huì)定期發(fā)送一個(gè)窗口探測(cè)包
,這個(gè)包用于探測(cè)接收端主機(jī)是否還能夠接受數(shù)據(jù),當(dāng)接收端的緩沖區(qū)一旦面臨數(shù)據(jù)溢出的風(fēng)險(xiǎn)時(shí),窗口大小的值也隨之被設(shè)置為一個(gè)更小的值通知發(fā)送端,從而控制數(shù)據(jù)發(fā)送量。
下面是一個(gè)流量控制示意圖
發(fā)送端主機(jī)根據(jù)接收端主機(jī)的窗口大小進(jìn)行流量控制。由此也可以防止發(fā)送端主機(jī)一次發(fā)送過(guò)大數(shù)據(jù)導(dǎo)致接收端主機(jī)無(wú)法處理。
如上圖所示,當(dāng)主機(jī) B 收到報(bào)文段 2000 - 2999 之后緩沖區(qū)已滿(mǎn),不得不暫時(shí)停止接收數(shù)據(jù)。然后主機(jī) A 發(fā)送窗口探測(cè)包,窗口探測(cè)包非常小僅僅一個(gè)字節(jié)。然后主機(jī) B 更新緩沖區(qū)接收窗口大小并發(fā)送窗口更新通知給主機(jī) A,然后主機(jī) A 再繼續(xù)發(fā)送報(bào)文段。
在上面的發(fā)送過(guò)程中,窗口更新通知可能會(huì)丟失,一旦丟失發(fā)送端就不會(huì)發(fā)送數(shù)據(jù),所以窗口探測(cè)包會(huì)隨機(jī)發(fā)送,以避免這種情況發(fā)生。
連接管理
在繼續(xù)介紹下面有意思的特性之前,我們先來(lái)把關(guān)注點(diǎn)放在 TCP 的連接管理
上,因?yàn)闆](méi)有 TCP 連接,也就沒(méi)有后續(xù)的一系列 TCP 特性什么事兒了。假設(shè)運(yùn)行在一臺(tái)主機(jī)上的進(jìn)程想要和另一臺(tái)主機(jī)上的進(jìn)程建立一條 TCP 連接,那么客戶(hù)中的 TCP 會(huì)使用下面這些步驟與服務(wù)器中的 TCP 建立連接。
首先,客戶(hù)端首先向服務(wù)器發(fā)送一個(gè)特殊的 TCP 報(bào)文段。這個(gè)報(bào)文段首部不包含應(yīng)用層數(shù)據(jù),但是在報(bào)文段的首部中有一個(gè)?
SYN 標(biāo)志位
?被置為 1。因此,這個(gè)特殊的報(bào)文段也可以叫做 SYN 報(bào)文段。然后,客戶(hù)端隨機(jī)選擇一個(gè)初始序列號(hào)(client_isn)
?,并將此數(shù)字放入初始 TCP SYN 段的序列號(hào)字段中,SYN 段又被封裝在 IP 數(shù)據(jù)段中發(fā)送給服務(wù)器。一旦包含 IP 數(shù)據(jù)段到達(dá)服務(wù)器后,服務(wù)端會(huì)從 IP 數(shù)據(jù)段中提取 TCP SYN 段,將 TCP 緩沖區(qū)和變量分配給連接,然后給客戶(hù)端發(fā)送一個(gè)連接所允許的報(bào)文段。這個(gè)連接所允許的報(bào)文段也不包括任何應(yīng)用層數(shù)據(jù)。然而,它卻包含了三個(gè)非常重要的信息。
這些緩沖區(qū)和變量的分配使 TCP 容易受到稱(chēng)為 SYN 泛洪的拒絕服務(wù)攻擊。
首先,SYN 比特被置為 1 。
然后,TCP 報(bào)文段的首部確認(rèn)號(hào)被設(shè)置為?
client_isn + 1
。最后,服務(wù)器選擇自己的
初始序號(hào)(server_isn)
,并將其放置到 TCP 報(bào)文段首部的序號(hào)字段中。如果用大白話(huà)解釋下就是,我收到了你發(fā)起建立連接的 SYN 報(bào)文段,這個(gè)報(bào)文段具有首部字段 client_isn。我同意建立該連接,我自己的初始序號(hào)是 server_isn。這個(gè)允許連接的報(bào)文段被稱(chēng)為?
SYNACK 報(bào)文段
第三步,在收到 SYNACK 報(bào)文段后,客戶(hù)端也要為該連接分配緩沖區(qū)和變量。客戶(hù)端主機(jī)向服務(wù)器發(fā)送另外一個(gè)報(bào)文段,最后一個(gè)報(bào)文段對(duì)服務(wù)器發(fā)送的響應(yīng)報(bào)文做了確認(rèn),確認(rèn)的標(biāo)準(zhǔn)是客戶(hù)端發(fā)送的數(shù)據(jù)段中確認(rèn)號(hào)為 server_isn + 1,因?yàn)檫B接已經(jīng)建立,所以 SYN 比特被置為 0 。以上就是 TCP 建立連接的三次數(shù)據(jù)段發(fā)送過(guò)程,也被稱(chēng)為?
三次握手
。
一旦完成這三個(gè)步驟,客戶(hù)和服務(wù)器主機(jī)就可以相互發(fā)送報(bào)文段了,在以后的每一個(gè)報(bào)文段中,SYN 比特都被置為 0 ,整個(gè)過(guò)程描述如下圖所示
在客戶(hù)端主機(jī)和服務(wù)端主機(jī)建立連接后,參與一條 TCP 連接的兩個(gè)進(jìn)程中的任何一個(gè)都能終止 TCP 連接。連接結(jié)束后,主機(jī)中的緩存和變量將會(huì)被釋放。假設(shè)客戶(hù)端主機(jī)想要終止 TCP 連接,它會(huì)經(jīng)歷如下過(guò)程
客戶(hù)應(yīng)用進(jìn)程發(fā)出一個(gè)關(guān)閉命令,客戶(hù) TCP 向服務(wù)器進(jìn)程發(fā)送一個(gè)特殊的 TCP 報(bào)文段,這個(gè)特殊的報(bào)文段的首部標(biāo)志 FIN 被設(shè)置為 1 。當(dāng)服務(wù)器收到這個(gè)報(bào)文段后,就會(huì)向發(fā)送方發(fā)送一個(gè)確認(rèn)報(bào)文段。然后,服務(wù)器發(fā)送它自己的終止報(bào)文段,F(xiàn)IN 位被設(shè)置為 1 ??蛻?hù)端對(duì)這個(gè)終止報(bào)文段進(jìn)行確認(rèn)。此時(shí),在兩臺(tái)主機(jī)上用于該連接的所有資源都被釋放了,如下圖所示
在一個(gè) TCP 連接的生命周期內(nèi),運(yùn)行在每臺(tái)主機(jī)中的 TCP 協(xié)議都會(huì)在各種?TCP 狀態(tài)(TCP State)
?之間進(jìn)行變化,TCP 的狀態(tài)主要有?LISTEN、SYN-SEND、SYN-RECEIVED、ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT、CLOSING、LAST-ACK、TIME-WAIT 和 CLOSED?。這些狀態(tài)的解釋如下
LISTEN
: 表示等待任何來(lái)自遠(yuǎn)程 TCP 和端口的連接請(qǐng)求。SYN-SEND
: 表示發(fā)送連接請(qǐng)求后等待匹配的連接請(qǐng)求。SYN-RECEIVED
: 表示已接收并發(fā)送連接請(qǐng)求后等待連接確認(rèn),也就是 TCP 三次握手中第二步后服務(wù)端的狀態(tài)ESTABLISHED
: 表示已經(jīng)連接已經(jīng)建立,可以將應(yīng)用數(shù)據(jù)發(fā)送給其他主機(jī)
上面這四種狀態(tài)是 TCP 三次握手所涉及的。
FIN-WAIT-1
: 表示等待來(lái)自遠(yuǎn)程 TCP 的連接終止請(qǐng)求,或者等待先前發(fā)送的連接終止請(qǐng)求的確認(rèn)。FIN-WAIT-2
: 表示等待來(lái)自遠(yuǎn)程 TCP 的連接終止請(qǐng)求。CLOSE-WAIT
: 表示等待本地用戶(hù)的連接終止請(qǐng)求。CLOSING
: 表示等待來(lái)自遠(yuǎn)程 TCP 的連接終止請(qǐng)求確認(rèn)。LAST-ACK
: 表示等待先前發(fā)送給遠(yuǎn)程 TCP 的連接終止請(qǐng)求的確認(rèn)(包括對(duì)它的連接終止請(qǐng)求的確認(rèn))。TIME-WAIT
: 表示等待足夠的時(shí)間以確保遠(yuǎn)程 TCP 收到其連接終止請(qǐng)求的確認(rèn)。CLOSED
: 表示連接已經(jīng)關(guān)閉,無(wú)連接狀態(tài)。
上面 7 種狀態(tài)是 TCP 四次揮手,也就是斷開(kāi)鏈接所設(shè)計(jì)的。
TCP 的連接狀態(tài)會(huì)進(jìn)行各種切換,這些 TCP 連接的切換是根據(jù)事件進(jìn)行的,這些事件由用戶(hù)調(diào)用:OPEN、SEND、RECEIVE、CLOSE、ABORT 和 STATUS。涉及到 TCP 報(bào)文段的標(biāo)志有?SYN、ACK、RST 和 FIN?,當(dāng)然,還有超時(shí)。
我們下面加上 TCP 連接狀態(tài)后,再來(lái)看一下三次握手和四次揮手的過(guò)程。
三次握手建立連接
下圖畫(huà)出了 TCP 連接建立的過(guò)程。假設(shè)圖中左端是客戶(hù)端主機(jī),右端是服務(wù)端主機(jī),一開(kāi)始,兩端都處于CLOSED(關(guān)閉)
狀態(tài)。
服務(wù)端進(jìn)程準(zhǔn)備好接收來(lái)自外部的 TCP 連接,一般情況下是調(diào)用 bind、listen、socket 三個(gè)函數(shù)完成。這種打開(kāi)方式被認(rèn)為是?
被動(dòng)打開(kāi)(passive open)
。然后服務(wù)端進(jìn)程處于?LISTEN
?狀態(tài),等待客戶(hù)端連接請(qǐng)求。客戶(hù)端通過(guò)?
connect
?發(fā)起主動(dòng)打開(kāi)(active open)
,向服務(wù)器發(fā)出連接請(qǐng)求,請(qǐng)求中首部同步位 SYN = 1,同時(shí)選擇一個(gè)初始序號(hào) sequence ,簡(jiǎn)寫(xiě) seq = x。SYN 報(bào)文段不允許攜帶數(shù)據(jù),只消耗一個(gè)序號(hào)。此時(shí),客戶(hù)端進(jìn)入?SYN-SEND
?狀態(tài)。服務(wù)器收到客戶(hù)端連接后,,需要確認(rèn)客戶(hù)端的報(bào)文段。在確認(rèn)報(bào)文段中,把 SYN 和 ACK 位都置為 1 。確認(rèn)號(hào)是 ack = x + 1,同時(shí)也為自己選擇一個(gè)初始序號(hào) seq = y。請(qǐng)注意,這個(gè)報(bào)文段也不能攜帶數(shù)據(jù),但同樣要消耗掉一個(gè)序號(hào)。此時(shí),TCP 服務(wù)器進(jìn)入?
SYN-RECEIVED(同步收到)
?狀態(tài)。客戶(hù)端在收到服務(wù)器發(fā)出的響應(yīng)后,還需要給出確認(rèn)連接。確認(rèn)連接中的 ACK 置為 1 ,序號(hào)為 seq = x + 1,確認(rèn)號(hào)為 ack = y + 1。TCP 規(guī)定,這個(gè)報(bào)文段可以攜帶數(shù)據(jù)也可以不攜帶數(shù)據(jù),如果不攜帶數(shù)據(jù),那么下一個(gè)數(shù)據(jù)報(bào)文段的序號(hào)仍是 seq = x + 1。這時(shí),客戶(hù)端進(jìn)入?
ESTABLISHED (已連接)
?狀態(tài)服務(wù)器收到客戶(hù)的確認(rèn)后,也進(jìn)入?
ESTABLISHED
?狀態(tài)。
TCP 建立一個(gè)連接需要三個(gè)報(bào)文段,釋放一個(gè)連接卻需要四個(gè)報(bào)文段。
四次揮手
數(shù)據(jù)傳輸結(jié)束后,通信的雙方可以釋放連接。數(shù)據(jù)傳輸結(jié)束后的客戶(hù)端主機(jī)和服務(wù)端主機(jī)都處于 ESTABLISHED 狀態(tài),然后進(jìn)入釋放連接的過(guò)程。
TCP 斷開(kāi)連接需要?dú)v經(jīng)的過(guò)程如下
客戶(hù)端應(yīng)用程序發(fā)出釋放連接的報(bào)文段,并停止發(fā)送數(shù)據(jù),主動(dòng)關(guān)閉 TCP 連接。客戶(hù)端主機(jī)發(fā)送釋放連接的報(bào)文段,報(bào)文段中首部 FIN 位置為 1 ,不包含數(shù)據(jù),序列號(hào)位 seq = u,此時(shí)客戶(hù)端主機(jī)進(jìn)入?
FIN-WAIT-1(終止等待 1)
?階段。服務(wù)器主機(jī)接受到客戶(hù)端發(fā)出的報(bào)文段后,即發(fā)出確認(rèn)應(yīng)答報(bào)文,確認(rèn)應(yīng)答報(bào)文中 ACK = 1,生成自己的序號(hào)位 seq = v,ack = u + 1,然后服務(wù)器主機(jī)就進(jìn)入?
CLOSE-WAIT(關(guān)閉等待)
?狀態(tài),這個(gè)時(shí)候客戶(hù)端主機(jī) -> 服務(wù)器主機(jī)這條方向的連接就釋放了,客戶(hù)端主機(jī)沒(méi)有數(shù)據(jù)需要發(fā)送,此時(shí)服務(wù)器主機(jī)是一種半連接的狀態(tài),但是服務(wù)器主機(jī)仍然可以發(fā)送數(shù)據(jù)。客戶(hù)端主機(jī)收到服務(wù)端主機(jī)的確認(rèn)應(yīng)答后,即進(jìn)入?
FIN-WAIT-2(終止等待2)
?的狀態(tài)。等待客戶(hù)端發(fā)出連接釋放的報(bào)文段。當(dāng)服務(wù)器主機(jī)沒(méi)有數(shù)據(jù)發(fā)送后,應(yīng)用進(jìn)程就會(huì)通知 TCP 釋放連接。這時(shí)服務(wù)端主機(jī)會(huì)發(fā)出斷開(kāi)連接的報(bào)文段,報(bào)文段中 ACK = 1,序列號(hào) seq = w,因?yàn)樵谶@之間可能已經(jīng)發(fā)送了一些數(shù)據(jù),所以 seq 不一定等于 v + 1。ack = u + 1,在發(fā)送完斷開(kāi)請(qǐng)求的報(bào)文后,服務(wù)端主機(jī)就進(jìn)入了?
LAST-ACK(最后確認(rèn))
的階段。客戶(hù)端收到服務(wù)端的斷開(kāi)連接請(qǐng)求后,客戶(hù)端需要作出響應(yīng),客戶(hù)端發(fā)出斷開(kāi)連接的報(bào)文段,在報(bào)文段中,ACK = 1, 序列號(hào) seq = u + 1,因?yàn)榭蛻?hù)端從連接開(kāi)始斷開(kāi)后就沒(méi)有再發(fā)送數(shù)據(jù),ack = w + 1,然后進(jìn)入到?
TIME-WAIT(時(shí)間等待)
?狀態(tài),請(qǐng)注意,這個(gè)時(shí)候 TCP 連接還沒(méi)有釋放。必須經(jīng)過(guò)時(shí)間等待的設(shè)置,也就是?2MSL
?后,客戶(hù)端才會(huì)進(jìn)入?CLOSED
?狀態(tài),時(shí)間 MSL 叫做最長(zhǎng)報(bào)文段壽命(Maximum Segment Lifetime)
。服務(wù)端主要收到了客戶(hù)端的斷開(kāi)連接確認(rèn)后,就會(huì)進(jìn)入 CLOSED 狀態(tài)。因?yàn)榉?wù)端結(jié)束 TCP 連接時(shí)間要比客戶(hù)端早,而整個(gè)連接斷開(kāi)過(guò)程需要發(fā)送四個(gè)報(bào)文段,因此釋放連接的過(guò)程也被稱(chēng)為四次揮手。
什么是 TIME-WAIT
我上面只是簡(jiǎn)單提到了一下 TIME-WAIT 狀態(tài)和 2MSL 是啥,下面來(lái)聊一下這兩個(gè)概念。
MSL
?是 TCP 報(bào)文段可以存活或者駐留在網(wǎng)絡(luò)中的最長(zhǎng)時(shí)間。RFC 793 定義了 MSL 的時(shí)間是兩分鐘,但是具體的實(shí)現(xiàn)還要根據(jù)程序員來(lái)指定,一些實(shí)現(xiàn)采用了 30 秒的這個(gè)最大存活時(shí)間。
那么為什么要等待?2MSL
?呢?
主要是因?yàn)閮蓚€(gè)理由
為了保證最后一個(gè)響應(yīng)能夠到達(dá)服務(wù)器,因?yàn)樵谟?jì)算機(jī)網(wǎng)絡(luò)中,最后一個(gè) ACK 報(bào)文段可能會(huì)丟失,從而致使客戶(hù)端一直處于?
LAST-ACK
?狀態(tài)等待客戶(hù)端響應(yīng)。這時(shí)候服務(wù)器會(huì)重傳一次?FINACK?斷開(kāi)連接報(bào)文,客戶(hù)端接收后再重新確認(rèn),重啟定時(shí)器。如果客戶(hù)端不是 2MSL ,在客戶(hù)端發(fā)送 ACK 后直接關(guān)閉的話(huà),如果報(bào)文丟失,那么雙方主機(jī)會(huì)無(wú)法進(jìn)入 CLOSED 狀態(tài)。還可以防止
已失效
的報(bào)文段??蛻?hù)端在發(fā)送最后一個(gè) ACK 之后,再經(jīng)過(guò)經(jīng)過(guò) 2MSL,就可以使本鏈接持續(xù)時(shí)間內(nèi)所產(chǎn)生的所有報(bào)文段都從網(wǎng)絡(luò)中消失。從而保證在關(guān)閉連接后不會(huì)有還在網(wǎng)絡(luò)中滯留的報(bào)文段去騷擾服務(wù)器。
這里注意一點(diǎn):在服務(wù)器發(fā)送了 FIN-ACK 之后,會(huì)立即啟動(dòng)超時(shí)重傳計(jì)時(shí)器。客戶(hù)端在發(fā)送最后一個(gè) ACK 之后會(huì)立即啟動(dòng)時(shí)間等待計(jì)時(shí)器。
說(shuō)好的 RST 呢
說(shuō)好的?RST
、SYN
、FIN
?標(biāo)志用于連接的建立和關(guān)閉,那么 SYN 和 FIN 都現(xiàn)身了,那 RST 呢?也是啊,我們上面探討的都是一種理想的情況,就是客戶(hù)端服務(wù)器雙方都會(huì)接受傳輸報(bào)文段的情況,還有一種情況是當(dāng)主機(jī)收到 TCP 報(bào)文段后,其 IP 和端口號(hào)不匹配的情況。假設(shè)客戶(hù)端主機(jī)發(fā)送一個(gè)請(qǐng)求,而服務(wù)器主機(jī)經(jīng)過(guò) IP 和端口號(hào)的判斷后發(fā)現(xiàn)不是給這個(gè)服務(wù)器的,那么服務(wù)器就會(huì)發(fā)出一個(gè)?RST
?特殊報(bào)文段給客戶(hù)端。
因此,當(dāng)服務(wù)端發(fā)送一個(gè) RST 特殊報(bào)文段給客戶(hù)端的時(shí)候,它就會(huì)告訴客戶(hù)端沒(méi)有匹配的套接字連接,請(qǐng)不要再繼續(xù)發(fā)送了。
上面探討的是 TCP 的情況,那么 UDP 呢?
使用 UDP 作為傳輸協(xié)議后,如果套接字不匹配的話(huà),UDP 主機(jī)就會(huì)發(fā)送一個(gè)特殊的 ICMP 數(shù)據(jù)報(bào)。
SYN 洪泛攻擊
下面我們來(lái)討論一下什么是?SYN 洪泛攻擊。
我們?cè)?TCP 的三次握手中已經(jīng)看到,服務(wù)器為了響應(yīng)一個(gè)收到的 SYN,分配并初始化變量連接和緩存,然后服務(wù)器發(fā)送一個(gè) SYNACK 作為響應(yīng),然后等待來(lái)自于客戶(hù)端的 ACK 報(bào)文。如果客戶(hù)端不發(fā)送 ACK 來(lái)完成最后一步的話(huà),那么這個(gè)連接就處在一個(gè)掛起的狀態(tài),也就是半連接狀態(tài)。
攻擊者通常在這種情況下發(fā)送大量的 TCP SYN 報(bào)文段,服務(wù)端繼續(xù)響應(yīng),但是每個(gè)連接都完不成三次握手的步驟。隨著 SYN 的不斷增加,服務(wù)器會(huì)不斷的為這些半開(kāi)連接分配資源,導(dǎo)致服務(wù)器的連接最終被消耗殆盡。這種攻擊也是屬于?Dos
?攻擊的一種。
抵御這種攻擊的方式是使用?SYN cookie
?,下面是它的工作流程介紹
當(dāng)服務(wù)器收到一個(gè) SYN 報(bào)文段時(shí),它并不知道這個(gè)報(bào)文段是來(lái)自哪里,是來(lái)自攻擊者主機(jī)還是客戶(hù)端主機(jī)(雖然攻擊者也是客戶(hù)端,不過(guò)這么說(shuō)更便于區(qū)分) 。因此服務(wù)器不會(huì)為報(bào)文段生成一個(gè)半開(kāi)連接。與此相反,服務(wù)器生成一個(gè)初始的 TCP 序列號(hào),這個(gè)序列號(hào)是 SYN 報(bào)文段的源和目的 IP 地址與端口號(hào)這個(gè)四元組構(gòu)造的一個(gè)復(fù)雜的散列函數(shù),這個(gè)散列函數(shù)生成的 TCP 序列號(hào)就是?
SYN Cookie
,用于緩存 SYN 請(qǐng)求。然后,服務(wù)器會(huì)發(fā)送帶著 SYN Cookie 的 SYNACK 分組。有一點(diǎn)需要注意的是,服務(wù)器不會(huì)記憶這個(gè) Cookie 或 SYN 的其他狀態(tài)信息。如果客戶(hù)端不是攻擊者的話(huà),它就會(huì)返回一個(gè) ACK 報(bào)文段。當(dāng)服務(wù)器收到這個(gè) ACK 后,需要驗(yàn)證這個(gè) ACK 與 SYN 發(fā)送的是否相同,驗(yàn)證的標(biāo)準(zhǔn)就是確認(rèn)字段中的確認(rèn)號(hào)和序列號(hào),源和目的 IP 地址與端口號(hào)以及和散列函數(shù)的是否一致,散列函數(shù)的結(jié)果 + 1 是否和 SYNACK 中的確認(rèn)值相同。(大致是這樣,說(shuō)的不對(duì)還請(qǐng)讀者糾正) 。如果有興趣讀者可以自行深入了解。如果是合法的,服務(wù)器就會(huì)生成一個(gè)具有套接字的全開(kāi)連接。
如果客戶(hù)端沒(méi)有返回 ACK,即認(rèn)為是攻擊者,那么這樣也沒(méi)關(guān)系,服務(wù)器沒(méi)有收到 ACK,不會(huì)分配變量和緩存資源,不會(huì)對(duì)服務(wù)器產(chǎn)生危害。
擁塞控制
有了 TCP 的窗口控制后,使計(jì)算機(jī)網(wǎng)絡(luò)中兩個(gè)主機(jī)之間不再是以單個(gè)數(shù)據(jù)段的形式發(fā)送了,而是能夠連續(xù)發(fā)送大量的數(shù)據(jù)包。然而,大量數(shù)據(jù)包同時(shí)也伴隨著其他問(wèn)題,比如網(wǎng)絡(luò)負(fù)載、網(wǎng)絡(luò)擁堵等問(wèn)題。TCP 為了防止這類(lèi)問(wèn)題的出現(xiàn),使用了?擁塞控制
?機(jī)制,擁塞控制機(jī)制會(huì)在面臨網(wǎng)絡(luò)擁塞時(shí)遏制發(fā)送方的數(shù)據(jù)發(fā)送。
擁塞控制主要有兩種方法
端到端的擁塞控制
: 因?yàn)榫W(wǎng)絡(luò)層沒(méi)有為運(yùn)輸層擁塞控制提供顯示支持。所以即使網(wǎng)絡(luò)中存在擁塞情況,端系統(tǒng)也要通過(guò)對(duì)網(wǎng)絡(luò)行為的觀察來(lái)推斷。TCP 就是使用了端到端的擁塞控制方式。IP 層不會(huì)向端系統(tǒng)提供有關(guān)網(wǎng)絡(luò)擁塞的反饋信息。那么 TCP 如何推斷網(wǎng)絡(luò)擁塞呢?如果超時(shí)或者三次冗余確認(rèn)就被認(rèn)為是網(wǎng)絡(luò)擁塞,TCP 會(huì)減小窗口的大小,或者增加往返時(shí)延來(lái)避免。網(wǎng)絡(luò)輔助的擁塞控制
: 在網(wǎng)絡(luò)輔助的擁塞控制中,路由器會(huì)向發(fā)送方提供關(guān)于網(wǎng)絡(luò)中擁塞狀態(tài)的反饋。這種反饋信息就是一個(gè)比特信息,它指示鏈路中的擁塞情況。
下圖描述了這兩種擁塞控制方式
TCP 擁塞控制
如果你看到這里,那我就暫定認(rèn)為你了解了 TCP 實(shí)現(xiàn)可靠性的基礎(chǔ)了,那就是使用序號(hào)和確認(rèn)號(hào)。除此之外,另外一個(gè)實(shí)現(xiàn) TCP 可靠性基礎(chǔ)的就是 TCP 的擁塞控制。如果說(shuō)
TCP 所采用的方法是讓每一個(gè)發(fā)送方根據(jù)所感知到的網(wǎng)絡(luò)的擁塞程度來(lái)限制發(fā)出報(bào)文段的速率,如果 TCP 發(fā)送方感知到?jīng)]有什么擁塞,則 TCP 發(fā)送方會(huì)增加發(fā)送速率;如果發(fā)送方感知沿著路徑有阻塞,那么發(fā)送方就會(huì)降低發(fā)送速率。
但是這種方法有三個(gè)問(wèn)題
TCP 發(fā)送方如何限制它向其他連接發(fā)送報(bào)文段的速率呢?
一個(gè) TCP 發(fā)送方是如何感知到網(wǎng)絡(luò)擁塞的呢?
當(dāng)發(fā)送方感知到端到端的擁塞時(shí),采用何種算法來(lái)改變其發(fā)送速率呢?
我們先來(lái)探討一下第一個(gè)問(wèn)題,TCP 發(fā)送方如何限制它向其他連接發(fā)送報(bào)文段的速率呢?
我們知道 TCP 是由接收緩存、發(fā)送緩存和變量(LastByteRead, rwnd,等)
組成。發(fā)送方的 TCP 擁塞控制機(jī)制會(huì)跟蹤一個(gè)變量,即?擁塞窗口(congestion window)
?的變量,擁塞窗口表示為?cwnd
,用于限制 TCP 在接收到 ACK 之前可以發(fā)送到網(wǎng)絡(luò)的數(shù)據(jù)量。而接收窗口(rwnd)
?是一個(gè)用于告訴接收方能夠接受的數(shù)據(jù)量。
一般來(lái)說(shuō),發(fā)送方未確認(rèn)的數(shù)據(jù)量不得超過(guò) cwnd 和 rwnd 的最小值,也就是
LastByteSent - LastByteAcked <= min(cwnd,rwnd)
由于每個(gè)數(shù)據(jù)包的往返時(shí)間是 RTT,我們假設(shè)接收端有足夠的緩存空間用于接收數(shù)據(jù),我們就不用考慮 rwnd 了,只專(zhuān)注于 cwnd,那么,該發(fā)送方的發(fā)送速率大概是?cwnd/RTT 字節(jié)/秒
?。通過(guò)調(diào)節(jié) cwnd,發(fā)送方因此能調(diào)整它向連接發(fā)送數(shù)據(jù)的速率。
一個(gè) TCP 發(fā)送方是如何感知到網(wǎng)絡(luò)擁塞的呢?
這個(gè)我們上面討論過(guò),是 TCP 根據(jù)超時(shí)或者 3 個(gè)冗余 ACK 來(lái)感知的。
當(dāng)發(fā)送方感知到端到端的擁塞時(shí),采用何種算法來(lái)改變其發(fā)送速率呢??
這個(gè)問(wèn)題比較復(fù)雜,且容我娓娓道來(lái),一般來(lái)說(shuō),TCP 會(huì)遵循下面這幾種指導(dǎo)性原則
如果在報(bào)文段發(fā)送過(guò)程中丟失,那就意味著網(wǎng)絡(luò)擁堵,此時(shí)需要適當(dāng)降低 TCP 發(fā)送方的速率。
一個(gè)確認(rèn)報(bào)文段指示發(fā)送方正在向接收方傳遞報(bào)文段,因此,當(dāng)對(duì)先前未確認(rèn)報(bào)文段的確認(rèn)到達(dá)時(shí),能夠增加發(fā)送方的速率。為啥呢?因?yàn)槲创_認(rèn)的報(bào)文段到達(dá)接收方也就表示著網(wǎng)絡(luò)不擁堵,能夠順利到達(dá),因此發(fā)送方擁塞窗口長(zhǎng)度會(huì)變大,所以發(fā)送速率會(huì)變快
帶寬探測(cè)
,帶寬探測(cè)說(shuō)的是 TCP 可以通過(guò)調(diào)節(jié)傳輸速率來(lái)增加/減小 ACK 到達(dá)的次數(shù),如果出現(xiàn)丟包事件,就會(huì)減小傳輸速率。因此,為了探測(cè)擁塞開(kāi)始出現(xiàn)的頻率, TCP 發(fā)送方應(yīng)該增加它的傳輸速率。然后慢慢使傳輸速率降低,進(jìn)而再次開(kāi)始探測(cè),看看擁塞開(kāi)始速率是否發(fā)生了變化。
在了解完 TCP 擁塞控制后,下面我們就該聊一下 TCP 的?擁塞控制算法(TCP congestion control algorithm)
?了。TCP 擁塞控制算法主要包含三個(gè)部分:慢啟動(dòng)、擁塞避免、快速恢復(fù),下面我們依次來(lái)看一下
慢啟動(dòng)
當(dāng)一條 TCP 開(kāi)始建立連接時(shí),cwnd 的值就會(huì)初始化為一個(gè) MSS 的較小值。這就使得初始發(fā)送速率大概是?MSS/RTT 字節(jié)/秒
?,比如要傳輸 1000 字節(jié)的數(shù)據(jù),RTT 為 200 ms ,那么得到的初始發(fā)送速率大概是 40 kb/s 。實(shí)際情況下可用帶寬要比這個(gè) MSS/RTT 大得多,因此 TCP 想要找到最佳的發(fā)送速率,可以通過(guò)?慢啟動(dòng)(slow-start)
?的方式,在慢啟動(dòng)的方式中,cwnd 的值會(huì)初始化為 1 個(gè) MSS,并且每次傳輸報(bào)文確認(rèn)后就會(huì)增加一個(gè) MSS,cwnd 的值會(huì)變?yōu)?2 個(gè) MSS,這兩個(gè)報(bào)文段都傳輸成功后每個(gè)報(bào)文段 + 1,會(huì)變?yōu)?4 個(gè) MSS,依此類(lèi)推,每成功一次 cwnd 的值就會(huì)翻倍。如下圖所示
發(fā)送速率不可能會(huì)一直增長(zhǎng),增長(zhǎng)總有結(jié)束的時(shí)候,那么何時(shí)結(jié)束呢?慢啟動(dòng)通常會(huì)使用下面這幾種方式結(jié)束發(fā)送速率的增長(zhǎng)。
如果在慢啟動(dòng)的發(fā)送過(guò)程出現(xiàn)丟包的情況,那么 TCP 會(huì)將發(fā)送方的 cwnd 設(shè)置為 1 并重新開(kāi)始慢啟動(dòng)的過(guò)程,此時(shí)會(huì)引入一個(gè)?
ssthresh(慢啟動(dòng)閾值)
?的概念,它的初始值就是產(chǎn)生丟包的 cwnd 的值 / 2,即當(dāng)檢測(cè)到擁塞時(shí),ssthresh 的值就是窗口值的一半。第二種方式是直接和 ssthresh 的值相關(guān)聯(lián),因?yàn)楫?dāng)檢測(cè)到擁塞時(shí),ssthresh 的值就是窗口值的一半,那么當(dāng) cwnd > ssthresh 時(shí),每次翻番都可能會(huì)出現(xiàn)丟包,所以最好的方式就是 cwnd 的值 = ssthresh ,這樣 TCP 就會(huì)轉(zhuǎn)為擁塞控制模式,結(jié)束慢啟動(dòng)。
慢啟動(dòng)結(jié)束的最后一種方式就是如果檢測(cè)到 3 個(gè)冗余 ACK,TCP 就會(huì)執(zhí)行一種快速重傳并進(jìn)入恢復(fù)狀態(tài)。
擁塞避免
當(dāng) TCP 進(jìn)入擁塞控制狀態(tài)后,cwnd 的值就等于擁塞時(shí)值的一半,也就是 ssthresh 的值。所以,無(wú)法每次報(bào)文段到達(dá)后都將 cwnd 的值再翻倍。而是采用了一種相對(duì)保守
的方式,每次傳輸完成后只將 cwnd 的值增加一個(gè) MSS
,比如收到了 10 個(gè)報(bào)文段的確認(rèn),但是 cwnd 的值只增加一個(gè) MSS。這是一種線(xiàn)性增長(zhǎng)模式,它也會(huì)有增長(zhǎng)逾值,它的增長(zhǎng)逾值和慢啟動(dòng)一樣,如果出現(xiàn)丟包,那么 cwnd 的值就是一個(gè) MSS,ssthresh 的值就等于 cwnd 的一半;或者是收到 3 個(gè)冗余的 ACK 響應(yīng)也能停止 MSS 增長(zhǎng)。如果 TCP 將 cwnd 的值減半后,仍然會(huì)收到 3 個(gè)冗余 ACK,那么就會(huì)將 ssthresh 的值記錄為 cwnd 值的一半,進(jìn)入?快速恢復(fù)
?狀態(tài)。
快速恢復(fù)
在快速恢復(fù)中,對(duì)于使 TCP 進(jìn)入快速恢復(fù)狀態(tài)缺失的報(bào)文段,對(duì)于每個(gè)收到的冗余 ACK,cwnd 的值都會(huì)增加一個(gè) MSS 。當(dāng)對(duì)丟失報(bào)文段的一個(gè) ACK 到達(dá)時(shí),TCP 在降低 cwnd 后進(jìn)入擁塞避免狀態(tài)。如果在擁塞控制狀態(tài)后出現(xiàn)超時(shí),那么就會(huì)遷移到慢啟動(dòng)狀態(tài),cwnd 的值被設(shè)置為 1 個(gè) MSS,ssthresh 的值設(shè)置為 cwnd 的一半。
如果你能用心看到這里,我相信你定會(huì)有所收獲。
特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒(méi)關(guān)注的小伙伴,可以長(zhǎng)按關(guān)注一下:
長(zhǎng)按訂閱更多精彩▼
如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!