TCP 三次握手原理,你真的理解嗎?

2021-12-28 阿里技術

阿里妹導讀:最近,阿里中間件小哥哥蟄劍碰到一個問題——client端連接伺服器總是拋異常。在反覆定位分析、並查閱各種資料文章搞懂後,他發現沒有文章把這兩個隊列以及怎麼觀察他們的指標說清楚。

因此,蟄劍寫下這篇文章,希望藉此能把這個問題說清楚。歡迎大家一起交流探討。

問題描述

場景:JAVA的client和server,使用socket通信。server使用NIO。

1.間歇性得出現client向server建立連接三次握手已經完成,但server的selector沒有響應到這連接。

2.出問題的時間點,會同時有很多連接出現這個問題。

3.selector沒有銷毀重建,一直用的都是一個。

4.程序剛啟動的時候必會出現一些,之後會間歇性出現。

分析問題


正常TCP建連接三次握手過程:


第一步:client 發送 syn 到server 發起握手;

第二步:server 收到 syn後回復syn+ack給client;

第三步:client 收到syn+ack後,回復server一個ack表示收到了server的syn+ack(此時client的56911埠的連接已經是established)。

從問題的描述來看,有點像TCP建連接的時候全連接隊列(accept隊列,後面具體講)滿了,尤其是症狀2、4. 為了證明是這個原因,馬上通過 netstat -s | egrep "listen" 去看隊列的溢出統計數據:    

反覆看了幾次之後發現這個overflowed 一直在增加,那麼可以明確的是server上全連接隊列一定溢出了。

接著查看溢出後,OS怎麼處理:

tcp_abort_on_overflow 為0表示如果三次握手第三步的時候全連接隊列滿了那麼server扔掉client 發過來的ack(在server端認為連接還沒建立起來)

為了證明客戶端應用代碼的異常跟全連接隊列滿有關係,我先把tcp_abort_on_overflow修改成 1,1表示第三步的時候如果全連接隊列滿了,server發送一個reset包給client,表示廢掉這個握手過程和這個連接(本來在server端這個連接就還沒建立起來)。

接著測試,這時在客戶端異常中可以看到很多connection reset by peer的錯誤,到此證明客戶端錯誤是這個原因導致的(邏輯嚴謹、快速證明問題的關鍵點所在)。

於是開發同學翻看java 原始碼發現socket 默認的backlog(這個值控制全連接隊列的大小,後面再詳述)是50,於是改大重新跑,經過12個小時以上的壓測,這個錯誤一次都沒出現了,同時觀察到 overflowed 也不再增加了。

到此問題解決,簡單來說TCP三次握手後有個accept隊列,進到這個隊列才能從Listen變成accept,默認backlog 值是50,很容易就滿了。滿了之後握手第三步的時候server就忽略了client發過來的ack包(隔一段時間server重發握手第二步的syn+ack包給client),如果這個連接一直排不上隊就異常了。

但是不能只是滿足問題的解決,而是要去復盤解決過程,中間涉及到了哪些知識點是我所缺失或者理解不到位的;這個問題除了上面的異常信息表現出來之外,還有沒有更明確地指徵來查看和確認這個問題。


深入理解TCP握手過程中建連接的流程和隊列

