TCP協(xié)議
TCP協(xié)議全稱(chēng): 傳輸控制協(xié)議, 顧名思義, 就是要對(duì)數(shù)據(jù)的傳輸進(jìn)行一定的控制.
報(bào)頭

每部分的含義和作用
源端口號(hào)/目的端口號(hào): 表示數(shù)據(jù)從哪個(gè)進(jìn)程來(lái), 到哪個(gè)進(jìn)程去.
32位序號(hào):
4位首部長(zhǎng)度: 表示該tcp報(bào)頭有多少個(gè)4字節(jié)(32個(gè)bit)
6位保留: 顧名思義, 先保留著, 以防萬(wàn)一
6位標(biāo)志位
URG: 標(biāo)識(shí)緊急指針是否有效
ACK: 標(biāo)識(shí)確認(rèn)序號(hào)是否有效
PSH: 用來(lái)提示接收端應(yīng)用程序立刻將數(shù)據(jù)從tcp緩沖區(qū)讀走
RST: 要求重新建立連接. 我們把含有RST標(biāo)識(shí)的報(bào)文稱(chēng)為復(fù)位報(bào)文段
SYN: 請(qǐng)求建立連接. 我們把含有SYN標(biāo)識(shí)的報(bào)文稱(chēng)為同步報(bào)文段
FIN: 通知對(duì)端, 本端即將關(guān)閉. 我們把含有FIN標(biāo)識(shí)的報(bào)文稱(chēng)為結(jié)束報(bào)文段
16位窗口大小:
16位檢驗(yàn)和: 由發(fā)送端填充, 檢驗(yàn)形式有CRC校驗(yàn)等. 如果接收端校驗(yàn)不通過(guò), 則認(rèn)為數(shù)據(jù)有問(wèn)題. 此處的校驗(yàn)和不光包含TCP首部, 也包含TCP數(shù)據(jù)部分.
16位緊急指針: 用來(lái)標(biāo)識(shí)哪部分?jǐn)?shù)據(jù)是緊急數(shù)據(jù).
選項(xiàng)、數(shù)據(jù):暫時(shí)忽略
連接管理機(jī)制
正常情況下, tcp需要經(jīng)過(guò)三次握手建立連接, 四次揮手?jǐn)嚅_(kāi)連接.
三次握手
第一次:
客戶(hù)端 - - > 服務(wù)器 此時(shí)服務(wù)器知道了客戶(hù)端要建立連接了
第二次:
客戶(hù)端 < - - 服務(wù)器 此時(shí)客戶(hù)端知道服務(wù)器收到連接請(qǐng)求了
第三次:
客戶(hù)端 - - > 服務(wù)器 此時(shí)服務(wù)器知道客戶(hù)端收到了自己的回應(yīng)
到這里, 就可以認(rèn)為客戶(hù)端與服務(wù)器已經(jīng)建立了連接.
再來(lái)看個(gè)圖.

