日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

TCP大礼包

發布時間:2025/4/16 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TCP大礼包 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:OFFER—PLSxD
鏈接:
https://www.nowcoder.com/discuss/530380
來源:牛客網

TCP 大禮包

連接到底是什么?

所謂的連接其實只是雙方都維護了一個狀態,通過每一次通信來維護狀態的變更,使得看起來好像有一條線關聯了對方。

TCP 協議頭

**序號:**用于對字節流進行編號,例如序號為 301,表示第一個字節的編號為 301,如果攜帶的數據長度為 100 字節,那么下一個報文段的序號應為 401。

**確認號:**期望收到的下一個報文段的序號。例如 B 正確收到 A 發送來的一個報文段,序號為 501,攜帶的數據長度為 200 字節,因此 B 期望下一個報文段的序號為 701,B 發送給 A 的確認報文段中確認號就為 701。

**數據偏移:**指的是數據部分距離報文段起始處的偏移量,實際上指的是首部的長度。

**控制位:**八位從左到右分別是 CWR,ECE,URG,ACK,PSH,RST,SYN,FIN。

**CWR:**CWR 標志與后面的 ECE 標志都用于 IP 首部的 ECN 字段,ECE 標志為 1 時,則通知對方已將擁塞窗口縮小;

**ECE:**若其值為 1 則會通知對方,從對方到這邊的網絡有阻塞。在收到數據包的 IP 首部中 ECN 為 1 時將 TCP 首部中的 ECE 設為 1;

**URG:**該位設為 1,表示包中有需要緊急處理的數據,對于需要緊急處理的數據,與后面的緊急指針有關;

**ACK:**該位設為 1,確認應答的字段有效,TCP規定除了最初建立連接時的 SYN 包之外該位必須設為 1;

**PSH:**該位設為 1,表示需要將收到的數據立刻傳給上層應用協議,若設為 0,則先將數據進行緩存;

**RST:**該位設為 1,表示 TCP 連接出現異常必須強制斷開連接;

**SYN:**用于建立連接,該位設為 1,表示希望建立連接,并在其序列號的字段進行序列號初值設定;

**FIN:**該位設為 1,表示今后不再有數據發送,希望斷開連接。當通信結束希望斷開連接時,通信雙方的主機之間就可以相互交換 FIN 位置為 1 的 TCP 段。

每個主機又對對方的 FIN 包進行確認應答之后可以斷開連接。不過,主機收到 FIN 設置為 1 的 TCP 段之后不必馬上回復一個 FIN 包,而是可以等到緩沖區中的所有數據都因為已成功發送而被自動刪除之后再發 FIN 包;

**窗口:**窗口值作為接收方讓發送方設置其發送窗口的依據。之所以要有這個限制,是因為接收方的數據緩存空間是有限的。

TCP/UDP偽首部的理解

其目的是讓UDP兩次檢查數據是否已經正確到達目的地,具體是那兩次呢?我們注意偽首部字段:32位源IP地址、32位目的IP地址、8位協議、16位UDP長度。由此可知,第一次,通過偽首部的IP地址檢驗,UDP可以確認該數據報是不是發送給本機IP地址的;第二,通過偽首部的協議字段檢驗,UDP可以確認IP有沒有把不應該傳給UDP而應該傳給別的高層的數據報傳給了UDP。從這一點上,偽首部的作用其實很大。

SYN 超時了怎么處理?

也就是 client 發送 SYN 至 server 然后就掛了,此時 server 發送 SYN+ACK 就一直得不到回復,慢慢重試,階梯性重試, 在 Linux 中就是默認重試 5 次,并且就是階梯性的重試,間隔就是1s、2s、4s、8s、16s,再第五次發出之后還得等 32s 才能知道這次重試的結果,所以說總共等63s 才能斷開連接

SYN Flood 攻擊

可以開啟 tcp_syncookies,那就用不到 SYN 隊列了。

SYN 隊列滿了之后 TCP 根據自己的 ip、端口、然后對方的 ip、端口,對方 SYN 的序號,時間戳等一波操作生成一個特殊的序號(即 cookie)發回去,如果對方是正常的 client 會把這個序號發回來,然后 server 根據這個序號建連。

或者調整 tcp_synack_retries 減少重試的次數,設置 tcp_max_syn_backlog 增加 SYN 隊列數,設置 tcp_abort_on_overflow SYN 隊列滿了直接拒絕連接。

IP 、UDP和TCP,每一種格式的首部中均包含一個檢驗和。對每種分組,說明檢驗和包括IP數據報中的哪些部分,以及該檢驗和是強制的還是可選的

除了UDP的檢驗和,其他都是必需的。IP檢驗和只覆蓋了IP首部,而其他字段都緊接著IP首部開始。

為什么所有Internet協議收到有檢驗和錯的分組都僅作丟棄處理?

源IP地址、源端口號或者協議字段可能被破壞了。

為什么會發生 TCP 粘包、拆包?

  • 要發送的數據大于 TCP 發送緩沖區剩余空間大小,將會發生拆包。
  • 待發送數據大于 MSS(最大報文長度),TCP 在傳輸前將進行拆包。
  • 要發送的數據小于 TCP 發送緩沖區的大小,TCP 將多次寫入緩沖區的數據一次發送出去,將會發生粘包。
  • 接收數據端的應用層沒有及時讀取接收緩沖區中的數據,將發生粘包。

