win2008 查询 tcp连接失败_TCP详解(转)
一直以來寫過好些TCP連接的程序,各種情況都處理過,但是對具體的TCP協議卻缺少完整性的學習。在網上找了一篇覺得特別棒的文章,這里做下搬運工,保存下來,方便以后自己翻閱。
通過閱讀發現一個有意思的事情,其實TCP的客戶端是會把服務端給弄卡住的,客戶端一直不調用recv,當數據滿了后,服務器就發不出來數據了,會一直發送查詢窗口信息,具體看下面的滑動窗口那一段,我還做了一份代碼測試了一下,確實如描述一樣。
鏈接: https://pan.baidu.com/s/1mNyZiTLbmYq6Fnf-r6piqg 提取碼: nnhv
所以這體現了心跳的作用,當長時間沒收到數據的時候,直接斷開。
當然也可以用setsockopt 來設置下發送超時時間。假如send失敗就斷開
不過一般還是用心跳吧,也可以兩者都用
看完此文該就有了比較系統的了解了
以下是很值得欣賞的原文:
原文鏈接:https://blog.csdn.net/sinat_36629696/article/details/80740678
TCP協議
TCP協議全稱: 傳輸控制協議, 顧名思義, 就是要對數據的傳輸進行一定的控制.
先來看看它的報頭
我們來分析分析每部分的含義和作用
源端口號/目的端口號: 表示數據從哪個進程來, 到哪個進程去.
32位序號:
4位首部長度: 表示該tcp報頭有多少個4字節(32個bit)
6位保留: 顧名思義, 先保留著, 以防萬一
6位標志位
URG: 標識緊急指針是否有效
ACK: 標識確認序號是否有效
PSH: 用來提示接收端應用程序立刻將數據從tcp緩沖區讀走
RST: 要求重新建立連接. 我們把含有RST標識的報文稱為復位報文段
SYN: 請求建立連接. 我們把含有SYN標識的報文稱為同步報文段
FIN: 通知對端, 本端即將關閉. 我們把含有FIN標識的報文稱為結束報文段
16位窗口大小:
16位檢驗和: 由發送端填充, 檢驗形式有CRC校驗等. 如果接收端校驗不通過, 則認為數據有問題. 此處的校驗和不光包含TCP首部, 也包含TCP數據部分.
16位緊急指針: 用來標識哪部分數據是緊急數據.
選項和數據暫時忽略
連接管理機制
正常情況下, tcp需要經過三次握手建立連接, 四次揮手斷開連接.
那么什么是三次握手? 什么是四次揮手呢?
三次握手
第一次:
客戶端 - - > 服務器 此時服務器知道了客戶端要建立連接了
第二次:
客戶端 < - - 服務器 此時客戶端知道服務器收到連接請求了
第三次:
客戶端 - - > 服務器 此時服務器知道客戶端收到了自己的回應
到這里, 就可以認為客戶端與服務器已經建立了連接.
再來看個圖.
剛開始, 客戶端和服務器都處于 CLOSE 狀態.
此時, 客戶端向服務器主動發出連接請求, 服務器被動接受連接請求.
1, TCP服務器進程先創建傳輸控制塊TCB, 時刻準備接受客戶端進程的連接請求, 此時服務器就進入了 LISTEN(監聽)狀態
2, TCP客戶端進程也是先創建傳輸控制塊TCB, 然后向服務器發出連接請求報文,此時報文首部中的同步標志位SYN=1, 同時選擇一個初始序列號 seq = x, 此時,TCP客戶端進程進入了 SYN-SENT(同步已發送狀態)狀態。TCP規定, SYN報文段(SYN=1的報文段)不能攜帶數據,但需要消耗掉一個序號。
3, TCP服務器收到請求報文后, 如果同意連接, 則發出確認報文。確認報文中的 ACK=1, SYN=1, 確認序號是 x+1, 同時也要為自己初始化一個序列號 seq = y, 此時, TCP服務器進程進入了SYN-RCVD(同步收到)狀態。這個報文也不能攜帶數據, 但是同樣要消耗一個序號。
4, TCP客戶端進程收到確認后還, 要向服務器給出確認。確認報文的ACK=1,確認序號是 y+1,自己的序列號是 x+1.
5, 此時,TCP連接建立,客戶端進入ESTABLISHED(已建立連接)狀態。當服務器收到客戶端的確認后也進入ESTABLISHED狀態,此后雙方就可以開始通信了。
為什么不用兩次?
主要是為了防止已經失效的連接請求報文突然又傳送到了服務器,從而產生錯誤。如果使用的是兩次握手建立連接,假設有這樣一種場景,客戶端發送的第一個請求連接并且沒有丟失,只是因為在網絡中滯留的時間太長了,由于TCP的客戶端遲遲沒有收到確認報文,以為服務器沒有收到,此時重新向服務器發送這條報文,此后客戶端和服務器經過兩次握手完成連接,傳輸數據,然后關閉連接。此時之前滯留的那一次請求連接,因為網絡通暢了, 到達了服務器,這個報文本該是失效的,但是,兩次握手的機制將會讓客戶端和服務器再次建立連接,這將導致不必要的錯誤和資源的費。
如果采用的是三次握手,就算是那一次失效的報文傳送過來了,服務端接受到了那條失效報文并且回復了確認報文,但是客戶端不會再次發出確認。由于服務器收不到確認,就知道客戶端并沒有請求連接。
為什么不用四次?
因為三次已經可以滿足需要了, 四次就多余了.
再來看看何為四次揮手.
數據傳輸完畢后,雙方都可以釋放連接.
此時客戶端和服務器都是處于ESTABLISHED狀態,然后客戶端主動斷開連接,服務器被動斷開連接.
1, 客戶端進程發出連接釋放報文,并且停止發送數據。
釋放數據報文首部,FIN=1,其序列號為seq=u(等于前面已經傳送過來的數據的最后一個字節的序號加1),此時客戶端進入FIN-WAIT-1(終止等待1)狀態。 TCP規定,FIN報文段即使不攜帶數據,也要消耗一個序號。
2, 服務器收到連接釋放報文,發出確認報文,ACK=1,確認序號為 u+1,并且帶上自己的序列號seq=v,此時服務端就進入了CLOSE-WAIT(關閉等待)狀態。
TCP服務器通知高層的應用進程,客戶端向服務器的方向就釋放了,這時候處于半關閉狀態,即客戶端已經沒有數據要發送了,但是服務器若發送數據,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。
3, 客戶端收到服務器的確認請求后,此時客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送連接釋放報文(在這之前還需要接受服務器發送的最終數據)
4, 服務器將最后的數據發送完畢后,就向客戶端發送連接釋放報文,FIN=1,確認序號為v+1,由于在半關閉狀態,服務器很可能又發送了一些數據,假定此時的序列號為seq=w,此時,服務器就進入了LAST-ACK(最后確認)狀態,等待客戶端的確認。
5, 客戶端收到服務器的連接釋放報文后,必須發出確認,ACK=1,確認序號為w+1,而自己的序列號是u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。注意此時TCP連接還沒有釋放,必須經過2?MSL(最長報文段壽命)的時間后,當客戶端撤銷相應的TCB后,才進入CLOSED狀態。
6, 服務器只要收到了客戶端發出的確認,立即進入CLOSED狀態。同樣,撤銷TCB后,就結束了這次的TCP連接。可以看到,服務器結束TCP連接的時間要比客戶端早一些。
再來看一張圖.
為什么最后客戶端還要等待 2*MSL的時間呢?
MSL(Maximum Segment Lifetime),TCP允許不同的實現可以設置不同的MSL值。
第一,保證客戶端發送的最后一個ACK報文能夠到達服務器,因為這個ACK報文可能丟失,站在服務器的角度看來,我已經發送了FIN+ACK報文請求斷開了,客戶端還沒有給我回應,應該是我發送的請求斷開報文它沒有收到,于是服務器又會重新發送一次,而客戶端就能在這個2MSL時間段內收到這個重傳的報文,接著給出回應報文,并且會重啟2MSL計時器。
第二,防止類似與“三次握手”中提到了的“已經失效的連接請求報文段”出現在本連接中。客戶端發送完最后一個確認報文后,在這個2MSL時間中,就可以使本連接持續的時間內所產生的所有報文段都從網絡中消失。這樣新的連接中不會出現舊連接的請求報文。
為什么建立連接是三次握手,關閉連接確是四次揮手呢?
建立連接的時候, 服務器在LISTEN狀態下,收到建立連接請求的SYN報文后,把ACK和SYN放在一個報文里發送給客戶端。 而關閉連接時,服務器收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,而自己也未必全部數據都發送給對方了,所以己方可以立即關閉,也可以發送一些數據給對方后,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發送,從而導致多了一次。
如果已經建立了連接, 但是客戶端突發故障了怎么辦?
TCP設有一個保活計時器,顯然,客戶端如果出現故障,服務器不能一直等下去,白白浪費資源。服務器每收到一次客戶端的請求后都會重新復位這個計時器,時間通常是設置為2小時,若兩小時還沒有收到客戶端的任何數據,服務器就會發送一個探測報文段,以后每隔75分鐘發送一次。若一連發送10個探測報文仍然沒反應,服務器就認為客戶端出了故障,接著就關閉連接。
理解TIME_WAIT狀態
可以做一個實驗, 先運行server, 再運行client連接server, 然后斷開server, 再立馬運行server.
我們會發現:
綁定的時候出了問題.
這是因為,雖然server應用程序終止了,但TCP協議層的連接并沒有完全斷開,因此不能再次監聽綁定同樣的server端口.
TCP協議規定,主動關閉連接的一方要處于TIME_ WAIT狀態,等待2*MSL(maximum segment lifetime)的時間后才能回到CLOSED狀態.
我們使用Ctrl-C終止了server, 所以server是主動關閉連接的一方, 在TIME_WAIT期間仍然不能再次監聽同樣的server端口
MSL在RFC1122中規定為兩分鐘,但是各操作系統的實現不同, 在Centos7上默認配置的值是60s;
可以通過 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看MSL的值
解決TIME_WAIT引起的bind失敗問題
在server的TCP連接沒有完全斷開之前不允許重新監聽, 某些情況下可能是不合理的.
比如:
服務器需要處理非常大量的客戶端的連接(每個連接的生存時間可能很短, 但是每秒都有大量的客戶端來請求).
這個時候如果由服務器端主動關閉連接(比如某些客戶端不活躍, 就需要被服務器端主動清理掉), 就會產生大量TIME_WAIT連接.
由于我們的請求量很大, 就可能導致TIME_WAIT的連接數很多, 導致服務器的端口不夠用, 無法處理新的連接.
解決方法:
- 使用setsockopt()設置socket描述符的選項SO_REUSEADDR為1, 表示允許創建端口號相同但IP地址不同的多個socket描述符.
用法:
在server代碼的socket()和bind()調用之間插入如下代碼
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
確認應答機制(ACK機制)
TCP將每個字節的數據都進行了編號, 即為序列號.
每一個ACK都帶有對應的確認序列號, 意思是告訴發送者, 我已經收到了哪些數據; 下一次你要從哪里開始發.
比如, 客戶端向服務器發送了1005字節的數據, 服務器返回給客戶端的確認序號是1003, 那么說明服務器只收到了1-1002的數據.
1003, 1004, 1005都沒收到.
此時客戶端就會從1003開始重發.
超時重傳機制
主機A發送數據給B之后, 可能因為網絡擁堵等原因, 數據無法到達主機B
如果主機A在一個特定時間間隔內沒有收到B發來的確認應答, 就會進行重發
但是主機A沒收到確認應答也可能是ACK丟失了.
這種情況下, 主機B會收到很多重復數據.
那么TCP協議需要識別出哪些包是重復的, 并且把重復的丟棄.
這時候利用前面提到的序列號, 就可以很容易做到去重.
超時時間如何確定?
最理想的情況下, 找到一個最小的時間, 保證 “確認應答一定能在這個時間內返回”.
但是這個時間的長短, 隨著網絡環境的不同, 是有差異的.
如果超時時間設的太長, 會影響整體的重傳效率; 如果超時時間設的太短, 有可能會頻繁發送重復的包.
TCP為了保證任何環境下都能保持較高性能的通信, 因此會動態計算這個最大超時時間.
Linux中(BSD Unix和Windows也是如此), 超時以500ms為一個單位進行控制, 每次判定超時重發的超時時間都是500ms的整數倍.
如果重發一次之后, 仍然得不到應答, 等待 2*500ms 后再進行重傳. 如果仍然得不到應答, 等待 4*500ms 進行重傳.
依次類推, 以指數形式遞增. 累計到一定的重傳次數, TCP認為網絡異常或者對端主機出現異常, 強制關閉連接.
滑動窗口
剛才我們討論了確認應答機制, 對每一個發送的數據段, 都要給一個ACK確認應答. 收到ACK后再發送下一個數據段.
這樣做有一個比較大的缺點, 就是性能較差. 尤其是數據往返時間較長的時候.
那么我們可不可以一次發送多個數據段呢?
例如這樣:
一個概念: 窗口
窗口大小指的是無需等待確認應答就可以繼續發送數據的最大值.
上圖的窗口大小就是4000個字節 (四個段).
發送前四個段的時候, 不需要等待任何ACK, 直接發送
收到第一個ACK確認應答后, 窗口向后移動, 繼續發送第五六七八段的數據…
因為這個窗口不斷向后滑動, 所以叫做滑動窗口.
操作系統內核為了維護這個滑動窗口, 需要開辟發送緩沖區來記錄當前還有哪些數據沒有應答
只有ACK確認應答過的數據, 才能從緩沖區刪掉.
如果出現了丟包, 那么該如何進行重傳呢?
此時分兩種情況討論:
1, 數據包已經收到, 但確認應答ACK丟了.
這種情況下, 部分ACK丟失并無大礙, 因為還可以通過后續的ACK來確認對方已經收到了哪些數據包.
2, 數據包丟失
當某一段報文丟失之后, 發送端會一直收到 1001 這樣的ACK, 就像是在提醒發送端 “我想要的是 1001”
如果發送端主機連續三次收到了同樣一個 “1001” 這樣的應答, 就會將對應的數據 1001 - 2000 重新發送
這個時候接收端收到了 1001 之后, 再次返回的ACK就是7001了
因為2001 - 7000接收端其實之前就已經收到了, 被放到了接收端操作系統內核的接收緩沖區中.
這種機制被稱為 “高速重發控制” ( 也叫 “快重傳” )
流量控制
接收端處理數據的速度是有限的. 如果發送端發的太快, 導致接收端的緩沖區被填滿, 這個時候如果發送端繼續發送, 就會造成丟包, 進而引起丟包重傳等一系列連鎖反應.
因此TCP支持根據接收端的處理能力, 來決定發送端的發送速度.
這個機制就叫做 流量控制(Flow Control)
接收端將自己可以接收的緩沖區大小放入 TCP 首部中的 “窗口大小” 字段,
通過ACK通知發送端;
窗口大小越大, 說明網絡的吞吐量越高;
接收端一旦發現自己的緩沖區快滿了, 就會將窗口大小設置成一個更小的值通知給發送端;
發送端接受到這個窗口大小的通知之后, 就會減慢自己的發送速度;
如果接收端緩沖區滿了, 就會將窗口置為0;
這時發送方不再發送數據, 但是需要定期發送一個窗口探測數據段, 讓接收端把窗口大小再告訴發送端.
那么接收端如何把窗口大小告訴發送端呢?
我們的TCP首部中, 有一個16位窗口大小字段, 就存放了窗口大小的信息;
16位數字最大表示65536, 那么TCP窗口最大就是65536字節么?
實際上, TCP首部40字節選項中還包含了一個窗口擴大因子M, 實際窗口大小是窗口字段的值左移 M 位(左移一位相當于乘以2).
擁塞控制
雖然TCP有了滑動窗口這個大殺器, 能夠高效可靠地發送大量數據.
但是如果在剛開始就發送大量的數據, 仍然可能引發一些問題.
因為網絡上有很多計算機, 可能當前的網絡狀態已經比較擁堵.
在不清楚當前網絡狀態的情況下, 貿然發送大量數據, 很有可能雪上加霜.
因此, TCP引入 慢啟動 機制, 先發少量的數據, 探探路, 摸清當前的網絡擁堵狀態以后, 再決定按照多大的速度傳輸數據.
在此引入一個概念 擁塞窗口
發送開始的時候, 定義擁塞窗口大小為1;
每次收到一個ACK應答, 擁塞窗口加1;
每次發送數據包的時候, 將擁塞窗口和接收端主機反饋的窗口大小做比較, 取較小的值作為實際發送的窗口
像上面這樣的擁塞窗口增長速度, 是指數級別的.
“慢啟動” 只是指初使時慢, 但是增長速度非常快.
為了不增長得那么快, 此處引入一個名詞叫做慢啟動的閾值, 當擁塞窗口的大小超過這個閾值的時候, 不再按照指數方式增長, 而是按照線性方式增長.
當TCP開始啟動的時候, 慢啟動閾值等于窗口最大值
在每次超時重發的時候, 慢啟動閾值會變成原來的一半, 同時擁塞窗口置回1
少量的丟包, 我們僅僅是觸發超時重傳;
大量的丟包, 我們就認為是網絡擁塞;
當TCP通信開始后, 網絡吞吐量會逐漸上升;
隨著網絡發生擁堵, 吞吐量會立刻下降.
擁塞控制, 歸根結底是TCP協議想盡可能快的把數據傳輸給對方, 但是又要避免給網絡造成太大壓力的折中方案.
延遲應答
如果接收數據的主機立刻返回ACK應答, 這時候返回的窗口可能比較小.
假設接收端緩沖區為1M. 一次收到了500K的數據;
如果立刻應答, 返回的窗口大小就是500K;
但實際上可能處理端處理的速度很快, 10ms之內就把500K數據從緩沖區消費掉了; 在這種情況下, 接收端處理還遠沒有達到自己的極限, 即使窗口再放大一些, 也能處理過來;
如果接收端稍微等一會兒再應答, 比如等待200ms再應答, 那么這個時候返回的窗口大小就是1M
窗口越大, 網絡吞吐量就越大, 傳輸效率就越高.
TCP的目標是在保證網絡不擁堵的情況下盡量提高傳輸效率;
那么所有的數據包都可以延遲應答么?
肯定也不是
有兩個限制
數量限制: 每隔N個包就應答一次
時間限制: 超過最大延遲時間就應答一次
具體的數量N和最大延遲時間, 依操作系統不同也有差異
一般 N 取2, 最大延遲時間取200ms
捎帶應答
在延遲應答的基礎上, 我們發現, 很多情況下
客戶端和服務器在應用層也是 “一發一收” 的
意味著客戶端給服務器說了 “How are you”
服務器也會給客戶端回一個 “Fine, thank you”
那么這個時候ACK就可以搭順風車, 和服務器回應的 “Fine, thank you” 一起發送給客戶端
面向字節流
創建一個TCP的socket, 同時在內核中創建一個 發送緩沖區 和一個 接收緩沖區;
調用write時, 數據會先寫入發送緩沖區中;
如果發送的字節數太大, 會被拆分成多個TCP的數據包發出;
如果發送的字節數太小, 就會先在緩沖區里等待, 等到緩沖區大小差不多了, 或者到了其他合適的時機再發送出去;
接收數據的時候, 數據也是從網卡驅動程序到達內核的接收緩沖區;
然后應用程序可以調用read從接收緩沖區拿數據;
另一方面, TCP的一個連接, 既有發送緩沖區, 也有接收緩沖區,
那么對于這一個連接, 既可以讀數據, 也可以寫數據, 這個概念叫做 全雙工
由于緩沖區的存在, 所以TCP程序的讀和寫不需要一一匹配
例如:
寫100個字節的數據, 可以調用一次write寫100個字節, 也可以調用100次write, 每次寫一個字節;
讀100個字節數據時, 也完全不需要考慮寫的時候是怎么寫的, 既可以一次read 100個字節, 也可以一次read一個字節, 重復100次;
粘包問題
首先要明確, 粘包問題中的 “包”, 是指應用層的數據包.
在TCP的協議頭中, 沒有如同UDP一樣的 “報文長度” 字段
但是有一個序號字段.
站在傳輸層的角度, TCP是一個一個報文傳過來的. 按照序號排好序放在緩沖區中.
站在應用層的角度, 看到的只是一串連續的字節數據.
那么應用程序看到了這一連串的字節數據, 就不知道從哪個部分開始到哪個部分是一個完整的應用層數據包.
此時數據之間就沒有了邊界, 就產生了粘包問題
那么如何避免粘包問題呢?
歸根結底就是一句話, 明確兩個包之間的邊界
對于定長的包
- 保證每次都按固定大小讀取即可
例如上面的Request結構, 是固定大小的, 那么就從緩沖區從頭開始按sizeof(Request)依次讀取即可
對于變長的包
- 可以在數據包的頭部, 約定一個數據包總長度的字段, 從而就知道了包的結束位置
還可以在包和包之間使用明確的分隔符來作為邊界(應用層協議, 是程序員自己來定的, 只要保證分隔符不和正文沖突即可)
對于UDP協議來說, 是否也存在 “粘包問題” 呢?
對于UDP, 如果還沒有向上層交付數據, UDP的報文長度仍然存在.
同時, UDP是一個一個把數據交付給應用層的, 就有很明確的數據邊界.
站在應用層的角度, 使用UDP的時候, 要么收到完整的UDP報文, 要么不收.
不會出現收到 “半個” 的情況.
TCP 異常情況
進程終止: 進程終止會釋放文件描述符, 仍然可以發送FIN. 和正常關閉沒有什么區別.
機器重啟: 和進程終止的情況相同.
機器掉電/網線斷開: 接收端認為連接還在, 一旦接收端有寫入操作, 接收端發現連接已經不在了, 就會進行 reset. 即使沒有寫入操作, TCP自己也內置了一個保活定時器, 會定期詢問對方是否還在. 如果對方不在, 也會把連接釋放.
另外, 應用層的某些協議, 也有一些這樣的檢測機制.
例如HTTP長連接中, 也會定期檢測對方的狀態.
例如QQ, 在QQ斷線之后, 也會定期嘗試重新連接.
TCP 小結
為什么TCP這么復雜?
因為既要保證可靠性, 同時又要盡可能提高性能.
保證可靠性的機制
校驗和
序列號(按序到達)
確認應答
超時重傳
連接管理
流量控制
擁塞控制
提高性能的機制
滑動窗口
快速重傳
延遲應答
捎帶應答
定時器
超時重傳定時器
保活定時器
TIME_WAIT定時器
基于 TCP 的應用層協議
HTTP
HTTPS
SSH
Telnet
FTP
SMTP
…
當然, 也包括我們自己寫TCP程序時自定義的應用層協議
TCP 和 UDP 對比
我們說了TCP是可靠連接, 那么是不是TCP一定就優于UDP呢?
TCP和UDP之間的優點和缺點, 不能簡單絕對地進行比較
TCP用于可靠傳輸的情況, 應用于文件傳輸, 重要狀態更新等場景
UDP用于對高速傳輸和實時性要求較高的通信領域
例如, 早期的QQ, 視頻傳輸等. 另外UDP可以用于廣播
歸根結底, TCP和UDP都是一種工具, 什么時機用, 具體怎么用, 還是要根據具體的需求場景去決定.
————————————————
版權聲明:本文為CSDN博主「rugu_xxx」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/sinat_36629696/article/details/80740678
總結
以上是生活随笔為你收集整理的win2008 查询 tcp连接失败_TCP详解(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DDD 领域驱动设计落地实践:六步拆解
- 下一篇: centos7 python3.7 ss