剛開(kāi)始, 客戶(hù)端和服務(wù)器都處于 CLOSE 狀態(tài).
此時(shí), 客戶(hù)端向服務(wù)器主動(dòng)發(fā)出連接請(qǐng)求, 服務(wù)器被動(dòng)接受連接請(qǐng)求.
1, TCP服務(wù)器進(jìn)程先創(chuàng)建傳輸控制塊TCB, 時(shí)刻準(zhǔn)備接受客戶(hù)端進(jìn)程的連接請(qǐng)求, 此時(shí)服務(wù)器就進(jìn)入了 LISTEN(監(jiān)聽(tīng))狀態(tài)
2, TCP客戶(hù)端進(jìn)程也是先創(chuàng)建傳輸控制塊TCB, 然后向服務(wù)器發(fā)出連接請(qǐng)求報(bào)文,此時(shí)報(bào)文首部中的同步標(biāo)志位SYN=1, 同時(shí)選擇一個(gè)初始序列號(hào) seq = x, 此時(shí),TCP客戶(hù)端進(jìn)程進(jìn)入了 SYN-SENT(同步已發(fā)送狀態(tài))狀態(tài)。TCP規(guī)定, SYN報(bào)文段(SYN=1的報(bào)文段)不能攜帶數(shù)據(jù),但需要消耗掉一個(gè)序號(hào)。
3, TCP服務(wù)器收到請(qǐng)求報(bào)文后, 如果同意連接, 則發(fā)出確認(rèn)報(bào)文。確認(rèn)報(bào)文中的 ACK=1, SYN=1, 確認(rèn)序號(hào)是 x+1, 同時(shí)也要為自己初始化一個(gè)序列號(hào) seq = y, 此時(shí), TCP服務(wù)器進(jìn)程進(jìn)入了SYN-RCVD(同步收到)狀態(tài)。這個(gè)報(bào)文也不能攜帶數(shù)據(jù), 但是同樣要消耗一個(gè)序號(hào)。
4, TCP客戶(hù)端進(jìn)程收到確認(rèn)后還, 要向服務(wù)器給出確認(rèn)。確認(rèn)報(bào)文的ACK=1,確認(rèn)序號(hào)是 y+1,自己的序列號(hào)是 x+1.
5, 此時(shí),TCP連接建立,客戶(hù)端進(jìn)入ESTABLISHED(已建立連接)狀態(tài)。當(dāng)服務(wù)器收到客戶(hù)端的確認(rèn)后也進(jìn)入ESTABLISHED狀態(tài),此后雙方就可以開(kāi)始通信了。
為什么不用兩次?
主要是為了防止已經(jīng)失效的連接請(qǐng)求報(bào)文突然又傳送到了服務(wù)器,從而產(chǎn)生錯(cuò)誤。如果使用的是兩次握手建立連接,假設(shè)有這樣一種場(chǎng)景,客戶(hù)端發(fā)送的第一個(gè)請(qǐng)求連接并且沒(méi)有丟失,只是因?yàn)樵诰W(wǎng)絡(luò)中滯留的時(shí)間太長(zhǎng)了,由于TCP的客戶(hù)端遲遲沒(méi)有收到確認(rèn)報(bào)文,以為服務(wù)器沒(méi)有收到,此時(shí)重新向服務(wù)器發(fā)送這條報(bào)文,此后客戶(hù)端和服務(wù)器經(jīng)過(guò)兩次握手完成連接,傳輸數(shù)據(jù),然后關(guān)閉連接。此時(shí)之前滯留的那一次請(qǐng)求連接,因?yàn)榫W(wǎng)絡(luò)通暢了, 到達(dá)了服務(wù)器,這個(gè)報(bào)文本該是失效的,但是,兩次握手的機(jī)制將會(huì)讓客戶(hù)端和服務(wù)器再次建立連接,這將導(dǎo)致不必要的錯(cuò)誤和資源的費(fèi)。
如果采用的是三次握手,就算是那一次失效的報(bào)文傳送過(guò)來(lái)了,服務(wù)端接受到了那條失效報(bào)文并且回復(fù)了確認(rèn)報(bào)文,但是客戶(hù)端不會(huì)再次發(fā)出確認(rèn)。由于服務(wù)器收不到確認(rèn),就知道客戶(hù)端并沒(méi)有請(qǐng)求連接。
為什么不用四次?
因?yàn)槿我呀?jīng)可以滿足需要了, 四次就多余了。
四次揮手.
數(shù)據(jù)傳輸完畢后,雙方都可以釋放連接.
此時(shí)客戶(hù)端和服務(wù)器都是處于ESTABLISHED狀態(tài),然后客戶(hù)端主動(dòng)斷開(kāi)連接,服務(wù)器被動(dòng)斷開(kāi)連接.
1, 客戶(hù)端進(jìn)程發(fā)出連接釋放報(bào)文,并且停止發(fā)送數(shù)據(jù)。
釋放數(shù)據(jù)報(bào)文首部,F(xiàn)IN=1,其序列號(hào)為seq=u(等于前面已經(jīng)傳送過(guò)來(lái)的數(shù)據(jù)的最后一個(gè)字節(jié)的序號(hào)加1),此時(shí)客戶(hù)端進(jìn)入FIN-WAIT-1(終止等待1)狀態(tài)。 TCP規(guī)定,F(xiàn)IN報(bào)文段即使不攜帶數(shù)據(jù),也要消耗一個(gè)序號(hào)。
2, 服務(wù)器收到連接釋放報(bào)文,發(fā)出確認(rèn)報(bào)文,ACK=1,確認(rèn)序號(hào)為 u+1,并且?guī)献约旱男蛄刑?hào)seq=v,此時(shí)服務(wù)端就進(jìn)入了CLOSE-WAIT(關(guān)閉等待)狀態(tài)。
TCP服務(wù)器通知高層的應(yīng)用進(jìn)程,客戶(hù)端向服務(wù)器的方向就釋放了,這時(shí)候處于半關(guān)閉狀態(tài),即客戶(hù)端已經(jīng)沒(méi)有數(shù)據(jù)要發(fā)送了,但是服務(wù)器若發(fā)送數(shù)據(jù),客戶(hù)端依然要接受。這個(gè)狀態(tài)還要持續(xù)一段時(shí)間,也就是整個(gè)CLOSE-WAIT狀態(tài)持續(xù)的時(shí)間。
3, 客戶(hù)端收到服務(wù)器的確認(rèn)請(qǐng)求后,此時(shí)客戶(hù)端就進(jìn)入FIN-WAIT-2(終止等待2)狀態(tài),等待服務(wù)器發(fā)送連接釋放報(bào)文(在這之前還需要接受服務(wù)器發(fā)送的最終數(shù)據(jù))
4, 服務(wù)器將最后的數(shù)據(jù)發(fā)送完畢后,就向客戶(hù)端發(fā)送連接釋放報(bào)文,F(xiàn)IN=1,確認(rèn)序號(hào)為v+1,由于在半關(guān)閉狀態(tài),服務(wù)器很可能又發(fā)送了一些數(shù)據(jù),假定此時(shí)的序列號(hào)為seq=w,此時(shí),服務(wù)器就進(jìn)入了LAST-ACK(最后確認(rèn))狀態(tài),等待客戶(hù)端的確認(rèn)。
5, 客戶(hù)端收到服務(wù)器的連接釋放報(bào)文后,必須發(fā)出確認(rèn),ACK=1,確認(rèn)序號(hào)為w+1,而自己的序列號(hào)是u+1,此時(shí),客戶(hù)端就進(jìn)入了TIME-WAIT(時(shí)間等待)狀態(tài)。注意此時(shí)TCP連接還沒(méi)有釋放,必須經(jīng)過(guò)2?MSL(最長(zhǎng)報(bào)文段壽命)的時(shí)間后,當(dāng)客戶(hù)端撤銷(xiāo)相應(yīng)的TCB后,才進(jìn)入CLOSED狀態(tài)。
6, 服務(wù)器只要收到了客戶(hù)端發(fā)出的確認(rèn),立即進(jìn)入CLOSED狀態(tài)。同樣,撤銷(xiāo)TCB后,就結(jié)束了這次的TCP連接??梢钥吹剑?wù)器結(jié)束TCP連接的時(shí)間要比客戶(hù)端早一些。
再來(lái)看一張圖.