粘包、拆包解決辦法

由于 TCP 本身是面向字節流的,無法理解上層的業務數據,所以在底層是無法保證數據包不被拆分和重組的,這個問題只能通過上層的應用協議棧設計來解決,根據業界的主流協議的解決方案,歸納如下:

  • **消息定長:**發送端將每個數據包封裝為固定長度(不夠的可以通過補 0 填充),這樣接收端每次接收緩沖區中讀取固定長度的數據就自然而然的把每個數據包拆分開來。
  • **設置消息邊界:**服務端從網絡流中按消息邊界分離出消息內容。在包尾增加回車換行符進行分割,例如 FTP 協議。
  • **將消息分為消息頭和消息體:**消息頭中包含表示消息總長度(或者消息體長度)的字段。
  • 更復雜的應用層協議比如 Netty 中實現的一些協議都對粘包、拆包做了很好的處理。

TCP 提供了一種字節流服務,而收發雙方都不保持記錄的邊界。應用程序如何提供它們自己的記錄標識?

很多Internet應用使用一個回車和換行來標記每個應用記錄的結束。這是NVT ASCII采用的編碼。另外一種技術是在每個記錄之前加上一個記錄的字節計數,DNS和Sun RPC采用了這種技術。

為什么在 TCP 首部的開始便是源和目的的端口號?

一個ICMP差錯報文必須至少返回引起差錯的IP數據報中除了IP首部的前8個字節。當TCP收到一個ICMP差錯報文時,它需要檢查兩個端口號以決定差錯對應于哪個連接。因此,端口號必須包含在TCP首部的前8個字節里。

為什么TCP 首部有一個首部長度字段而UDP首部中卻沒有?

TCP首部的最后有一些選項,但UDP首部中沒有選項。

SYN 初始值 ISN 規律

當一端為建立連接而發送它的S Y N時,它為連接選擇一個初始序號。ISN隨時間而變化,因此每個連接都將具有不同的I S N。RFC 793 指出ISN可看作是一個 32 比特的計數器,每4 ms加1。這樣選擇序號的目的在于防止在網絡中被延遲的分組在以后又被傳送,而導致某個連接的一方對它作錯誤的解釋。所以 ISN 變成一個遞增值,真實的實現還需要加一些隨機值在里面,防止被不法份子猜到 ISN。

全雙工的體現

既然一個T C P連接是全雙工(即數據在兩個方向上能同時傳遞),因此每個方向必須單獨地進行關閉。這原則就是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向連接。當一端收到一個FIN,它必須通知應用層另一端幾經終止了那個方向的數據傳送。發送FIN通常是應用層進行關閉的結果。收到一個FIN只意味著在這一方向上沒有數據流動。一個TCP連接在收到一個FIN后仍能發送數據。而這對利用半關閉的應用來說是可能的,盡管在實際應用中只有很少的T C P應用程序這樣做。

MSS 確定(商議)的?

它并不是任何條件下都可協商。當建立一個連接時,每一方都有用于通告它期望接收的MSS選項(MSS選項只能出現在SYN報文段中)。如果一方不接收來自另一方的MSS值,則MSS就定為默認值536字節(這個默認值允許20字節的I P首部和20字節的TCP首部以適合576字節I P數據報)。

TCP 最大段大小

最大段大小是指 TCP 協議所允許的從對方接收到的最大報文段,因此這也是通信對方在發送數據時能夠使用的最大報文段。根據 [RFCO879],最大段大小只記錄 TCP 數據的字節數而不包括其他相關的 TCP 與 IP 頭部。當建立一條 TCP 連接時,通信的每一方都要在 SYN 報文段的 MSS 選項中說明自已允許的最大段大小。這 16 位的選項能夠說明最大段大小的數值。在沒有事先指明的情況下,最大段大小的默認數值為 536 字節。任何主機都應該能夠處理至少 576 字節的 IPv4 數據報。如果接照最小的 IPv4 與 TCP 頭部計算, TCP 協議要求在每次發送時的最大段大小為 536 字節,這樣就正好能夠組成一個 576 (20+20+536=576)字節的 IPv4 數據報。

最大段大小的數值為 1460。 這是 IPv4 協議中的典型值,因此 IPv4 數據報的大小也相應增加 40 個字節(總共 1500 字節,以太網中最大傳輸單元與互聯網路徑最大傳輸單元的典型數值): 20 字節的 TCP 頭部加 20 字節的 IP 頭部。

當使用 IPv6 協議時,最大段大小通常為 1440 字節。由于 IPv6 的頭部比 IPv4 多 20 個字節,因此最大段大小的數值相 應減少 20 字節。在 [RFC2675] 中 65535 是一個特殊數值,與 IPv6 超長數據報一起用來指定一個表示無限大的有效最大段大小值。在這種情況下,發送方的最大段大小等于路徑 MTU 的數值減去 60 字節(40 字節用于 IPv6 頭部, 20 字節用于 TCP 頭部)。值得注意的是,最大段大小并不是 TCP 通信雙方的協商結果,而是一個限定的數值。當通信的一方將自已的最大段大小選項發送給對方時,它已表明自已不愿意在整個連接過程中接收任何大于該尺寸的報文段。

