Broken pipe錯誤終極解釋

  • 敘述

    想必或多或少在Java的服務器都會遇到過這種異常,以下圖
    
    因爲Java偏上層,平常開發接觸系統底層的機會偏少,要搞清楚什麼緣由致使的這種異常,確定是先要百度google一番。html

  • 網絡解釋雲裏霧裏

    百度+google下,巴拉巴拉還真很多介紹這個錯誤的文章。欣喜地翻了一篇又一篇,但好像我依舊不明白具體什麼緣由致使的,雲裏霧裏啊。好吧,舉兩個例子:
    例子一:
      
    這上邊說的好像有點道理,寫個代碼作個試驗驗證下吧!直接上代碼:
    java

//client程序 public static void main(String[] args) { try { Socket s = new Socket(); s.connect(new InetSocketAddress("127.0.0.1",3113)); OutputStream os = s.getOutputStream(); os.write("hello".getBytes()); s.close(); System.in.read();//防止程序退出 }catch (Exception e){ e.printStackTrace(); } } //server程序 public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(3113); Socket s = ss.accept(); InputStream is = s.getInputStream(); byte[] buf =new byte[1024]; int len = is.read(buf); System.out.println("recv:"+new String(buf,0,len)); Thread.sleep(10000); s.getOutputStream().write("hello".getBytes()); System.out.println("send over"); System.in.read(); }catch (Exception e){ e.printStackTrace(); } }

    代碼邏輯比較簡單吧,client向server發送請求,而後調用close()關閉鏈接,服務端收到請求打印到控制檯,等待10秒(保證client關閉了鏈接),而後繼續向client發數據。看一下控制檯的結果:
    
    挺討厭,就是不報Broken pipe異常。上邊的文章,想說相信你真的好難啊!那再看另外一篇文章吧
    例二:
      
    這篇文章倒列舉了好幾種緣由,點擊了stop按鈕?被tomcat停掉?線程機制產生jvm出錯?真不知他媽的在說什麼,難道就不能再具體點嗎?
    這樣的文章看不上幾篇就煩了。linux

  • 意外發現

     網上找不到滿意的解釋,那就硬着頭皮翻翻講解底層一點的書籍吧。還真巧,在一本叫《UNIX網絡編程卷1》中得到了一點靈感。以下截圖:
    
    以下劃線部分所說:向某個已收到RST的鏈接執行寫操做時,將會返回EPIPE錯誤。EPIPE!PIPE!第一百零一靈感告訴我這與Broken pipe錯誤有關係。好了,有了新的發現就程序驗證吧。
    爲了順利實驗,先把實驗用到的兩個知識點說一下吧。編程

  • 知識準備之RST報文

    終止一個TCP鏈接的正常方式是發送FIN。在發送緩衝區中全部排隊數據都已發送以後才發送FIN,正常狀況下沒有任何數據丟失。但咱們有時也可能發送一個RST報文段而不是FIN來中途關閉一個鏈接。這稱爲異常關閉。
    如今知道RST報文的做用了,那就在大體列一下出現RST報文的場景吧:
      1.connect一個不存在的端口;
      2.向一個已經關掉的鏈接send數據;
      3.向一個已經崩潰的對端發送數據(鏈接以前已經被創建);
      4.close(sockfd)時,直接丟棄接收緩衝區未讀取的數據,並給對方發一個RST。這個是由SO_LINGER選項來控制的;
      5.a重啓,收到b的保活探針,a發rst,通知b。
    模擬出現RST報文的場景,最簡單地方法感受就是使用SO_LINGER選項來控制,那接下來再瞭解下SO_LINGER選項吧!windows

  • 知識準備之SO_LINGER參數

   SO_LINGER是用來設置函數close()關閉TCP鏈接時的行爲。缺省close()的行爲是,若是有數據殘留在socket發送緩衝區中則系統將繼續發送這些數據給對方,等待被確認,而後返回。tomcat

設置此選項並把超時時間設置爲零,調用close()會當即關閉該鏈接,經過發送RST分組(而不是用正常的FIN|ACK|FIN|ACK四個分組)來關閉該鏈接。至於發送緩衝區中若是有未發送完的數據,則丟棄。

    知識準備的差很少了,好了,準備開森的實驗了。服務器

  • 實驗驗證

   這裏再將實驗代碼貼一份吧,跟上邊的實驗代碼惟一的區別就是這裏設置了SO_LINGER選項。網絡

   這下你信了吧。這時你是否是也有點好奇,真的是設置了SO_LINGER產生了RST報文嗎?client和server之間到底進行了怎麼樣的交互呢?