為什么最后客戶(hù)端還要等待 2*MSL的時(shí)間呢?
MSL(Maximum Segment Lifetime),TCP允許不同的實(shí)現(xiàn)可以設(shè)置不同的MSL值。
第一,保證客戶(hù)端發(fā)送的最后一個(gè)ACK報(bào)文能夠到達(dá)服務(wù)器,因?yàn)檫@個(gè)ACK報(bào)文可能丟失,站在服務(wù)器的角度看來(lái),我已經(jīng)發(fā)送了FIN+ACK報(bào)文請(qǐng)求斷開(kāi)了,客戶(hù)端還沒(méi)有給我回應(yīng),應(yīng)該是我發(fā)送的請(qǐng)求斷開(kāi)報(bào)文它沒(méi)有收到,于是服務(wù)器又會(huì)重新發(fā)送一次,而客戶(hù)端就能在這個(gè)2MSL時(shí)間段內(nèi)收到這個(gè)重傳的報(bào)文,接著給出回應(yīng)報(bào)文,并且會(huì)重啟2MSL計(jì)時(shí)器。
第二,防止類(lèi)似與“三次握手”中提到了的“已經(jīng)失效的連接請(qǐng)求報(bào)文段”出現(xiàn)在本連接中??蛻?hù)端發(fā)送完最后一個(gè)確認(rèn)報(bào)文后,在這個(gè)2MSL時(shí)間中,就可以使本連接持續(xù)的時(shí)間內(nèi)所產(chǎn)生的所有報(bào)文段都從網(wǎng)絡(luò)中消失。這樣新的連接中不會(huì)出現(xiàn)舊連接的請(qǐng)求報(bào)文。
為什么建立連接是三次握手,關(guān)閉連接確是四次揮手呢?
建立連接的時(shí)候, 服務(wù)器在LISTEN狀態(tài)下,收到建立連接請(qǐng)求的SYN報(bào)文后,把ACK和SYN放在一個(gè)報(bào)文里發(fā)送給客戶(hù)端。
而關(guān)閉連接時(shí),服務(wù)器收到對(duì)方的FIN報(bào)文時(shí),僅僅表示對(duì)方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),而自己也未必全部數(shù)據(jù)都發(fā)送給對(duì)方了,所以己方可以立即關(guān)閉,也可以發(fā)送一些數(shù)據(jù)給對(duì)方后,再發(fā)送FIN報(bào)文給對(duì)方來(lái)表示同意現(xiàn)在關(guān)閉連接,因此,己方ACK和FIN一般都會(huì)分開(kāi)發(fā)送,從而導(dǎo)致多了一次。
如果已經(jīng)建立了連接, 但是客戶(hù)端突發(fā)故障了怎么辦?
TCP設(shè)有一個(gè)?;钣?jì)時(shí)器,顯然,客戶(hù)端如果出現(xiàn)故障,服務(wù)器不能一直等下去,白白浪費(fèi)資源。服務(wù)器每收到一次客戶(hù)端的請(qǐng)求后都會(huì)重新復(fù)位這個(gè)計(jì)時(shí)器,時(shí)間通常是設(shè)置為2小時(shí),若兩小時(shí)還沒(méi)有收到客戶(hù)端的任何數(shù)據(jù),服務(wù)器就會(huì)發(fā)送一個(gè)探測(cè)報(bào)文段,以后每隔75分鐘發(fā)送一次。若一連發(fā)送10個(gè)探測(cè)報(bào)文仍然沒(méi)反應(yīng),服務(wù)器就認(rèn)為客戶(hù)端出了故障,接著就關(guān)閉連接。
理解TIME_WAIT狀態(tài)
可以做一個(gè)實(shí)驗(yàn), 先運(yùn)行server, 再運(yùn)行client連接server, 然后斷開(kāi)server, 再立馬運(yùn)行server.
我們會(huì)發(fā)現(xiàn):