time_wait 的壞處

當 TCP執行一個主動關閉,并發回最后一個ACK,該連接必須在Time_wait狀態停留的時間為2倍的M S L。這樣可讓TCP再次發送最后的A C K以防這個ACK丟失(另一端超時并重發最后的F I N)。

這種2 MSL等待的另一個結果是這個TCP連接在2 MSL等待期間,定義這個連接的插口(客戶的I P地址和端口號,服務器的I P地址和端口號)不能再被使用。這個連接只能在2 MSL結束后才能再被使用。服務器通常執行被動關閉,不會進入TIME_WAIT狀態。這暗示如果我們終止一個客戶程序,并立即重新啟動這個客戶程序,則這個新客戶程序將不能重用相同的本地端口。這不會帶來什么問題,因為客戶使用本地端口,而并不關心這個端口號是什么。然而,對于服務器,情況就有所不同,因為服務器使用熟知端口。如果我們終止一個已經建立連接的服務器程序,并試圖立即重新啟動這個服務器程序,服務器程序將不能把它的這個熟知端口賦值給它的端點,因為那個端口是處于2 MSL連接的一部分。在重新啟動服務器程序前,它需要在1 ~ 4分鐘。

RST 復位什么時候發出

一般說來,無論何時一個報文段發往基準的連接( referenced connection)出現錯誤, TCP都會發出一個復位報文段(這里提到的“基準的連接”是指由目的 IP 地址和目的端口號以及源 IP 地址和源端口號指明的連接。這就是為什么RFC 793稱之為插口)。

產生復位的一種常見情況是當連接請求到達時,目的端口沒有進程正在聽。

發送一個復位報文段而不是F I N來中途釋放一個連接。有時稱這為異常釋放(abortive release)。異常終止一個連接對應用程序來說有兩個優點:(1)丟棄任何待發數據并立即發送復位報文段;(2)R S T的接收方會區分另一端執行的是異常關閉還是正常關閉。應用程序使用的A P I必須提供產生異常關閉而不是正常關閉的手段,R S T報文段中包含一個序號和確認序號。需要注意的是R S T報文段不會導致另一端產生任何響應,另一端根本不進行確認。收到R S T的一方將終止該連接,并通知應用層連接復位。

