Broken pipe錯誤終極解釋

  • 敘述

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

  • 網絡解釋雲裏霧裏

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

//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出錯?真不知他媽的在說什麼,難道就不能再具體點嗎?
    這樣的文章看不上幾篇就煩了。

  • 意外發現

     網上找不到滿意的解釋,那就硬着頭皮翻翻講解底層一點的書籍吧。還真巧,在一本叫《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選項吧!

  • 知識準備之SO_LINGER參數

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

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

    知識準備的差不多了,好了,準備開森的實驗了。

  • 實驗驗證

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

   這下你信了吧。這時你是不是也有點好奇,真的是設置了SO_LINGER產生了RST報文嗎?client和server之間到底進行了怎麼樣的交互呢?
   想看清具體client和server期間進行了怎樣的交互,那就只好抓包了。就用tcpdump抓包看吧,不管你會不會用,它都是簡單方便快捷的好工具,絕對是分析TCP的好幫手。
  • 抓包分析

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


    簡單解釋下: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嗎?還真的被我試出個不會的情況。

  • 特殊情況

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

//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表示正常關閉呢。

    原博客地址:https://www.cnblogs.com/metoy/p/6565486.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章