綁定的時(shí)候出了問(wèn)題.
這是因?yàn)?雖然server應(yīng)用程序終止了,但TCP協(xié)議層的連接并沒(méi)有完全斷開(kāi),因此不能再次監(jiān)聽(tīng)綁定同樣的server端口.
TCP協(xié)議規(guī)定,主動(dòng)關(guān)閉連接的一方要處于TIME_ WAIT狀態(tài),等待2*MSL(maximum segment lifetime)的時(shí)間后才能回到CLOSED狀態(tài).
我們使用Ctrl-C終止了server, 所以server是主動(dòng)關(guān)閉連接的一方, 在TIME_WAIT期間仍然不能再次監(jiān)聽(tīng)同樣的server端口
MSL在RFC1122中規(guī)定為兩分鐘,但是各操作系統(tǒng)的實(shí)現(xiàn)不同, 在Centos7上默認(rèn)配置的值是60s;
可以通過(guò) cat /proc/sys/net/ipv4/tcp_fin_timeout 查看MSL的值

解決TIME_WAIT引起的bind失敗問(wèn)題
在server的TCP連接沒(méi)有完全斷開(kāi)之前不允許重新監(jiān)聽(tīng), 某些情況下可能是不合理的.
比如:
服務(wù)器需要處理非常大量的客戶(hù)端的連接(每個(gè)連接的生存時(shí)間可能很短, 但是每秒都有大量的客戶(hù)端來(lái)請(qǐng)求).
這個(gè)時(shí)候如果由服務(wù)器端主動(dòng)關(guān)閉連接(比如某些客戶(hù)端不活躍, 就需要被服務(wù)器端主動(dòng)清理掉), 就會(huì)產(chǎn)生大量TIME_WAIT連接.
由于我們的請(qǐng)求量很大, 就可能導(dǎo)致TIME_WAIT的連接數(shù)很多, 導(dǎo)致服務(wù)器的端口不夠用, 無(wú)法處理新的連接.
解決方法:
- 使用setsockopt()設(shè)置socket描述符的選項(xiàng)SO_REUSEADDR為1, 表示允許創(chuàng)建端口號(hào)相同但I(xiàn)P地址不同的多個(gè)socket描述符.
用法:
在server代碼的socket()和bind()調(diào)用之間插入如下代碼
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
確認(rèn)應(yīng)答機(jī)制(ACK機(jī)制)

TCP將每個(gè)字節(jié)的數(shù)據(jù)都進(jìn)行了編號(hào), 即為序列號(hào).

每一個(gè)ACK都帶有對(duì)應(yīng)的確認(rèn)序列號(hào), 意思是告訴發(fā)送者, 我已經(jīng)收到了哪些數(shù)據(jù); 下一次你要從哪里開(kāi)始發(fā).
比如, 客戶(hù)端向服務(wù)器發(fā)送了1005字節(jié)的數(shù)據(jù), 服務(wù)器返回給客戶(hù)端的確認(rèn)序號(hào)是1003, 那么說(shuō)明服務(wù)器只收到了1-1002的數(shù)據(jù).
1003, 1004, 1005都沒(méi)收到.
此時(shí)客戶(hù)端就會(huì)從1003開(kāi)始重發(fā).
超時(shí)重傳機(jī)制

主機(jī)A發(fā)送數(shù)據(jù)給B之后, 可能因?yàn)榫W(wǎng)絡(luò)擁堵等原因, 數(shù)據(jù)無(wú)法到達(dá)主機(jī)B
如果主機(jī)A在一個(gè)特定時(shí)間間隔內(nèi)沒(méi)有收到B發(fā)來(lái)的確認(rèn)應(yīng)答, 就會(huì)進(jìn)行重發(fā)
但是主機(jī)A沒(méi)收到確認(rèn)應(yīng)答也可能是ACK丟失了.

