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

原文: http://lovestblog.cn/blog/2014/05/20/tcp-broken-pipe/

作者:你假笨@JVM


  以前我們經常會碰到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會關閉,因此會拋出這個顧名思義的異常


發佈了195 篇原創文章 · 獲贊 64 · 訪問量 50萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章