TCP 選項有什么

  • 窗口擴大選項使 TCP 的窗口定義從16 bit增加為32 bit。這并不是通過修改TCP首部來實現的, T C P首部仍然使用16 bit ,而是通過定義一個選項實現對16 bit 的擴大操作 來完成的。于是T C P在內部將實際的窗口大小維持為32 bit的值。
  • 時間戳選項使發送方在每個報文段中放置一個時間戳值。接收方在確認中返回這個數值,從而允許發送方為每一個收到的A C K計算RT T(我們必須說“每一個收到的A C K”而不是“每一個報文段”,是因為T C P通常用一個A C K來確認多個報文段)。我們提到過目前許多實現為每一個窗口只計算一個RT T,對于包含8個報文段的窗口而言這是正確的。然而,較大的窗口大小則需要進行更好的RT T計算。
  • 最大報文傳輸段(Maximum Segment Size —MSS)
  • 選擇確認選項(Selective Acknowledgements --SACK)
  • 半打開連接和半關閉連接的區別是什么?

    在一個半關閉的連接上,一個端點已經發送了一個FIN,正等待另一端的數據或者一個FIN。

    一個半打開的連接是當一個端點崩潰了,而另一端還不知道的情況。

    未連接隊列:在三次握手協議中,服務器維護一個未連接隊列,該隊列為每個客戶端的SYN包(syn=j)開設一個條目,該條目表明服務器已收到 SYN包,并向客戶發出確認,正在等待客戶的確認包。這些條目所標識的連接在服務器處于Syn_RCVD狀態,當服務器收到客戶的確認包時,刪除該條目, 服務器進入ESTABLISHED狀態。

    ACK延遲確認機制

    接收方在收到數據后,并不會立即回復ACK,而是延遲一定時間。一般ACK延遲發送時間為200ms,但是這個200ms并非收到數據后需要延遲的時間。系統有一個固定的定時器每隔200ms會來檢查是否需要發送ACK包。這樣做有2個目的:

  • 這樣做的目的是ACK是可以合并的,也就是指如果連續收到兩個TCP包,并不一定需要ACK兩次,只要回復最終的ACK就可以了,可以降低網絡流量。
  • 如果接收方有數據要發送,那么就會在發送數據的TCP數據包里,帶上ACK信息。這樣做,可以避免大量的ACK以一個單獨的TCP包發送,減少了網絡流量。
  • SACK(Selective Acknowledgment)

    SACK是一個TCP的選項,來允許TCP單獨確認非連續的片段,用于告知真正丟失的包,只重傳丟失的片段。要使用SACK,2個設備必須同時支持SACK才可以,建立連接的時候需要使用SACK Permitted的option,如果允許,后續的傳輸過程中TCP segment中的可以攜帶SACK option,這個option內容包含一系列的非連續的沒有確認的數據的seq range

    TCP收到亂序數據后會將其放到亂序序列中,然后發送重復ACK給對端。對端如果收到多個重復的ACK,認為發生丟包,TCP會重傳最后確認的包開始的后續包。這樣原先已經正確傳輸的包,可能會重復發送,降低了TCP性能。為改善這種情況,發展出SACK技術,使用SACK選項可以告知發包方收到了哪些數據,發包方收到這些信息后就會知道哪些數據丟失,然后立即重傳丟失的部分。

    SACK 重傳

  • 未啟用 SACK 時,TCP 重復 ACK 定義為收到連續相同的 ACK seq。[RFC5681]

  • 啟用 SACK 時,攜帶 SACK 的 ACK 也被認為重復 ACK。[RFC6675]

    SACK option格式 Kind 5 Length 剩下的都是沒有確認的segment的range了 比如說segment 501-600 沒有被確認,那么Left Edge of 1st Block = 501,Right Edge of 1st Block = 600,TCP的選項不能超過40個字節,所以邊界不能超過4組

  • Nagle算法

    在局域網上,小分組(被稱為微小分組)通常不會引起麻煩,因為局域網一般不會出現擁塞。但在廣域網上,這些小分組則會增加擁塞出現的可能。

    該算法要求一個TCP連接上最多只能有一個未被確認的未完成的小分組,在該分組的確認到達之前不能發送其他的小分組。相反, TCP收集這些少量的分組,并在確認到來時以一個分組的方式發出去。該算法的優越之處在于它是自適應的:確認到達得越快,數據也就發送得越快。而在希望減少微小分組數目的低速廣域網上,則會發送更少的分組。插口API用戶可以使用 TCP_NODELAY 選項來關閉Nagle算法。

    Karn算法

    當一個超時和重傳發生時,在重傳數據的確認最后到達之前,不能更新RT T估計器,因為我們并不知道A C K對應哪次傳輸(也許第一次傳輸被延遲而并沒有被丟棄,也有可能第一次傳輸的A C K被延遲)并且,由于數據被重傳, RTO已經得到了一個指數退避,我們在下一次傳輸時使用這個退避后的RTO。對一個沒有被重傳的報文段而言,除非收到了一個確認,否則不要計算新的RTO。

    Karn 算法在分組丟失時可以不測量 RTT 就能解決重傳的二義性問題。

    快重傳:3次相同的ack后會進入慢啟動嗎?

    No,在這種情況下沒有執行慢啟動的原因是由于收到重復的ACK不僅僅告訴我們一個分組丟失了。由于接收方只有在收到另一個報文段時才會產生重復的ACK,而該報文段已經離開了網絡并進入了接收方的緩存。也就是說,在收發兩端之間仍然有流動的數據,而我們不想執行慢啟動來突然減少數據流。

    流程

  • 當收到第3個重復的ACK 時,將ssthresh 設置為當前擁塞窗口 cwnd的一半。重傳丟失的報文段。設置cwnd為ssthresh加上3倍的報文段大小。

  • 每次收到另一個重復的ACK時, cwnd增加1個報文段大小并發送1個分組(如果新的 cwnd允許發送)。

  • 當下一個確認新數據的ACK到達時,設置cwnd為ssthresh(在第1步中設置的值)。這個 ACK應該是在進行重傳后的一個往返時間內對步驟1中重傳的確認。另外,這個ACK也應該是對丟失的分組和收到的第1個重復的A C K之間的所有中間報文段的確認。這一步采用的是擁塞避免,因為當分組丟失時我們將當前的速率減半。

  • 保活計時器的作用?

    TCP的Keepalive,目的在于看看對方有沒有發生異常,如果有異常就及時關閉連接。當傳輸雙方不主動關閉連接時,就算雙方沒有交換任何數據,連接也是一直有效的。保活定時器每隔一段時間會超時,超時后會檢查連接是否空閑太久了,如果空閑的時間超過了設置時間,就會發送探測報文。然后通過對端是否響應、響應是否符合預期,來判斷對端是否正常,如果不正常,就主動關閉連接,而不用等待HTTP層的關閉了。

    SYN Cookies

    在最常見的SYN Flood攻擊中,攻擊者在短時間內發送大量的TCP SYN包給受害者。受害者(服務器)為每個TCP SYN包分配一個特定的數據區,只要這些SYN包具有不同的源地址(攻擊者很容易偽造)。這將給TCP服務器造成很大的系統負擔,最終導致系統不能正常工作。

    SYN Cookie是對TCP服務器端的三次握手做一些修改,專門用來防范SYN Flood攻擊的一種手段。它的原理是,在TCP服務器接收到TCP SYN包并返回TCP SYN + ACK包時,不分配一個專門的數據區,而是根據這個SYN包計算出一個cookie值。這個cookie作為將要返回的SYN ACK包的初始序列號。當客戶端返回一個ACK包時,根據包頭信息計算cookie,與返回的確認序列號(初始序列號 + 1)進行對比,如果相同,則是一個正常連接,然后,分配資源,建立連接。

    cookie的計算:服務器收到一個SYN包,計算一個消息摘要mac。

    CLOSE_WAIT過多的解決方法

    系統產生大量“Too many open files”

    原因分析:在服務器與客戶端通信過程中,因服務器發生了 socket 未關導致的 closed_wait 發生,致使監聽 port 打開的句柄數到了 1024 個,且均處于 close_wait 的狀態,最終造成配置的port被占滿出現 “Too many open files”,無法再進行通信。

    解決辦法:有兩種措施可行

    一、解決:
    原因是因為調用 ServerSocket 類的 accept() 方法和 Socket 輸入流的 read() 方法時會引起線程阻塞,所以應該用 setSoTimeout() 方法設置超時(缺省的設置是0,即超時永遠不會發生);超時的判斷是累計式的,一次設置后,每次調用引起的阻塞時間都從該值中扣除,直至另一次超時設置或有超時異常拋出。比如,某種服務需要三次調用 read(),超時設置為1分鐘,那么如果某次服務三次 read()調用的總時間超過 1 分鐘就會有異常拋出,如果要在同一個 Socket 上反復進行這種服務,就要在每次服務之前設置一次超時。

    二、規避:
    調整系統參數,包括句柄相關參數和TCP/IP的參數;

  • open files 參數值 加大
  • 當客戶端因為某種原因先于服務端發出了 FIN 信號,就會導致服務端被動關閉,若服務端不主動關閉 socket 發 FIN 給 Client,此時服務端 Socket 會處于 CLOSE_WAIT 狀態(而不是 LAST_ACK 狀態)。通常來說,一個 CLOSE_WAIT 會維持至少 2 個小時的時間(系統默認超時時間的是 7200 秒,也就是 2 小時)。如果服務端程序因某個原因導致系統造成一堆 CLOSE_WAIT 消耗資源,那么通常是等不到釋放那一刻,系統就已崩潰。因此,解決這個問題的方法還可以通過修改 TCP/IP 的參數來縮短這個時間,于是修改 tcp_keepalive_*系列參數: /proc/sys/net/ipv4/tcp_keepalive_time , /proc/sys/net/ipv4/tcp_keepalive_probes ,/proc/sys/net/ipv4/tcp_keepalive_intvl
  • 短連接,并行連接,持久連接與長連接

    短連接

    短連接多用于操作頻繁,點對點的通訊,而且連接數不能太多的情況。每個 TCP 連接的建立都需要三次握手,每個 TCP 連接的斷開要四次揮手。適用于并發量大,但是每個用戶又不需頻繁操作的情況。

    但是在用戶需要頻繁操作的業務場景下(如新用戶注冊,網購提交訂單等),頻繁的使用短連接則會使性能時延產生疊加。

    用戶登錄這些不頻繁的操作可以考慮用短連接。

    并行連接

    針對短連接,人們想出了優化的辦法,連接多條,形成并行連接。

    并行連接允許客戶端打開多條連接,并行地執行多個事務,每個事務都有自己的TCP連接。這樣可以克服單條連接的空載時間和帶寬限制,時延可以重疊起來,而且如果單條連接沒有充分利用客戶端的網絡帶寬,可以將未用帶寬分配來裝載其他對象。

    在PC時代,利用并行連接來充分利用現代瀏覽器的多線程并發下載能力的場景非常廣泛。

    但是并行連接也會產生一定的問題,首先并行連接不一定更快,因為帶寬資源有限,每個連接都會去競爭這有限的帶寬,這樣帶來的性能提升就很小,甚至沒什么提升。

    一般機器上面并行連接的條數 4 - 6 條

    持久連接

    HTTP1.0 版本以后,允許 HTTP 設備在事務處理結束之后將 TCP 連接保持在打開狀態,以便為未來的 HTTP 請求重用現存的連接。在事務處理結束之后仍然保持在打開狀態的 TCP 連接被稱為持久連接。

    持久連接的時間參數,通常由服務器設定,比如 nginx 的 keepalivetimeout,keepalive timout 時間值意味著:一個 http 產生的 tcp 連接在傳送完最后一個響應后,還需要 hold 住 keepalive_timeout 秒后,才開始關閉這個連接;

    在 HTTP 1.1 中 所有的連接默認都是持續連接,除非特殊聲明不支持。HTTP 持久連接不使用獨立的 keepalive 信息,而是僅僅允許多個請求使用單個連接。然而,Apache 2.0 httpd 的默認連接過期時間是僅僅 15 秒,對于 Apache 2.2 只有 5 秒。短的過期時間的優點是能夠快速的傳輸多個 web 頁組件,而不會綁定多個服務器進程或線程太長時間。

    持久連接與并行連接相比,帶來的優勢如下:

  • 避免了每個事務都會打開/關閉一條新的連接,造成時間和帶寬的耗費;
  • 避免了 TCP 慢啟動特性的存在導致的每條新連接的性能降低;
  • 可打開的并行連接數量實際上是有限的,持久連接則可以減少建立的連接的數量;
  • 長連接

    長連接與持久連接本質上非常的相似,持久連接側重于 HTTP 應用層,特指一次請求結束之后,服務器會在自己設置的 keepalivetimeout 時間到期后才關閉已經建立的連接。長連接則是 client 方與 server 方先建立連接,連接建立后不斷開,然后再進行報文發送和接收,直到有一方主動關閉連接為止。

    長連接的適用場景也非常的廣泛:

  • 監控系統:后臺硬件熱插拔、LED、溫度、電壓發生變化等;
  • IM 應用:收發消息的操作;
  • 即時報價系統:例如股市行情 push 等;
  • 推送服務:各種 App 內置的 push 提醒服務;
  • TIME_WAIT快速回收與重用

    https://blog.csdn.net/dog250/article/details/13760985

    Linux實現了一個TIME_WAIT狀態快速回收的機制,即無需等待兩倍的MSL這么久的時間,而是等待一個Retrans時間即釋放,也就是等待一個重傳時間(一般超級短,以至于你都來不及能在netstat -ant中看到TIME_WAIT狀態)隨即釋放。釋放了之后,一個連接的tuple元素信息就都沒有了。在快速釋放掉TIME_WAIT連接之后,peer依然保留著。丟失的僅僅是端口信息。不過有了peer的IP地址信息以及TCP最后一次觸摸它的時間戳就足夠了,TCP規范給出一個優化,即一個新的連接除了同時觸犯了以下幾點,其它的均可以快速接入,即使它本應該處在TIME_WAIT狀態(但是被即快速回收了):
    1.來自同一臺機器的TCP連接攜帶時間戳;2.之前同一臺peer機器(僅僅識別IP地址,因為連接被快速釋放了,沒了端口信息)的某個TCP數據在MSL秒之內到過本機;3.新連接的時間戳小于peer機器上次TCP到來時的時間戳,且差值大于重放窗口戳。

    **建議:**如果前端部署了三/四層NAT設備,盡量關閉快速回收,以免發生NAT背后真實機器由于時間戳混亂導致的SYN拒絕問題。

    TW重用 有相關的規范,即:如果能保證以下任意一點,一個TW狀態的四元組(即一個socket連接)可以重新被新到來的SYN連接使用:

    1.初始序列號比TW老連接的末序列號大
    2.如果使能了時間戳,那么新到來的連接的時間戳比老連接的時間戳大

    BBR 算法

    BBR 全稱 bottleneck bandwidth and round-trip propagation time。基于包丟失檢測的 Reno、NewReno 或者 cubic 為代表,其主要問題有 Buffer bloat 和長肥管道兩種。和這些算法不同,bbr 算***時間窗口內的最大帶寬 max_bw 和最小 RTT min_rtt,并以此計算發送速率和擁塞窗口。

    當沒有足夠的數據來填滿管道時,RTprop 決定了流的行為;當有足夠的數據填滿時,那就變成了 BtlBw 來決定。這兩條約束交匯在點 inflight =BtlBw*RTprop,也就是管道的 BDP(帶寬與時延的乘積)。當管道被填滿時,那些超過的部分(inflight-BDP)就會在瓶頸鏈路中制造了一個隊列,從而導致了 RTT 的增大。當數據繼續增加直到填滿了緩存時,多余的報文就會被丟棄了。擁塞就是發生在 BDP 點的右邊,而擁塞控制算法就是來控制流的平均工作點離 BDP 點有多遠。

    TCP 參數

    • Backlog參數:表示未連接隊列的最大容納數目。
    • SYN-ACK 重傳次數: 服務器發送完SYN-ACK包,如果未收到客戶確認包,服務器進行首次重傳,等待一段時間仍未收到客戶確認包,進行第二次重傳,如果重傳次數超 過系統規定的最大重傳次數,系統將該連接信息從半連接隊列中刪除。注意,每次重傳等待的時間不一定相同。
    • 半連接存活時間:是指半連接隊列的條目存活的最長時間,也即服務從收到SYN包到確認這個報文無效的最長時間,該時間值是所有重傳請求包的最長等待時間總和。有時我們也稱半連接存活時間為Timeout時間、SYN_RECV存活時間。
    • 開啟SYN Cookies : net.ipv4.tcp_syncookies = 1
    • 開啟timewait重用。允許將TIME-WAIT sockets重新用于新的TCP連接,默認為0 :net.ipv4.tcp_tw_reuse = 1
    • 開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關閉;net.ipv4.tcp_tw_recycle = 1
    • tw 時間 : net.ipv4.tcp_fin_timeout 修改系統默認的 TIMEOUT 時間
    • 當keepalive起用的時候,TCP發送keepalive消息的頻度。缺省是2小時,改為20分鐘(20*60s)
      net.ipv4.tcp_keepalive_time = 1200

    tcp 異常

    試圖與一個不存在的端口建立連接

    這符合觸發發送RST分節的條件,目的為某端口的SYN分節到達,而端口沒有監聽,那么內核會立即響應一個RST,表示出錯。客戶端TCP收到這個RST之后則放棄這次連接的建立,并且返回給應用程序一個錯誤。正如上面所說的,建立連接的過程對應用程序來說是不可見的,這是操作系統幫我們來完成的,所以即使進程沒有啟動,也可以響應客戶端。

    試圖與一個不存在的主機上面的某端口建立連接

    這也是一種比較常見的情況,當某臺服務器主機宕機了,而客戶端并不知道,仍然嘗試去與其建立連接。根據上面的經驗,這次主機已經處于未啟動狀態,操作系統也幫不上忙了,那么也就是連RST也不能響應給客戶端,此時服務器端是一種完全沒有響應的狀態。那么此時客戶端的TCP會怎么辦呢?據書上介紹,如果客戶端TCP沒有得到任何響應,那么等待6s之后再發一個SYN,若無響應則等待24s再發一個,若總共等待了75s后仍未收到響應就會返回ETIMEDOUT錯誤。這是TCP建立連接自己的一個保護機制,但是我們要等待75s才能知道這個連接無法建立,對于我們所有服務來說都太長了。更好的做法是在代碼中給connect設置一個超時時間,使它變成我們可控的,讓等待時間在毫秒級還是可以接收的。

    Server進程被阻塞

    由于某些情況,服務器端進程無法響應任何請求,比如所在主機的硬盤滿了,導致進程處于完全阻塞,通常我們測試時會用gdb模擬這種情況。上面提到過,建立連接的過程對應用程序是不可見的,那么,這時連接可以正常建立。當然,客戶端進程也可以通過這個連接給服務器端發送請求,服務器端TCP會應答ACK表示已經收到這個分節(這里的收到指的是數據已經在內核的緩沖區里準備好,由于進程被阻塞,無法將數據從內核的緩沖區復制到應用程序的緩沖區),但永遠不會返回結果。

    我們殺死server

    這是線上最常見的操作,當一個模塊上線時,OP同學總是會先把舊的進程殺死,然后再啟動新的進程。那么在這個過程中TCP連接發生了什么呢。在進程正常退出時會自動調用close函數來關閉它所打開的文件描述符,這相當于服務器端來主動關閉連接——會發送一個FIN分節給客戶端TCP;客戶端要做的就是配合對端關閉連接,TCP會自動響應一個ACK,然后再由客戶端應用程序調用close函數,也就是我們上面所描述的關閉連接的4次揮手過程。接下來,客戶端還需要定時去重連,以便當服務器端進程重新啟動好時客戶端能夠繼續與之通信。

    當然,我們要保證客戶端隨時都可以響應服務器端的斷開連接請求,就必須不能讓客戶端進程再任何時刻阻塞在任何其他的輸入上面。比如,書上給的例子是客戶端進程會阻塞在標準輸入上面,這時如果服務器端主動斷開連接,顯然客戶端不能立刻響應,因為它還在識圖從標準輸入讀一段文本……當然這在實際中很少遇到,如果有多輸入源這種情況的話開通通常會用類似select功能的函數來處理,可以同時監控多個輸入源是否準備就緒,可以避免上述所說的不能立即響應對端關閉連接的情況。

    Server進程所在的主機關機

    實際上這種情況不會帶來什么更壞的后果。在系統關閉時,init進程會給所有進程發送SIGTERM信號,等待一段時間(5~20秒),然后再給所有仍在運行的進程發送SIGKILL信號。當服務器進程死掉時,會關閉所有文件描述符。帶來的影響和上面殺死server相同。

    Server進程所在的主機宕機

    這是我們線上另一種比較常見的狀況。即使宕機是一個小概率事件,線上幾千臺服務器動不動一兩臺掛掉也是常有的事。主機崩潰不會像關機那樣會預先殺死上面的進程,而是突然性的。那么此時我們的客戶端準備給服務器端發送一個請求,它由write寫入內核,由TCP作為一個分節發出,隨后客戶阻塞于read的調用(等待接收結果)。對端TCP顯然不會響應這個分節,因為主機已經掛掉,于是客戶端TCP持續重傳分節,試圖從服務器上接收一個ACK,然而服務器始終不能應答,重傳數次之后,大約4~10分鐘才停止,之后返回一個ETIMEDOUT錯誤。

    這樣盡管最后還是知道對方不可達,但是很多時候我們希望比等待4~10分鐘更快的知道這個結果。可以為read設置一個超時時間,就得到了一個較好的解決方法。但是這樣還是需要等待一個超時時間,事實上TCP為我們提供了更好的方法,用SO_KEEPALIVE的套接字選項——相當于心跳包,每隔一段時間給對方發送一個心跳包,當對方沒有響應時會一更短的時間間隔發送,一段時間后仍然無響應的話就斷開這個連接。

    服務器進程所在的主機宕機后重啟

    在客戶端發出請求前,服務器端主機經歷了宕機——重啟的過程。當客戶端TCP把分節發送到服務器端所在的主機,服務器端所在主機的TCP丟失了崩潰前所有連接信息,即TCP收到了一個根本不存在連接上的分節,所以會響應一個RST分節。如果開發的代碼足夠健壯的話會試圖重新建立連接,或者把這個請求轉發給其他服務器。

    當TCP連接的進程在忘記關閉Socket而退出、程序崩潰、或非正常方式結束進程的情況下(Windows客戶端),會導致TCP連接的對端進程產生“104: Connection reset by peer”(Linux下)或“10054: An existing connection was forcibly closed by the remote host”(Windows下)錯誤

    當TCP連接的進程機器發生死機、系統突然重啟、網線松動或網絡不通等情況下,連接的對端進程可能檢測不到任何異常,并最后等待“超時”才斷開TCP連接

    當TCP連接的進程正常關閉Socket時,對端進程在檢查到TCP關閉事件之前仍然向TCP發送消息,則在Send消息時會產生“32: Broken pipe”(Linux下)或“10053: An established connection was aborted by the software in your host machine”(Windows下)錯誤

    當TCP連接的對端進程已經關閉了Socket的情況下,本端進程再發送數據時,第一包可以發送成功(但會導致對端發送一個RST包過來):
    之后如果再繼續發送數據會失敗,錯誤碼為“10053: An established connection was aborted by the software in your host machine”(Windows下)或“32: Broken pipe,同時收到SIGPIPE信號”(Linux下)錯誤;
    之后如果接收數據,則Windows下會報10053的錯誤,而Linux下則收到正常關閉消息

    TCP連接的本端接收緩沖區中還有未接收數據的情況下close了Socket,則本端TCP會向對端發送RST包,而不是正常的FIN包,這就會導致對端進程提前(RST包比正常數據包先被收到)收到“10054: An existing connection was forcibly closed by the remote host”(Windows下)或“104: Connection reset by peer”(Linux下)錯誤

    PSH和URG

    PSH 標志的作用就在這里,當PSH 被置為1 時, 會被立即推出,不會等待其他數據進入緩沖區。當接受端收到 PSH 被置 1 的數據包時,立即將該分段上交到對應的應用程序。即有如下作用:

    • 通知發送方立即發送數據。
    • 接收方立即將數據推送到應用程序。

    這個標志是在TCP層清空發送緩存,并將報文段交給IP層的時候設置的(相當于表示一次TCP層的發送操作)。(還有一點需要注意:大多數的API沒有向應用層提供通知TCP層設置PUSH標志的方法,據說是因為很多實現程序認為PUSH標志已經過時,而一個好的TCP實現能夠自行決定何時設置這個標志。

    URG 標志用于通知接收方,數據段內某些數據是需要緊急處理的,應該被優先考慮。接收方收到 URH標志有效的數據報時,回去檢測 TCP 報頭中的16 位字段 緊急指針。 該字段指示從第一個字節計數的段中的數據時多少緊急處理的。

    端口號

    標準的端口號由 Internet 號碼分配機構(IANA)分配。這組數字被劃分為特定范圍,包括
    熟知端口號(0 - 1023)、注冊端口號(1024 - 49151)和動態/私有端口號(49152 - 65535)。

    如果我們測試這些標準服務和其他 TCP/IP 服務(Telnet、 FTP、 SMTP等) 使用的端口號,會發現它們大多數是奇數。這是有歷史原困的,這些端口號從 NCP 端口號派生而來(NCP 是網絡控制協議,在 TCP 之前作為 ARPANET 的傳輸層協議)。NCP 雖然簡單,但不是全雙工的,困此每個應用需要兩個連接,并為每個應用保留奇偶成對的端口號。當 TCP 和 UDP 成為標準的傳輸層協議時,每個應用只需要一個端口號,因此來自 NCP 的奇數端口號被使用。

    QQ,微信

    早期的時候,QQ 還是主要使用 TCP 協議,而后來就轉向了采用 UDP 的方式來保持在線,TCP 的方式來上傳和下載數據。現在,UDP 是 QQ 的默認工作方式,表現良好。相信這個也被沿用到了微信上。

    簡單的考證:登錄 PC 版 QQ,關閉多余的 QQ 窗口只留下主窗口,并將其最小化。幾分鐘過后,查看系統網絡連接,會發現 QQ 進程已不保有任何 TCP 連接,但有 UDP 網絡活動。這時在發送聊天信息,或者打開其他窗口和功能,將發現 QQ 進程會啟用 TCP 連接。

    登陸成功之后,QQ 有一個 TCP 連接來保持在線狀態。這個 TCP 連接的遠程端口一般是80,采用 UDP 方式登陸的時候,端口是 8000。

    QQ 客戶端之間的消息傳送采用了 UDP,因為國內的網絡環境非常復雜,而且很多用戶采用的方式是通過代理服務器共享一條線路上網的方式,在這些復雜的情況下,客戶端之間能彼此建立起來 TCP 連接的概率較小,嚴重影響傳送信息的效率。而 UDP 包能夠穿透大部分的代理服務器,因此 QQ 選擇了 UDP 作為客戶之間的主要通信協議。

    騰訊采用了上層協議來保證可靠傳輸:如果客戶端使用 UDP 協議發出消息后,服務器收到該包,需要使用 UDP 協議發回一個應答包。如此來保證消息可以無遺漏傳輸。之所以會發生在客戶端明明看到“消息發送失敗”但對方又收到了這個消息的情況,就是因為客戶端發出的消息服務器已經收到并轉發成功,但客戶端由于網絡原因沒有收到服務器的應答包引起的。

    參考 :

    • https://blog.csdn.net/zhangskd/article/details/44177475
    • https://juejin.im/post/6869734247465402382#heading-0
    • https://www.jianshu.com/p/d759788ab83f
    • https://halfrost.com/advance_tcp/
      來保證可靠傳輸:如果客戶端使用 UDP 協議發出消息后,服務器收到該包,需要使用 UDP 協議發回一個應答包。如此來保證消息可以無遺漏傳輸。之所以會發生在客戶端明明看到“消息發送失敗”但對方又收到了這個消息的情況,就是因為客戶端發出的消息服務器已經收到并轉發成功,但客戶端由于網絡原因沒有收到服務器的應答包引起的。

    參考 :

    • https://blog.csdn.net/zhangskd/article/details/44177475
    • https://juejin.im/post/6869734247465402382#heading-0
    • https://www.jianshu.com/p/d759788ab83f
    • https://halfrost.com/advance_tcp/
    • https://juejin.im/post/6844903881781018632

    總結

    以上是生活随笔為你收集整理的TCP大礼包的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。