這種情況下, 主機(jī)B會(huì)收到很多重復(fù)數(shù)據(jù).
那么TCP協(xié)議需要識(shí)別出哪些包是重復(fù)的, 并且把重復(fù)的丟棄.
這時(shí)候利用前面提到的序列號(hào), 就可以很容易做到去重.
超時(shí)時(shí)間如何確定?
最理想的情況下, 找到一個(gè)最小的時(shí)間, 保證 “確認(rèn)應(yīng)答一定能在這個(gè)時(shí)間內(nèi)返回”.
但是這個(gè)時(shí)間的長(zhǎng)短, 隨著網(wǎng)絡(luò)環(huán)境的不同, 是有差異的.
如果超時(shí)時(shí)間設(shè)的太長(zhǎng), 會(huì)影響整體的重傳效率; 如果超時(shí)時(shí)間設(shè)的太短, 有可能會(huì)頻繁發(fā)送重復(fù)的包.
TCP為了保證任何環(huán)境下都能保持較高性能的通信, 因此會(huì)動(dòng)態(tài)計(jì)算這個(gè)最大超時(shí)時(shí)間.
Linux中(BSD Unix和Windows也是如此), 超時(shí)以500ms為一個(gè)單位進(jìn)行控制, 每次判定超時(shí)重發(fā)的超時(shí)時(shí)間都是500ms的整數(shù)倍.
如果重發(fā)一次之后, 仍然得不到應(yīng)答, 等待 2500ms 后再進(jìn)行重傳. 如果仍然得不到應(yīng)答, 等待 4500ms 進(jìn)行重傳.
依次類(lèi)推, 以指數(shù)形式遞增. 累計(jì)到一定的重傳次數(shù), TCP認(rèn)為網(wǎng)絡(luò)異?;蛘邔?duì)端主機(jī)出現(xiàn)異常, 強(qiáng)制關(guān)閉連接.
滑動(dòng)窗口
剛才我們討論了確認(rèn)應(yīng)答機(jī)制, 對(duì)每一個(gè)發(fā)送的數(shù)據(jù)段, 都要給一個(gè)ACK確認(rèn)應(yīng)答. 收到ACK后再發(fā)送下一個(gè)數(shù)據(jù)段.
這樣做有一個(gè)比較大的缺點(diǎn), 就是性能較差. 尤其是數(shù)據(jù)往返時(shí)間較長(zhǎng)的時(shí)候.
那么我們可不可以一次發(fā)送多個(gè)數(shù)據(jù)段呢?
例如這樣:

窗口
窗口大小指的是無(wú)需等待確認(rèn)應(yīng)答就可以繼續(xù)發(fā)送數(shù)據(jù)的最大值.
上圖的窗口大小就是4000個(gè)字節(jié) (四個(gè)段).
發(fā)送前四個(gè)段的時(shí)候, 不需要等待任何ACK, 直接發(fā)送
收到第一個(gè)ACK確認(rèn)應(yīng)答后, 窗口向后移動(dòng), 繼續(xù)發(fā)送第五六七八段的數(shù)據(jù)…
因?yàn)檫@個(gè)窗口不斷向后滑動(dòng), 所以叫做滑動(dòng)窗口.
操作系統(tǒng)內(nèi)核為了維護(hù)這個(gè)滑動(dòng)窗口, 需要開(kāi)辟發(fā)送緩沖區(qū)來(lái)記錄當(dāng)前還有哪些數(shù)據(jù)沒(méi)有應(yīng)答
只有ACK確認(rèn)應(yīng)答過(guò)的數(shù)據(jù), 才能從緩沖區(qū)刪掉.

如果出現(xiàn)了丟包, 那么該如何進(jìn)行重傳呢?
此時(shí)分兩種情況討論:
1, 數(shù)據(jù)包已經(jīng)收到, 但確認(rèn)應(yīng)答ACK丟了.

這種情況下, 部分ACK丟失并無(wú)大礙, 因?yàn)檫€可以通過(guò)后續(xù)的ACK來(lái)確認(rèn)對(duì)方已經(jīng)收到了哪些數(shù)據(jù)包.
2, 數(shù)據(jù)包丟失