想看清具體client和server期間進行了怎樣的交互,那就只好抓包了。就用tcpdump抓包看吧,無論你會不會用,它都是簡單方便快捷的好工具,絕對是分析TCP的好幫手。//client程序 public static void main(String[] args) { try { Socket s = new Socket(); s.setSoLinger(true,0);//設置調用close就發送RST s.connect(new InetSocketAddress("127.0.0.1",3113)); OutputStream os = s.getOutputStream(); os.write("hello".getBytes()); s.close(); System.in.read();//防止程序退出 }catch (Exception e){ e.printStackTrace(); } } //server程序 public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(3113); Socket s = ss.accept(); InputStream is = s.getInputStream(); byte[] buf =new byte[1024]; int len = is.read(buf); System.out.println("recv:"+new String(buf,0,len)); Thread.sleep(10000); s.getOutputStream().write("hello".getBytes()); System.out.println("send over"); System.in.read(); }catch (Exception e){ e.printStackTrace(); } }
此次果不其然,終於遇到了期盼的異常。不信?那我截圖你看:

  • 抓包分析

    就按照上邊的實驗程序抓個包吧,又大又清晰地截圖^_^


    簡單解釋下:localhost.50387是client端,localhost.cs-auth-svr是server端。
                      第一行:client向server發送SYN請求創建鏈接
                      第二行:server向client發送SYN也請求創建鏈接
                      第三行:client向server返回ACK表示贊成鏈接
                      第四行:server向client發送ack?什麼?TCP三步握手創建鏈接怎麼變成四步了?啥時候的事啊咋沒通知我啊?難道個人mac不在狀態手滑了就發出去了?算了先不care這個問題了,知道的能夠告訴下我。
                      第五行:看到Flags [P.]了嗎,P是push的意思就是發數據,這裏就是client向server發送數據,length 5就是client發送的hello的長度,沒錯吧
                      第六行:這裏是server向client發送ac表示已經接收了hello
                      第七行:這是重點,Flags[R.],R就表明RST報文,client向server發送了RST報文。

    如今應該一切雲開月明瞭吧。^_^
     收到RST包,繼續向對方寫數據就必定會報Broken pipe嗎?還真的被我試出個不會的狀況。

jvm

  • 特殊狀況

   這個特殊狀況也很好理解,按照上邊說的:向一個已經關掉的鏈接send數據時會收到對方的RST報文。此時再向其sends數據就不會報Broken pipe。直接上測試程序和抓包吧socket

//client程序 public static void main(String[] args) { try { Socket s = new Socket(); s.connect(new InetSocketAddress("127.0.0.1",3113)); OutputStream os = s.getOutputStream(); os.write("hello".getBytes()); s.close(); System.in.read();//防止程序退出 }catch (Exception e){ e.printStackTrace(); } } //server程序 public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(3113); Socket s = ss.accept(); InputStream is = s.getInputStream(); byte[] buf =new byte[1024]; int len = is.read(buf); System.out.println("recv:"+new String(buf,0,len)); Thread.sleep(10000); s.getOutputStream().write("hello".getBytes()); s.getOutputStream().write("hello2".getBytes()); System.out.println("send over"); System.in.read(); }catch (Exception e){ e.printStackTrace(); } }
client調用close向server發送FIN,server向client發送hello,而後收到client的RST報文,繼續向client發送hello2。

   上邊流程能夠看到,client向server發送了RST報文,可是服務器繼續寫也不會報錯,畢竟誰讓client以前就向server發送了FIN表示正常關閉呢。

  • 尾言

    分析到這裏,Broken pipe錯誤的緣由應該很清楚了吧。可是還須要強調,上邊的實驗分析過程是在UNIX(MAC)下完成的,這個實驗對windows不成立,我們Java都是跑在linux上能夠先不care。Linux應該跟UNIX差很少,固然這裏我沒有測驗,測出差別來的能夠分享下。就這樣吧

 
分類:  網絡編程
相關文章
相關標籤/搜索