1. 程式人生 > >從tcp原理角度理解Broken pipe和Connection Reset by Peer的區別

從tcp原理角度理解Broken pipe和Connection Reset by Peer的區別

以前我們經常會碰到Broken pipe或者Connection reset by peer之類的異常,但是tcp實現裡什麼情況下會丟擲這些異常呢,以前我給對方的回答都是模稜兩可的,自己說實話都沒把握,因為自己也沒有驗證過,對它們的認識都是從網上看來的,正確與否也不知道,昨天獨明突然又問到這個問題,前段時間正好對tcp這塊研究了一段時間,有了點理論知識之後再從實踐角度對此問題進行一下分析,下面對我這次的調研過程進行下描述與大家分享,希望大家以後對此類問題都能很自信地應答。

三次握手和四次揮手過程

  在講具體的原因之前,我們有必要補充下tcp這塊的一些基礎知識,我們都知道tcp通訊有三次握手和四次揮手,網上介紹的文章也一大堆,圖我也懶得畫了,直接網上找一個圖給大家

  三次握手是最前面的三條線表示的過程,四次揮手是最後面的四條線表示的過程,裡面涉及到幾個關鍵詞,SYN,ACK,FIN,MSS,其中SYN是主要用在三次握手過程中的,FIN用在四次揮手過程中,ACK在三次握手和四次揮手過程中的作用就是對收到的SYN和FIN做一個確認,SYN,FIN等存在於TCP頭裡(tcp報文圖也給大家弄了個圖,不用再去找啦),0/1表示有無此標記,在tcp實現裡後面還會跟一個依次遞增的數字,比如上面的J,K等,確認就是遞增這些數字(真正的資料報文的ack除外),MSS是表示每一個tcp報文裡資料欄位的最大長度,不包括tcp頭的大小噢 相信大家看到這兩個圖會對這些概念有了一個清晰的認識了

tcpdump抓包工具

  介紹了基礎原理之後,再介紹下抓包工具,tcpdump,這工具對你瞭解tcp的整個過程會非常有幫助,在你無法除錯tcp實現的情況下這個工具自然也是必不可少的,具體用法網上有很多介紹,直接從man page上也可以看到詳細的介紹,我也不多說啦,下面的截圖就是tcpdump根據tcp通訊過程獲取到的

  這要稍微提下tcpdump的結果和上面的幾個過程的對應關係 前面三條其實就是我們上面所說的三次握手,四次握手過程上面沒有完全表現出來,只完成了一半的揮手過程(5,8兩條表示的) 裡面有幾個標識S,F,ack,P,其實還有個R,如果有這些標識那麼在tcp頭裡的SYN,FIN,ACK,PSH,RET分別為1,其中PSH表示要求tcp立即將資料傳遞給上層,不要做別的什麼處理,RET這個表示重置連線,也是和我們今天討論的問題有很大關係的FLAG,下面會詳細介紹

reset報文傳送場景

  RST的標誌位,這個標識為在如下幾種情況下會被設定,以下是我瞭解的情況,可能還有更多的場景,沒有驗證

  • 當嘗試和未開放的伺服器埠建立tcp連線時,伺服器tcp將會直接向客戶端傳送reset報文
  • 雙方之前已經正常建立了通訊通道,也可能進行過了互動,當某一方在互動的過程中發生了異常,如崩潰等,異常的一方會向對端傳送reset報文,通知對方將連線關閉
  • 當收到TCP報文,但是發現該報文不是已建立的TCP連線列表可處理的,則其直接向對端傳送reset報文
  • ack報文丟失,並且超出一定的重傳次數或時間後,會主動向對端傳送reset報文釋放該TCP連線

Broken pipe以及Connection reset by peer

  做了這麼些鋪墊之後下面進入正題,那麼Broken pipe或者Connection reset by peer分別代表什麼意思呢,下面從glibc的原始碼裡有對此的介紹

#. TRANS Broken pipe; there is no process reading from the other end of a pipe.
#. TRANS Every library function that returns this error code also generates a
#. TRANS @code{SIGPIPE} signal; this signal terminates the program if not handled
#. TRANS or blocked.  Thus, your program will never actually see @code{EPIPE}
#. TRANS unless it has handled or blocked @code{SIGPIPE}.
#: sysdeps/generic/siglist.h:39 sysdeps/gnu/errlist.c:359
#: sysdeps/unix/siglist.c:39
msgid "Broken pipe"
msgstr "斷開的管道"

#. TRANS A network connection was closed for reasons outside the control of the
#. TRANS local host, such as by the remote machine rebooting or an unrecoverable
#. TRANS protocol violation.
#: sysdeps/gnu/errlist.c:614
msgid "Connection reset by peer"
msgstr ""

  其實我們java異常裡看到的Broken pipe或者Connection reset by peer資訊不是jdk或者jvm裡定義的,我看到這些關鍵字往往會首先搜尋下jdk或者hotspot原始碼找到位置進行上下文分析,但是這次沒找到,後面才想到應該是linux或者glibc裡定義的,果然在glibc離看到了如上的描述和定義

  對於Broken pipe在管道的另外一端沒有程序再讀的時候就會丟擲此異常,Connection reset by peer的描述其實不是很正確,從我的實踐來看只描述了一方面,其實在某一端正常close之後,也是可能會有此異常的。

異常模擬

  從我的測試場景是這樣的, 共同的前提是客戶端向服務端發了資料之後立馬呼叫close關閉socket並進程退出,而服務端在收到客戶端的資料之後sleep一會,保證對方的socket已經關閉,接著分別進行兩種場景測試

  場景:

  1. 服務端往socket裡寫一次資料,返回繼續做select

  2. 服務端連續寫兩次資料,必須保證兩次的buffer都是有資料的,也就是保證ByteBuffer的pos和limit要不是一個值

  結果:

  1. 會丟擲Connection reset by peer 

  2. 會丟擲Broken pipe

  分析:

  1. 當我們往一個對端已經close的通道寫資料的時候,對方的tcp會收到這個報文,並且反饋一個reset報文,tcpdump的結果如下所示,當收到reset報文的時候,繼續做select讀資料的時候就會丟擲Connect reset by peer的異常,從堆疊可以看得出 

  2. 當第一次往一個對端已經close的通道寫資料的時候會和上面的情況一樣,會收到reset報文,當再次往這個socket寫資料的時候,就會丟擲Broken pipe了 ,根據tcp的約定,當收到reset包的時候,上層必須要做出處理,呼叫將socket檔案描述符進行關閉,其實也意味著pipe會關閉,因此會丟擲這個顧名思義的異常