當(dāng)某一段報(bào)文丟失之后, 發(fā)送端會(huì)一直收到 1001 這樣的ACK, 就像是在提醒發(fā)送端 “我想要的是 1001”
如果發(fā)送端主機(jī)連續(xù)三次收到了同樣一個(gè) “1001” 這樣的應(yīng)答, 就會(huì)將對(duì)應(yīng)的數(shù)據(jù) 1001 - 2000 重新發(fā)送
這個(gè)時(shí)候接收端收到了 1001 之后, 再次返回的ACK就是7001了
因?yàn)?001 - 7000接收端其實(shí)之前就已經(jīng)收到了, 被放到了接收端操作系統(tǒng)內(nèi)核的接收緩沖區(qū)中.
這種機(jī)制被稱(chēng)為 “高速重發(fā)控制” ( 也叫 “快重傳” )
流量控制
接收端處理數(shù)據(jù)的速度是有限的. 如果發(fā)送端發(fā)的太快, 導(dǎo)致接收端的緩沖區(qū)被填滿, 這個(gè)時(shí)候如果發(fā)送端繼續(xù)發(fā)送, 就會(huì)造成丟包, 進(jìn)而引起丟包重傳等一系列連鎖反應(yīng).
因此TCP支持根據(jù)接收端的處理能力, 來(lái)決定發(fā)送端的發(fā)送速度.
這個(gè)機(jī)制就叫做 流量控制(Flow Control)
接收端將自己可以接收的緩沖區(qū)大小放入 TCP 首部中的 “窗口大小” 字段,
通過(guò)ACK通知發(fā)送端;
窗口大小越大, 說(shuō)明網(wǎng)絡(luò)的吞吐量越高;
接收端一旦發(fā)現(xiàn)自己的緩沖區(qū)快滿了, 就會(huì)將窗口大小設(shè)置成一個(gè)更小的值通知給發(fā)送端;
發(fā)送端接受到這個(gè)窗口大小的通知之后, 就會(huì)減慢自己的發(fā)送速度;
如果接收端緩沖區(qū)滿了, 就會(huì)將窗口置為0;
這時(shí)發(fā)送方不再發(fā)送數(shù)據(jù), 但是需要定期發(fā)送一個(gè)窗口探測(cè)數(shù)據(jù)段, 讓接收端把窗口大小再告訴發(fā)送端.

那么接收端如何把窗口大小告訴發(fā)送端呢?
我們的TCP首部中, 有一個(gè)16位窗口大小字段, 就存放了窗口大小的信息;
16位數(shù)字最大表示65536, 那么TCP窗口最大就是65536字節(jié)么?
實(shí)際上, TCP首部40字節(jié)選項(xiàng)中還包含了一個(gè)窗口擴(kuò)大因子M, 實(shí)際窗口大小是窗口字段的值左移 M 位(左移一位相當(dāng)于乘以2).
擁塞控制
雖然TCP有了滑動(dòng)窗口這個(gè)大殺器, 能夠高效可靠地發(fā)送大量數(shù)據(jù).
但是如果在剛開(kāi)始就發(fā)送大量的數(shù)據(jù), 仍然可能引發(fā)一些問(wèn)題.
因?yàn)榫W(wǎng)絡(luò)上有很多計(jì)算機(jī), 可能當(dāng)前的網(wǎng)絡(luò)狀態(tài)已經(jīng)比較擁堵.
在不清楚當(dāng)前網(wǎng)絡(luò)狀態(tài)的情況下, 貿(mào)然發(fā)送大量數(shù)據(jù), 很有可能雪上加霜.
因此, TCP引入 慢啟動(dòng) 機(jī)制, 先發(fā)少量的數(shù)據(jù), 探探路, 摸清當(dāng)前的網(wǎng)絡(luò)擁堵?tīng)顟B(tài)以后, 再?zèng)Q定按照多大的速度傳輸數(shù)據(jù).

擁塞窗口
發(fā)送開(kāi)始的時(shí)候, 定義擁塞窗口大小為1;
每次收到一個(gè)ACK應(yīng)答, 擁塞窗口加1;
每次發(fā)送數(shù)據(jù)包的時(shí)候, 將擁塞窗口和接收端主機(jī)反饋的窗口大小做比較, 取較小的值作為實(shí)際發(fā)送的窗口
像上面這樣的擁塞窗口增長(zhǎng)速度, 是指數(shù)級(jí)別的.
“慢啟動(dòng)” 只是指初使時(shí)慢, 但是增長(zhǎng)速度非???
為了不增長(zhǎng)得那么快, 此處引入一個(gè)名詞叫做慢啟動(dòng)的閾值, 當(dāng)擁塞窗口的大小超過(guò)這個(gè)閾值的時(shí)候, 不再按照指數(shù)方式增長(zhǎng), 而是按照線性方式增長(zhǎng).

