win2008 查询 tcp连接失败_TCP详解(转)
一直以來寫過好些TCP連接的程序,各種情況都處理過,但是對(duì)具體的TCP協(xié)議卻缺少完整性的學(xué)習(xí)。在網(wǎng)上找了一篇覺得特別棒的文章,這里做下搬運(yùn)工,保存下來,方便以后自己翻閱。
通過閱讀發(fā)現(xiàn)一個(gè)有意思的事情,其實(shí)TCP的客戶端是會(huì)把服務(wù)端給弄卡住的,客戶端一直不調(diào)用recv,當(dāng)數(shù)據(jù)滿了后,服務(wù)器就發(fā)不出來數(shù)據(jù)了,會(huì)一直發(fā)送查詢窗口信息,具體看下面的滑動(dòng)窗口那一段,我還做了一份代碼測試了一下,確實(shí)如描述一樣。
鏈接: https://pan.baidu.com/s/1mNyZiTLbmYq6Fnf-r6piqg 提取碼: nnhv
所以這體現(xiàn)了心跳的作用,當(dāng)長時(shí)間沒收到數(shù)據(jù)的時(shí)候,直接斷開。
當(dāng)然也可以用setsockopt 來設(shè)置下發(fā)送超時(shí)時(shí)間。假如send失敗就斷開
不過一般還是用心跳吧,也可以兩者都用
看完此文該就有了比較系統(tǒng)的了解了
以下是很值得欣賞的原文:
原文鏈接:https://blog.csdn.net/sinat_36629696/article/details/80740678
TCP協(xié)議
TCP協(xié)議全稱: 傳輸控制協(xié)議, 顧名思義, 就是要對(duì)數(shù)據(jù)的傳輸進(jìn)行一定的控制.
先來看看它的報(bào)頭
我們來分析分析每部分的含義和作用
源端口號(hào)/目的端口號(hào): 表示數(shù)據(jù)從哪個(gè)進(jìn)程來, 到哪個(gè)進(jìn)程去.
32位序號(hào):
4位首部長度: 表示該tcp報(bào)頭有多少個(gè)4字節(jié)(32個(gè)bit)
6位保留: 顧名思義, 先保留著, 以防萬一
6位標(biāo)志位
URG: 標(biāo)識(shí)緊急指針是否有效
ACK: 標(biāo)識(shí)確認(rèn)序號(hào)是否有效
PSH: 用來提示接收端應(yīng)用程序立刻將數(shù)據(jù)從tcp緩沖區(qū)讀走
RST: 要求重新建立連接. 我們把含有RST標(biāo)識(shí)的報(bào)文稱為復(fù)位報(bào)文段
SYN: 請(qǐng)求建立連接. 我們把含有SYN標(biāo)識(shí)的報(bào)文稱為同步報(bào)文段
FIN: 通知對(duì)端, 本端即將關(guān)閉. 我們把含有FIN標(biāo)識(shí)的報(bào)文稱為結(jié)束報(bào)文段
16位窗口大小:
16位檢驗(yàn)和: 由發(fā)送端填充, 檢驗(yàn)形式有CRC校驗(yàn)等. 如果接收端校驗(yàn)不通過, 則認(rèn)為數(shù)據(jù)有問題. 此處的校驗(yàn)和不光包含TCP首部, 也包含TCP數(shù)據(jù)部分.
16位緊急指針: 用來標(biāo)識(shí)哪部分?jǐn)?shù)據(jù)是緊急數(shù)據(jù).
選項(xiàng)和數(shù)據(jù)暫時(shí)忽略
連接管理機(jī)制
正常情況下, tcp需要經(jīng)過三次握手建立連接, 四次揮手?jǐn)嚅_連接.
那么什么是三次握手? 什么是四次揮手呢?
三次握手
第一次:
客戶端 - - > 服務(wù)器 此時(shí)服務(wù)器知道了客戶端要建立連接了
第二次:
客戶端 < - - 服務(wù)器 此時(shí)客戶端知道服務(wù)器收到連接請(qǐng)求了
第三次:
客戶端 - - > 服務(wù)器 此時(shí)服務(wù)器知道客戶端收到了自己的回應(yīng)
到這里, 就可以認(rèn)為客戶端與服務(wù)器已經(jīng)建立了連接.
再來看個(gè)圖.
剛開始, 客戶端和服務(wù)器都處于 CLOSE 狀態(tài).
此時(shí), 客戶端向服務(wù)器主動(dòng)發(fā)出連接請(qǐng)求, 服務(wù)器被動(dòng)接受連接請(qǐng)求.
1, TCP服務(wù)器進(jìn)程先創(chuàng)建傳輸控制塊TCB, 時(shí)刻準(zhǔn)備接受客戶端進(jìn)程的連接請(qǐng)求, 此時(shí)服務(wù)器就進(jìn)入了 LISTEN(監(jiān)聽)狀態(tài)
2, TCP客戶端進(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客戶端進(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客戶端進(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連接建立,客戶端進(jìn)入ESTABLISHED(已建立連接)狀態(tài)。當(dāng)服務(wù)器收到客戶端的確認(rèn)后也進(jìn)入ESTABLISHED狀態(tài),此后雙方就可以開始通信了。
為什么不用兩次?
主要是為了防止已經(jīng)失效的連接請(qǐng)求報(bào)文突然又傳送到了服務(wù)器,從而產(chǎn)生錯(cuò)誤。如果使用的是兩次握手建立連接,假設(shè)有這樣一種場景,客戶端發(fā)送的第一個(gè)請(qǐng)求連接并且沒有丟失,只是因?yàn)樵诰W(wǎng)絡(luò)中滯留的時(shí)間太長了,由于TCP的客戶端遲遲沒有收到確認(rèn)報(bào)文,以為服務(wù)器沒有收到,此時(shí)重新向服務(wù)器發(fā)送這條報(bào)文,此后客戶端和服務(wù)器經(jīng)過兩次握手完成連接,傳輸數(shù)據(jù),然后關(guān)閉連接。此時(shí)之前滯留的那一次請(qǐng)求連接,因?yàn)榫W(wǎng)絡(luò)通暢了, 到達(dá)了服務(wù)器,這個(gè)報(bào)文本該是失效的,但是,兩次握手的機(jī)制將會(huì)讓客戶端和服務(wù)器再次建立連接,這將導(dǎo)致不必要的錯(cuò)誤和資源的費(fèi)。
如果采用的是三次握手,就算是那一次失效的報(bào)文傳送過來了,服務(wù)端接受到了那條失效報(bào)文并且回復(fù)了確認(rèn)報(bào)文,但是客戶端不會(huì)再次發(fā)出確認(rèn)。由于服務(wù)器收不到確認(rèn),就知道客戶端并沒有請(qǐng)求連接。
為什么不用四次?
因?yàn)槿我呀?jīng)可以滿足需要了, 四次就多余了.
再來看看何為四次揮手.
數(shù)據(jù)傳輸完畢后,雙方都可以釋放連接.
此時(shí)客戶端和服務(wù)器都是處于ESTABLISHED狀態(tài),然后客戶端主動(dòng)斷開連接,服務(wù)器被動(dòng)斷開連接.
1, 客戶端進(jìn)程發(fā)出連接釋放報(bào)文,并且停止發(fā)送數(shù)據(jù)。
釋放數(shù)據(jù)報(bào)文首部,FIN=1,其序列號(hào)為seq=u(等于前面已經(jīng)傳送過來的數(shù)據(jù)的最后一個(gè)字節(jié)的序號(hào)加1),此時(shí)客戶端進(jìn)入FIN-WAIT-1(終止等待1)狀態(tài)。 TCP規(guī)定,FIN報(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)程,客戶端向服務(wù)器的方向就釋放了,這時(shí)候處于半關(guān)閉狀態(tài),即客戶端已經(jīng)沒有數(shù)據(jù)要發(fā)送了,但是服務(wù)器若發(fā)送數(shù)據(jù),客戶端依然要接受。這個(gè)狀態(tài)還要持續(xù)一段時(shí)間,也就是整個(gè)CLOSE-WAIT狀態(tài)持續(xù)的時(shí)間。
3, 客戶端收到服務(wù)器的確認(rèn)請(qǐng)求后,此時(shí)客戶端就進(jìn)入FIN-WAIT-2(終止等待2)狀態(tài),等待服務(wù)器發(fā)送連接釋放報(bào)文(在這之前還需要接受服務(wù)器發(fā)送的最終數(shù)據(jù))
4, 服務(wù)器將最后的數(shù)據(jù)發(fā)送完畢后,就向客戶端發(fā)送連接釋放報(bào)文,FIN=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),等待客戶端的確認(rèn)。
5, 客戶端收到服務(wù)器的連接釋放報(bào)文后,必須發(fā)出確認(rèn),ACK=1,確認(rèn)序號(hào)為w+1,而自己的序列號(hào)是u+1,此時(shí),客戶端就進(jìn)入了TIME-WAIT(時(shí)間等待)狀態(tài)。注意此時(shí)TCP連接還沒有釋放,必須經(jīng)過2?MSL(最長報(bào)文段壽命)的時(shí)間后,當(dāng)客戶端撤銷相應(yīng)的TCB后,才進(jìn)入CLOSED狀態(tài)。
6, 服務(wù)器只要收到了客戶端發(fā)出的確認(rèn),立即進(jìn)入CLOSED狀態(tài)。同樣,撤銷TCB后,就結(jié)束了這次的TCP連接。可以看到,服務(wù)器結(jié)束TCP連接的時(shí)間要比客戶端早一些。
再來看一張圖.
為什么最后客戶端還要等待 2*MSL的時(shí)間呢?
MSL(Maximum Segment Lifetime),TCP允許不同的實(shí)現(xiàn)可以設(shè)置不同的MSL值。
第一,保證客戶端發(fā)送的最后一個(gè)ACK報(bào)文能夠到達(dá)服務(wù)器,因?yàn)檫@個(gè)ACK報(bào)文可能丟失,站在服務(wù)器的角度看來,我已經(jīng)發(fā)送了FIN+ACK報(bào)文請(qǐng)求斷開了,客戶端還沒有給我回應(yīng),應(yīng)該是我發(fā)送的請(qǐng)求斷開報(bào)文它沒有收到,于是服務(wù)器又會(huì)重新發(fā)送一次,而客戶端就能在這個(gè)2MSL時(shí)間段內(nèi)收到這個(gè)重傳的報(bào)文,接著給出回應(yīng)報(bào)文,并且會(huì)重啟2MSL計(jì)時(shí)器。
第二,防止類似與“三次握手”中提到了的“已經(jīng)失效的連接請(qǐng)求報(bào)文段”出現(xiàn)在本連接中。客戶端發(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ā)送給客戶端。 而關(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ì)方來表示同意現(xiàn)在關(guān)閉連接,因此,己方ACK和FIN一般都會(huì)分開發(fā)送,從而導(dǎo)致多了一次。
如果已經(jīng)建立了連接, 但是客戶端突發(fā)故障了怎么辦?
TCP設(shè)有一個(gè)保活計(jì)時(shí)器,顯然,客戶端如果出現(xiàn)故障,服務(wù)器不能一直等下去,白白浪費(fèi)資源。服務(wù)器每收到一次客戶端的請(qǐng)求后都會(huì)重新復(fù)位這個(gè)計(jì)時(shí)器,時(shí)間通常是設(shè)置為2小時(shí),若兩小時(shí)還沒有收到客戶端的任何數(shù)據(jù),服務(wù)器就會(huì)發(fā)送一個(gè)探測報(bào)文段,以后每隔75分鐘發(fā)送一次。若一連發(fā)送10個(gè)探測報(bào)文仍然沒反應(yīng),服務(wù)器就認(rèn)為客戶端出了故障,接著就關(guān)閉連接。
理解TIME_WAIT狀態(tài)
可以做一個(gè)實(shí)驗(yàn), 先運(yùn)行server, 再運(yùn)行client連接server, 然后斷開server, 再立馬運(yùn)行server.
我們會(huì)發(fā)現(xiàn):
綁定的時(shí)候出了問題.
這是因?yàn)?雖然server應(yīng)用程序終止了,但TCP協(xié)議層的連接并沒有完全斷開,因此不能再次監(jiān)聽綁定同樣的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)聽同樣的server端口
MSL在RFC1122中規(guī)定為兩分鐘,但是各操作系統(tǒng)的實(shí)現(xiàn)不同, 在Centos7上默認(rèn)配置的值是60s;
可以通過 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看MSL的值
解決TIME_WAIT引起的bind失敗問題
在server的TCP連接沒有完全斷開之前不允許重新監(jiān)聽, 某些情況下可能是不合理的.
比如:
服務(wù)器需要處理非常大量的客戶端的連接(每個(gè)連接的生存時(shí)間可能很短, 但是每秒都有大量的客戶端來請(qǐng)求).
這個(gè)時(shí)候如果由服務(wù)器端主動(dòng)關(guān)閉連接(比如某些客戶端不活躍, 就需要被服務(wù)器端主動(dòng)清理掉), 就會(huì)產(chǎn)生大量TIME_WAIT連接.
由于我們的請(qǐng)求量很大, 就可能導(dǎo)致TIME_WAIT的連接數(shù)很多, 導(dǎo)致服務(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ù); 下一次你要從哪里開始發(fā).
比如, 客戶端向服務(wù)器發(fā)送了1005字節(jié)的數(shù)據(jù), 服務(wù)器返回給客戶端的確認(rèn)序號(hào)是1003, 那么說明服務(wù)器只收到了1-1002的數(shù)據(jù).
1003, 1004, 1005都沒收到.
此時(shí)客戶端就會(huì)從1003開始重發(fā).
超時(shí)重傳機(jī)制
主機(jī)A發(fā)送數(shù)據(jù)給B之后, 可能因?yàn)榫W(wǎng)絡(luò)擁堵等原因, 數(shù)據(jù)無法到達(dá)主機(jī)B
如果主機(jī)A在一個(gè)特定時(shí)間間隔內(nèi)沒有收到B發(fā)來的確認(rèn)應(yīng)答, 就會(huì)進(jìn)行重發(fā)
但是主機(jī)A沒收到確認(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í)間的長短, 隨著網(wǎng)絡(luò)環(huán)境的不同, 是有差異的.
如果超時(shí)時(shí)間設(shè)的太長, 會(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)答, 等待 2*500ms 后再進(jìn)行重傳. 如果仍然得不到應(yīng)答, 等待 4*500ms 進(jìn)行重傳.
依次類推, 以指數(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í)間較長的時(shí)候.
那么我們可不可以一次發(fā)送多個(gè)數(shù)據(jù)段呢?
例如這樣:
一個(gè)概念: 窗口
窗口大小指的是無需等待確認(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)窗口, 需要開辟發(fā)送緩沖區(qū)來記錄當(dāng)前還有哪些數(shù)據(jù)沒有應(yīng)答
只有ACK確認(rèn)應(yīng)答過的數(shù)據(jù), 才能從緩沖區(qū)刪掉.
如果出現(xiàn)了丟包, 那么該如何進(jìn)行重傳呢?
此時(shí)分兩種情況討論:
1, 數(shù)據(jù)包已經(jīng)收到, 但確認(rèn)應(yīng)答ACK丟了.
這種情況下, 部分ACK丟失并無大礙, 因?yàn)檫€可以通過后續(xù)的ACK來確認(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ī)制被稱為 “高速重發(fā)控制” ( 也叫 “快重傳” )
流量控制
接收端處理數(shù)據(jù)的速度是有限的. 如果發(fā)送端發(fā)的太快, 導(dǎo)致接收端的緩沖區(qū)被填滿, 這個(gè)時(shí)候如果發(fā)送端繼續(xù)發(fā)送, 就會(huì)造成丟包, 進(jìn)而引起丟包重傳等一系列連鎖反應(yīng).
因此TCP支持根據(jù)接收端的處理能力, 來決定發(fā)送端的發(fā)送速度.
這個(gè)機(jī)制就叫做 流量控制(Flow Control)
接收端將自己可以接收的緩沖區(qū)大小放入 TCP 首部中的 “窗口大小” 字段,
通過ACK通知發(fā)送端;
窗口大小越大, 說明網(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è)窗口探測數(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ù).
但是如果在剛開始就發(fā)送大量的數(shù)據(jù), 仍然可能引發(fā)一些問題.
因?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ài)以后, 再?zèng)Q定按照多大的速度傳輸數(shù)據(jù).
在此引入一個(gè)概念 擁塞窗口
發(fā)送開始的時(shí)候, 定義擁塞窗口大小為1;
每次收到一個(gè)ACK應(yīng)答, 擁塞窗口加1;
每次發(fā)送數(shù)據(jù)包的時(shí)候, 將擁塞窗口和接收端主機(jī)反饋的窗口大小做比較, 取較小的值作為實(shí)際發(fā)送的窗口
像上面這樣的擁塞窗口增長速度, 是指數(shù)級(jí)別的.
“慢啟動(dòng)” 只是指初使時(shí)慢, 但是增長速度非常快.
為了不增長得那么快, 此處引入一個(gè)名詞叫做慢啟動(dòng)的閾值, 當(dāng)擁塞窗口的大小超過這個(gè)閾值的時(shí)候, 不再按照指數(shù)方式增長, 而是按照線性方式增長.
當(dāng)TCP開始啟動(dòng)的時(shí)候, 慢啟動(dòng)閾值等于窗口最大值
在每次超時(shí)重發(fā)的時(shí)候, 慢啟動(dòng)閾值會(huì)變成原來的一半, 同時(shí)擁塞窗口置回1
少量的丟包, 我們僅僅是觸發(fā)超時(shí)重傳;
大量的丟包, 我們就認(rèn)為是網(wǎng)絡(luò)擁塞;
當(dāng)TCP通信開始后, 網(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)沒有達(dá)到自己的極限, 即使窗口再放大一些, 也能處理過來;
如果接收端稍微等一會(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í)間限制: 超過最大延遲時(shí)間就應(yīng)答一次
具體的數(shù)量N和最大延遲時(shí)間, 依操作系統(tǒng)不同也有差異
一般 N 取2, 最大延遲時(shí)間取200ms
捎帶應(yīng)答
在延遲應(yīng)答的基礎(chǔ)上, 我們發(fā)現(xiàn), 很多情況下
客戶端和服務(wù)器在應(yīng)用層也是 “一發(fā)一收” 的
意味著客戶端給服務(wù)器說了 “How are you”
服務(wù)器也會(huì)給客戶端回一個(gè) “Fine, thank you”
那么這個(gè)時(shí)候ACK就可以搭順風(fēng)車, 和服務(wù)器回應(yīng)的 “Fine, thank you” 一起發(fā)送給客戶端
面向字節(jié)流
創(chuàng)建一個(gè)TCP的socket, 同時(shí)在內(nèi)核中創(chuàng)建一個(gè) 發(fā)送緩沖區(qū) 和一個(gè) 接收緩沖區(qū);
調(diào)用write時(shí), 數(shù)據(jù)會(huì)先寫入發(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ù), 也可以寫數(shù)據(jù), 這個(gè)概念叫做 全雙工
由于緩沖區(qū)的存在, 所以TCP程序的讀和寫不需要一一匹配
例如:
寫100個(gè)字節(jié)的數(shù)據(jù), 可以調(diào)用一次write寫100個(gè)字節(jié), 也可以調(diào)用100次write, 每次寫一個(gè)字節(jié);
讀100個(gè)字節(jié)數(shù)據(jù)時(shí), 也完全不需要考慮寫的時(shí)候是怎么寫的, 既可以一次read 100個(gè)字節(jié), 也可以一次read一個(gè)字節(jié), 重復(fù)100次;
粘包問題
首先要明確, 粘包問題中的 “包”, 是指應(yīng)用層的數(shù)據(jù)包.
在TCP的協(xié)議頭中, 沒有如同UDP一樣的 “報(bào)文長度” 字段
但是有一個(gè)序號(hào)字段.
站在傳輸層的角度, TCP是一個(gè)一個(gè)報(bào)文傳過來的. 按照序號(hào)排好序放在緩沖區(qū)中.
站在應(yīng)用層的角度, 看到的只是一串連續(xù)的字節(jié)數(shù)據(jù).
那么應(yīng)用程序看到了這一連串的字節(jié)數(shù)據(jù), 就不知道從哪個(gè)部分開始到哪個(gè)部分是一個(gè)完整的應(yīng)用層數(shù)據(jù)包.
此時(shí)數(shù)據(jù)之間就沒有了邊界, 就產(chǎn)生了粘包問題
那么如何避免粘包問題呢?
歸根結(jié)底就是一句話, 明確兩個(gè)包之間的邊界
對(duì)于定長的包
- 保證每次都按固定大小讀取即可
例如上面的Request結(jié)構(gòu), 是固定大小的, 那么就從緩沖區(qū)從頭開始按sizeof(Request)依次讀取即可
對(duì)于變長的包
- 可以在數(shù)據(jù)包的頭部, 約定一個(gè)數(shù)據(jù)包總長度的字段, 從而就知道了包的結(jié)束位置
還可以在包和包之間使用明確的分隔符來作為邊界(應(yīng)用層協(xié)議, 是程序員自己來定的, 只要保證分隔符不和正文沖突即可)
對(duì)于UDP協(xié)議來說, 是否也存在 “粘包問題” 呢?
對(duì)于UDP, 如果還沒有向上層交付數(shù)據(jù), UDP的報(bào)文長度仍然存在.
同時(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)閉沒有什么區(qū)別.
機(jī)器重啟: 和進(jìn)程終止的情況相同.
機(jī)器掉電/網(wǎng)線斷開: 接收端認(rèn)為連接還在, 一旦接收端有寫入操作, 接收端發(fā)現(xiàn)連接已經(jīng)不在了, 就會(huì)進(jìn)行 reset. 即使沒有寫入操作, TCP自己也內(nèi)置了一個(gè)保活定時(shí)器, 會(huì)定期詢問對(duì)方是否還在. 如果對(duì)方不在, 也會(huì)把連接釋放.
另外, 應(yīng)用層的某些協(xié)議, 也有一些這樣的檢測機(jī)制.
例如HTTP長連接中, 也會(huì)定期檢測對(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í)器
保活定時(shí)器
TIME_WAIT定時(shí)器
基于 TCP 的應(yīng)用層協(xié)議
HTTP
HTTPS
SSH
Telnet
FTP
SMTP
…
當(dāng)然, 也包括我們自己寫TCP程序時(shí)自定義的應(yīng)用層協(xié)議
TCP 和 UDP 對(duì)比
我們說了TCP是可靠連接, 那么是不是TCP一定就優(yōu)于UDP呢?
TCP和UDP之間的優(yōu)點(diǎn)和缺點(diǎn), 不能簡單絕對(duì)地進(jìn)行比較
TCP用于可靠傳輸?shù)那闆r, 應(yīng)用于文件傳輸, 重要狀態(tài)更新等場景
UDP用于對(duì)高速傳輸和實(shí)時(shí)性要求較高的通信領(lǐng)域
例如, 早期的QQ, 視頻傳輸?shù)? 另外UDP可以用于廣播
歸根結(jié)底, TCP和UDP都是一種工具, 什么時(shí)機(jī)用, 具體怎么用, 還是要根據(jù)具體的需求場景去決定.
————————————————
版權(quán)聲明:本文為CSDN博主「rugu_xxx」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/sinat_36629696/article/details/80740678
總結(jié)
以上是生活随笔為你收集整理的win2008 查询 tcp连接失败_TCP详解(转)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DDD 领域驱动设计落地实践:六步拆解
- 下一篇: 删除共享内存_进程通信专题之 共享内存