(圖片來源:http://www.cnxct.com/something-about-phpfpm-s-backlog/)

如上圖所示,這裡有兩個隊列:syns queue(半連接隊列);accept queue(全連接隊列)。

三次握手中,在第一步server收到client的syn後,把這個連接信息放到半連接隊列中,同時回復syn+ack給client(第二步);


第三步的時候server收到client的ack,如果這時全連接隊列沒滿,那麼從半連接隊列拿出這個連接的信息放入到全連接隊列中,否則按tcp_abort_on_overflow指示的執行。

這時如果全連接隊列滿了並且tcp_abort_on_overflow是0的話,server過一段時間再次發送syn+ack給client(也就是重新走握手的第二步),如果client超時等待比較短,client就很容易異常了。

在我們的os中retry 第二步的默認次數是2(centos默認是5次):


如果TCP連接隊列溢出,有哪些指標可以看呢?

上述解決過程有點繞,聽起來懵,那麼下次再出現類似問題有什麼更快更明確的手段來確認這個問題呢?(通過具體的、感性的東西來強化我們對知識點的理解和吸收。)

netstat -s

比如上面看到的 667399 times ,表示全連接隊列溢出的次數,隔幾秒鐘執行下,如果這個數字一直在增加的話肯定全連接隊列偶爾滿了。

ss 命令

上面看到的第二列Send-Q 值是50,表示第三列的listen埠上的全連接隊列最大為50,第一列Recv-Q為全連接隊列當前使用了多少。

全連接隊列的大小取決於:min(backlog, somaxconn) . backlog是在socket創建的時候傳入的,somaxconn是一個os級別的系統參數。

這個時候可以跟我們的代碼建立聯繫了,比如Java創建ServerSocket的時候會讓你傳入backlog的值:

(來自JDK幫助文檔:https://docs.oracle.com/javase/7/docs/api/java/net/ServerSocket.html)

半連接隊列的大小取決於:max(64,  /proc/sys/net/ipv4/tcp_max_syn_backlog),不同版本的os會有些差異。

我們寫代碼的時候從來沒有想過這個backlog或者說大多時候就沒給他值(那麼默認就是50),直接忽視了他,首先這是一個知識點的盲點;其次也許哪天你在哪篇文章中看到了這個參數,當時有點印象,但是過一陣子就忘了,這是知識之間沒有建立連接,不是體系化的。但是如果你跟我一樣首先經歷了這個問題的痛苦,然後在壓力和痛苦的驅動自己去找為什麼,同時能夠把為什麼從代碼層推理理解到OS層,那麼這個知識點你才算是比較好地掌握了,也會成為你的知識體系在TCP或者性能方面成長自我生長的一個有力抓手。


netstat 命令

netstat跟ss命令一樣也能看到Send-Q、Recv-Q這些狀態信息,不過如果這個連接不是Listen狀態的話,Recv-Q就是指收到的數據還在緩存中,還沒被進程讀取,這個值就是還沒被進程讀取的 bytes;而 Send 則是發送隊列中沒有被遠程主機確認的 bytes 數。

netstat -tn 看到的 Recv-Q 跟全連接半連接沒有關係,這裡特意拿出來說一下是因為容易跟 ss -lnt 的 Recv-Q 搞混淆,順便建立知識體系,鞏固相關知識點 。  

比如如下netstat -t 看到的Recv-Q有大量數據堆積,那麼一般是CPU處理不過來導致的:

 

上面是通過一些具體的工具、指標來認識全連接隊列(工程效率的手段)。  


實踐驗證一下上面的理解

把java中backlog改成10(越小越容易溢出),繼續跑壓力,這個時候client又開始報異常了,然後在server上通過 ss 命令觀察到:

按照前面的理解,這個時候我們能看到3306這個埠上的服務全連接隊列最大是10,但是現在有11個在隊列中和等待進隊列的,肯定有一個連接進不去隊列要overflow掉,同時也確實能看到overflow的值在不斷地增大。


Tomcat和Nginx中的Accept隊列參數

Tomcat默認短連接,backlog(Tomcat裡面的術語是Accept count)Ali-tomcat默認是200, Apache Tomcat默認100。

Nginx默認是511

因為Nginx是多進程模式,所以看到了多個8085,也就是多個進程都監聽同一個埠以儘量避免上下文切換來提升性能   


總結


全連接隊列、半連接隊列溢出這種問題很容易被忽視,但是又很關鍵,特別是對於一些短連接應用(比如Nginx、PHP,當然他們也是支持長連接的)更容易爆發。 一旦溢出,從cpu、線程狀態看起來都比較正常,但是壓力上不去,在client看來rt也比較高(rt=網絡+排隊+真正服務時間),但是從server日誌記錄的真正服務時間來看rt又很短。

jdk、netty等一些框架默認backlog比較小,可能有些情況下導致性能上不去。

希望通過本文能夠幫大家理解TCP連接過程中的半連接隊列和全連接隊列的概念、原理和作用,更關鍵的是有哪些指標可以明確看到這些問題(工程效率幫助強化對理論的理解)。

另外每個具體問題都是最好學習的機會,光看書理解肯定是不夠深刻的,請珍惜每個具體問題,碰到後能夠把來龍去脈弄清楚,每個問題都是你對具體知識點通關的好機會。

最後提出相關問題給大家思考


全連接隊列滿了會影響半連接隊列嗎?

netstat -s看到的overflowed和ignored的數值有什麼聯繫嗎?

如果client走完了TCP握手的第三步,在client看來連接已經建立好了,但是server上的對應連接實際沒有準備好,這個時候如果client發數據給server,server會怎麼處理呢?(有同學說會reset,你覺得呢?)

提出這些問題,希望以這個知識點為抓手,讓你的知識體系開始自我生長。

參考文章

http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html

http://www.cnblogs.com/zengkefu/p/5606696.html

http://www.cnxct.com/something-about-phpfpm-s-backlog/

http://jaseywang.me/2014/07/20/tcp-queue-%E7%9A%84%E4%B8%80%E4%BA%9B%E9%97%AE%E9%A2%98/

http://jin-yang.github.io/blog/network-synack-queue.html#

http://blog.chinaunix.net/uid-20662820-id-4154399.html

每天一篇技術文章,

看不過癮?

關注「阿里巴巴機器智能」,

發現更多AI乾貨。

 ↑ 翹首以盼等你關注


你可能還喜歡

點擊下方圖片即可閱讀

如果你的世界只剩下代碼.

為什麼做技術 PM 這麼難?

畢業3年,為何技術能力相差越來越大?

關注「阿里技術」

把握前沿技術脈搏

相關焦點

  • TCP三次握手,四次揮手,你真的懂嗎?
    本文來自於作者投稿,公眾號:碼農桃花源這時候我會「胸有成竹」地「背誦」前期準備好的「答案」,第一次怎麼怎麼,第二次……答完後就沒有下文了,面試官貌似也沒有深入下去的意思,深入下去我也不懂,皆大歡喜!作為程式設計師,要有「刨根問底」的精神,知其然,更要知其所以然。這篇文章希望能抽絲剝繭,還原背後的原理。
  • 從源碼看tcp三次握手(上)
    著名的TCP三次握手 前面已經介紹過常用套接字接口函數,也就是伺服器調用bind、listen 以及 accept 等待客戶端進行連接,而客戶端connect函數主動請求連接伺服器。當客戶端使用tcp套接字進行連接時,調用 connect 函數將激發 TCP 的三次握手過程。如下圖:TCP三次握手的剖析 這裡我們使用的網絡編程模型都是阻塞式的。所謂阻塞式,就是調用發起後不會直接返回,由作業系統內核處理之後才會返回。相對的,還有一種叫做非阻塞式的,暫時先不討論。
  • TCP三次握手及四次揮手詳細圖解
    相對於SOCKET開發者,TCP創建過程和連結折除過程是由TCP/IP協議棧自動創建的.因此開發者並不需要控制這個過程.但是對於理解
  • 理解TCP/IP三次握手與四次揮手的正確姿勢
    中間雙方可能就要不斷的確認網絡是否恢復,但是有時候會:她:「你可以聽到了嗎?」我:「可以了,你呢?」、她:「喂喂,你可以聽到了嗎?」我:「可以了,我可以聽到了,你呢?」順便說一句,原則上任何數據傳輸都無法確保絕對可靠,三次握手只是確保可靠的基本需要。
  • Wireshark 基本介紹和學習 TCP 三次握手
    記得大學的時候就學習過TCP的三次握手協議,那時候只是知道,雖然在書上看過很多TCP和UDP的資料,但是從來沒有真正見過這些數據包, 老是感覺在雲上飄一樣,學得不踏實。有了wireshark就能截獲這些網絡數據包,可以清晰的看到數據包中的每一個欄位。更能加深我們對網絡協議的理解。對我而言, wireshark 是學習網絡協議最好的工具。
  • 一文徹底搞懂 TCP三次握手、四次揮手過程及原理
    中間雙方可能就要不斷的確認網絡是否恢復,但是有時候會:她:「你可以聽到了嗎?」我:「可以了,你呢?」、她:「喂喂,你可以聽到了嗎?」我:「可以了,我可以聽到了,你呢?」她:「你可以聽到了嗎?」註:以下情節純屬虛構 方案TCP建立連接為什麼是三次握手,而不是兩次或四次?TCP,名為傳輸控制協議,是一種可靠的傳輸層協議,IP協議號為6。順便說一句,原則上任何數據傳輸都無法確保絕對可靠,三次握手只是確保可靠的基本需要。舉個日常例子,打電話時我們對話如下:
  • 如果TCP不握手三次,改成兩次或者四次會怎樣?
    TCP的「三次握手」是什麼意思?介紹完了TCP到底是個啥,現在我們來講講,TCP這個「握手」是怎麼個回事兒。「握手」你可以理解為是TCP發功時所需要的儀式。其實呢,三次握手就是為了對每次發送的數據量進行跟蹤與協商,確保數據段的發送和接收同步,根據所接收到的數據量而確認數據發送、接收完畢後何時撤消聯繫,並建立虛連接。那說了半天,到底握手是怎麼握?
  • 35 張圖解被問千百遍的 TCP 三次握手和四次揮手面試題
    02 TCP 連接建立TCP 三次握手過程和狀態變遷TCP 是面向連接的協議,所以使用 TCP 前必須先建立連接,而建立連接是通過三次握手而進行的。TCP 三次握手一開始,客戶端和服務端都處於 CLOSED 狀態。
  • 以女朋友為例講解 TCP/IP 三次握手與四次揮手
    中間雙方可能就要不斷的確認網絡是否恢復,但是有時候會:她:「你可以聽到了嗎?」我:「可以了,你呢?」、她:「喂喂,你可以聽到了嗎?」我:「可以了,我可以聽到了,你呢?」她:「你可以聽到了嗎?」註:以下情節純屬虛構方案TCP建立連接為什麼是三次握手,而不是兩次或四次?TCP,名為傳輸控制協議,是一種可靠的傳輸層協議,IP協議號為6。順便說一句,原則上任何數據傳輸都無法確保絕對可靠,三次握手只是確保可靠的基本需要。舉個日常例子,打電話時我們對話如下:
  • 為什麼 TCP 建立連接需要三次握手
    如果你有想要了解的問題,可以在文章下面留言。TCP 協議是我們幾乎每天都會接觸到的網絡協議,絕大多數網絡連接的建立都是基於 TCP 協議的,學過計算機網絡或者對 TCP 協議稍有了解的人都知道 —— 使用 TCP 協議建立連接需要經過三次握手(three-way handshake)。
  • TCP的 「三次握手」 和「四次揮手」,到底是什麼鬼?
    顯然這很重要,至少能夠幫助你找到工作的原因之一,學習網絡知識點太多太多,沒有那麼快就能記住。理解的網絡協議,應該從背景原理去著手。那麼從現在開始請認真閱讀了哦!適合人群,對計算機有相應的了解,入門軟體技術的朋友。從客戶端發送請求,會是神馬結果?
  • TCP為什麼需要三次握手?用最通俗的話解釋給你聽
    TCP實現原理和為什麼需要三次握手?兩次握手不可以?四次握手不可以?讀者可以帶著疑問,看一遍本篇博客的詳細講解ok,首先解釋原因之前還是要先複習一下TCP的基本知識和三次握手協議:1、什麼是TCP協議?
  • 網際網路基礎-TCP網絡「握手」協議
    有一些網絡基礎的同學都知道TCP連接需要三次握手,斷開需要4次握手。但是如果要說TCP連接建立和斷開過程中,連接狀態是如何變化的,估計很難說出具體情況,下面我們看一張圖,描述了TCP連接狀態變化過程: tcp連接狀態變化過程這裡有幾個問題需要注意:1、為什麼建立連接時還需要第3次確認
  • TCP/IP三次握手與四次揮手學習筆記(一)
    劃重點:TCP(傳輸控制協議)和IP(網際協議) 是最先定義的兩個核心協議,所以才統稱為TCP/IP協議族TCP的三次握手四次揮手TCP是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,在發送數據前,通信雙方必須在彼此間建立一條連接。
  • 重學TCP/IP協議和三次握手四次揮手
    需要注意的是:三次握手三次握手的本質是確認通信雙方收發數據的能力首先,我讓信使運輸一份信件給對方,對方收到了,那麼他就知道了我的發件能力和他的收件能力是可以的。於是他給我回信,我若收到了,我便知我的發件能力和他的收件能力是可以的,並且他的發件能力和我的收件能力是可以。
  • 傳輸層協議 TCP:TCP 為什麼握手是 3 次、揮手是 4 次?
    狀態健康嗎?傳輸速度如何?等。所以,連接是網絡行為狀態的記錄。和連接關聯的還有一個名詞,叫作會話(Session),會話是應用的行為。比如微信裡張三和你聊天,那麼張三和你建立一個會話。你要和張三聊天,你們創建一個聊天窗口,這個就是會話。你開始 Typing,開始傳輸數據,你和微信伺服器間建立一個連接。如果你們聊一段時間,各自休息了,約定先不要關微信,1 個小時後再回來。
  • 面試官問:為什麼 TCP 建立連接需要三次握手 ?
    需要注意的是我們會將重點放到為什麼需要 TCP 建立連接需要『三次握手』,而不僅僅是為什麼需要『三次』握手。概述在具體分析今天的問題之前,我們首先可以了解一下最常見的錯誤類比,這個對 TCP 連接過程的錯誤比喻誤導了很多人,作者在比較長的一段時間內也認為它能夠很好地描述 TCP 建立連接為什麼需要三次握手:這種用類比來解釋問題往往就會面臨『十個類比九個錯』的尷尬局面,如果別人用類比回答你的為什麼
  • 近兩萬字 TCP 硬核知識,教你吊打面試官!
    接下來,將以三個角度來闡述提升 TCP 的策略,分別是:TCP 三次握手的性能提升;TCP 四次揮手的性能提升;TCP 數據傳輸的性能提升;TCP 三次握手的性能提升TCP 是面向連接的、可靠的、雙向傳輸的傳輸層通信協議,所以在傳輸數據之前需要經過三次握手才能建立連接。
  • 關於 TCP 三次握手和四次揮手,滿分回答在此
    掌握了本文講的全部知識點,關於 TCP 三次握手和四次揮手基本就 OK 了 😊1. TCP 和 UDP講解 TCP 三次握手和四次握手之前,我們先了解一下 TCP 和 UDP 這兩個重量級的傳輸層協議。
  • 關於TCP三次握手和四次揮手,滿分回答在此
    掌握了本文講的全部知識點,關於 TCP 三次握手和四次揮手基本就 OK 了 😊1. TCP 和 UDP講解 TCP 三次握手和四次握手之前,我們先了解一下 TCP 和 UDP 這兩個重量級的傳輸層協議。