當(dāng)TCP開(kāi)始啟動(dòng)的時(shí)候, 慢啟動(dòng)閾值等于窗口最大值
在每次超時(shí)重發(fā)的時(shí)候, 慢啟動(dòng)閾值會(huì)變成原來(lái)的一半, 同時(shí)擁塞窗口置回1
少量的丟包, 我們僅僅是觸發(fā)超時(shí)重傳;
大量的丟包, 我們就認(rèn)為是網(wǎng)絡(luò)擁塞;
當(dāng)TCP通信開(kāi)始后, 網(wǎng)絡(luò)吞吐量會(huì)逐漸上升;
隨著網(wǎng)絡(luò)發(fā)生擁堵, 吞吐量會(huì)立刻下降.
擁塞控制, 歸根結(jié)底是TCP協(xié)議想盡可能快的把數(shù)據(jù)傳輸給對(duì)方, 但是又要避免給網(wǎng)絡(luò)造成太大壓力的折中方案.
延遲應(yīng)答
如果接收數(shù)據(jù)的主機(jī)立刻返回ACK應(yīng)答, 這時(shí)候返回的窗口可能比較小.
假設(shè)接收端緩沖區(qū)為1M. 一次收到了500K的數(shù)據(jù);
如果立刻應(yīng)答, 返回的窗口大小就是500K;
但實(shí)際上可能處理端處理的速度很快, 10ms之內(nèi)就把500K數(shù)據(jù)從緩沖區(qū)消費(fèi)掉了; 在這種情況下, 接收端處理還遠(yuǎn)沒(méi)有達(dá)到自己的極限, 即使窗口再放大一些, 也能處理過(guò)來(lái);
如果接收端稍微等一會(huì)兒再應(yīng)答, 比如等待200ms再應(yīng)答, 那么這個(gè)時(shí)候返回的窗口大小就是1M
窗口越大, 網(wǎng)絡(luò)吞吐量就越大, 傳輸效率就越高.
TCP的目標(biāo)是在保證網(wǎng)絡(luò)不擁堵的情況下盡量提高傳輸效率;
那么所有的數(shù)據(jù)包都可以延遲應(yīng)答么?
肯定也不是
有兩個(gè)限制
數(shù)量限制: 每隔N個(gè)包就應(yīng)答一次
時(shí)間限制: 超過(guò)最大延遲時(shí)間就應(yīng)答一次
具體的數(shù)量N和最大延遲時(shí)間, 依操作系統(tǒng)不同也有差異
一般 N 取2, 最大延遲時(shí)間取200ms
捎帶應(yīng)答
在延遲應(yīng)答的基礎(chǔ)上, 我們發(fā)現(xiàn), 很多情況下
客戶(hù)端和服務(wù)器在應(yīng)用層也是 “一發(fā)一收” 的
意味著客戶(hù)端給服務(wù)器說(shuō)了 “How are you”
服務(wù)器也會(huì)給客戶(hù)端回一個(gè) “Fine, thank you”
那么這個(gè)時(shí)候ACK就可以搭順風(fēng)車(chē), 和服務(wù)器回應(yīng)的 “Fine, thank you” 一起發(fā)送給客戶(hù)端

面向字節(jié)流
創(chuàng)建一個(gè)TCP的socket, 同時(shí)在內(nèi)核中創(chuàng)建一個(gè) 發(fā)送緩沖區(qū) 和一個(gè) 接收緩沖區(qū);
調(diào)用write時(shí), 數(shù)據(jù)會(huì)先寫(xiě)入發(fā)送緩沖區(qū)中;
如果發(fā)送的字節(jié)數(shù)太大, 會(huì)被拆分成多個(gè)TCP的數(shù)據(jù)包發(fā)出;
如果發(fā)送的字節(jié)數(shù)太小, 就會(huì)先在緩沖區(qū)里等待, 等到緩沖區(qū)大小差不多了, 或者到了其他合適的時(shí)機(jī)再發(fā)送出去;
接收數(shù)據(jù)的時(shí)候, 數(shù)據(jù)也是從網(wǎng)卡驅(qū)動(dòng)程序到達(dá)內(nèi)核的接收緩沖區(qū);
然后應(yīng)用程序可以調(diào)用read從接收緩沖區(qū)拿數(shù)據(jù);
另一方面, TCP的一個(gè)連接, 既有發(fā)送緩沖區(qū), 也有接收緩沖區(qū),
那么對(duì)于這一個(gè)連接, 既可以讀數(shù)據(jù), 也可以寫(xiě)數(shù)據(jù), 這個(gè)概念叫做 全雙工
由于緩沖區(qū)的存在, 所以TCP程序的讀和寫(xiě)不需要一一匹配
例如:
寫(xiě)100個(gè)字節(jié)的數(shù)據(jù), 可以調(diào)用一次write寫(xiě)100個(gè)字節(jié), 也可以調(diào)用100次write, 每次寫(xiě)一個(gè)字節(jié);
讀100個(gè)字節(jié)數(shù)據(jù)時(shí), 也完全不需要考慮寫(xiě)的時(shí)候是怎么寫(xiě)的, 既可以一次read 100個(gè)字節(jié), 也可以一次read一個(gè)字節(jié), 重復(fù)100次;
粘包問(wèn)題
首先要明確, 粘包問(wèn)題中的 “包”, 是指應(yīng)用層的數(shù)據(jù)包.
在TCP的協(xié)議頭中, 沒(méi)有如同UDP一樣的 “報(bào)文長(zhǎng)度” 字段
但是有一個(gè)序號(hào)字段.
站在傳輸層的角度, TCP是一個(gè)一個(gè)報(bào)文傳過(guò)來(lái)的. 按照序號(hào)排好序放在緩沖區(qū)中.
站在應(yīng)用層的角度, 看到的只是一串連續(xù)的字節(jié)數(shù)據(jù).
那么應(yīng)用程序看到了這一連串的字節(jié)數(shù)據(jù), 就不知道從哪個(gè)部分開(kāi)始到哪個(gè)部分是一個(gè)完整的應(yīng)用層數(shù)據(jù)包.
此時(shí)數(shù)據(jù)之間就沒(méi)有了邊界, 就產(chǎn)生了粘包問(wèn)題
那么如何避免粘包問(wèn)題呢?
歸根結(jié)底就是一句話, 明確兩個(gè)包之間的邊界
對(duì)于定長(zhǎng)的包
- 保證每次都按固定大小讀取即可
例如上面的Request結(jié)構(gòu), 是固定大小的, 那么就從緩沖區(qū)從頭開(kāi)始按sizeof(Request)依次讀取即可
對(duì)于變長(zhǎng)的包
- 可以在數(shù)據(jù)包的頭部, 約定一個(gè)數(shù)據(jù)包總長(zhǎng)度的字段, 從而就知道了包的結(jié)束位置
還可以在包和包之間使用明確的分隔符來(lái)作為邊界(應(yīng)用層協(xié)議, 是程序員自己來(lái)定的, 只要保證分隔符不和正文沖突即可)
對(duì)于UDP協(xié)議來(lái)說(shuō), 是否也存在 “粘包問(wèn)題” 呢?
對(duì)于UDP, 如果還沒(méi)有向上層交付數(shù)據(jù), UDP的報(bào)文長(zhǎng)度仍然存在.
同時(shí), UDP是一個(gè)一個(gè)把數(shù)據(jù)交付給應(yīng)用層的, 就有很明確的數(shù)據(jù)邊界.
站在應(yīng)用層的角度, 使用UDP的時(shí)候, 要么收到完整的UDP報(bào)文, 要么不收.
不會(huì)出現(xiàn)收到 “半個(gè)” 的情況.
TCP 異常情況
進(jìn)程終止: 進(jìn)程終止會(huì)釋放文件描述符, 仍然可以發(fā)送FIN. 和正常關(guān)閉沒(méi)有什么區(qū)別.
機(jī)器重啟: 和進(jìn)程終止的情況相同.
機(jī)器掉電/網(wǎng)線斷開(kāi): 接收端認(rèn)為連接還在, 一旦接收端有寫(xiě)入操作, 接收端發(fā)現(xiàn)連接已經(jīng)不在了, 就會(huì)進(jìn)行 reset. 即使沒(méi)有寫(xiě)入操作, TCP自己也內(nèi)置了一個(gè)保活定時(shí)器, 會(huì)定期詢(xún)問(wèn)對(duì)方是否還在. 如果對(duì)方不在, 也會(huì)把連接釋放.
另外, 應(yīng)用層的某些協(xié)議, 也有一些這樣的檢測(cè)機(jī)制.
例如HTTP長(zhǎng)連接中, 也會(huì)定期檢測(cè)對(duì)方的狀態(tài).
例如QQ, 在QQ斷線之后, 也會(huì)定期嘗試重新連接.
TCP 小結(jié)
為什么TCP這么復(fù)雜?
因?yàn)榧纫WC可靠性, 同時(shí)又要盡可能提高性能.
保證可靠性的機(jī)制
*校驗(yàn)和
*序列號(hào)(按序到達(dá))
*確認(rèn)應(yīng)答
*超時(shí)重傳
*連接管理
*流量控制
*擁塞控制
提高性能的機(jī)制
*滑動(dòng)窗口
*快速重傳
*延遲應(yīng)答
*捎帶應(yīng)答
定時(shí)器
*超時(shí)重傳定時(shí)器
*?;疃〞r(shí)器
*TIME_WAIT定時(shí)器
基于 TCP 的應(yīng)用層協(xié)議
*HTTP
*HTTPS
*SSH
*Telnet
*FTP
*SMTP
*…
當(dāng)然, 也包括我們自己寫(xiě)TCP程序時(shí)自定義的應(yīng)用層協(xié)議
TCP 和 UDP 對(duì)比
我們說(shuō)了TCP是可靠連接, 那么是不是TCP一定就優(yōu)于UDP呢?
TCP和UDP之間的優(yōu)點(diǎn)和缺點(diǎn), 不能簡(jiǎn)單絕對(duì)地進(jìn)行比較
TCP用于可靠傳輸?shù)那闆r, 應(yīng)用于文件傳輸, 重要狀態(tài)更新等場(chǎng)景
UDP用于對(duì)高速傳輸和實(shí)時(shí)性要求較高的通信領(lǐng)域
例如, 早期的QQ, 視頻傳輸?shù)? 另外UDP可以用于廣播
歸根結(jié)底, TCP和UDP都是一種工具, 什么時(shí)機(jī)用, 具體怎么用, 還是要根據(jù)具體的需求場(chǎng)景去決定.