1. 程式人生 > >Broken Pipe

Broken Pipe

Broken Pipe發生的原因

當某個程序試圖往一個已收到RST的SOCKET連線寫資料,就會出現Broken Pipe。
(由於TCP協議層已經處於RST狀態了,因此不會將資料發出,而是發一個SIGPIPE訊號給應用層,SIGPIPE訊號的預設處理動作是終止程式。)

那麼確定什麼時候TCP會發送RST報文段,就可以確定Broken Pipe發生的具體原因。

之前已經分析了TCP RST報文產生的幾種情景了。

原因分析

broken pipe出現的前提條件是程序試圖往一個已經在RST狀態的TCP連線寫入資料。

那麼這個寫入資料,到底應該怎麼理解呢?到底是程序試圖往本地SendQ傳送快取區寫入資料還是TCP協議試圖將SendQ的資料傳送到對端的RecvQ呢?按照字面意思應該是前者。

之前我們已經分析了幾種會出現RST報文的情況。
結合我們出現該異常的介面分析。我們發現我們出錯的介面,返回的資料,最小的8K多,最大的超過128K。查閱了幾天的異常日誌,都沒有發現一個報出broken pipe異常錯誤的介面的返回資料小於8K。

根據RST報文產生的情況,我們可以做出如下推斷,當Client端與我們的伺服器建立了TCP連結之後。當TCP協議將服務端SendQ佇列裡的內容傳送到對端(Client)的ReceQ佇列中後,Client關閉了程序,此時ReceQ 讀取快取區還有資料未被讀取(不管ReceQ的資料Client端有沒有讀取過,也不管TCP將多少服務端的資料發到了ReceQ,總之,就是在關閉的時候,還有資料存在於讀取快取區中),這時候關閉socket,會導致端Client端產生一個RST重置報文。
這時候服務端的資料還沒有寫完,會繼續寫入,當再次寫入的時候,TCP協議已經是RST狀態的了,這個時候,就會發生broken pipe。

上面的推論能夠解釋broken pipe發生的一整個流程。

那我們來檢視下linux伺服器的預設SendQ大小與我們介面返回的資料大小的對比,就能夠是否確實是這些介面的資料寫入都需要多次寫入快取區。

那麼如何檢視linux預設的SendQ緩衝區大小呢。linux給我們提供了相應的命令

ubuntu@VM-104-50-ubuntu:/data/iyourcar$ cat /proc/sys/net/ipv4/tcp_wmem
4096    16384   4194304

最小    預設     最大

我們發現,預設寫快取區大小是16k,可是我們的介面返回的資料最小的是8k多呀,這個介面返回的資料是可以一次寫入快取區的呀。咋回事呀,怎麼這樣也會broken呢。如果服務端一次性寫入16k資料到寫快取區,那麼是不可能出現broken pipe的呀。那隻能證明我們的程式並不是一次性寫入16k的資料給快取區,這個大小肯定是要比8k多要小的。那我們就來求證一下,用一個會報出異常的介面在出現異常的地方進行debug,主要debug寫入資料的流程。

使用的內建容器是Tomcat

OutputBuffer#appendByteArray


 public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

  public OutputBuffer() {

        this(DEFAULT_BUFFER_SIZE);

    }
   private void appendByteArray(byte src[], int off, int len) throws IOException {
        if (len == 0) {
            return;
        }

        int limit = bb.capacity();
        //我們發現,每一次寫入的位元組數bb.capacity()大小,而預設的capacity大小就是8*1024,也就是8k
        while (len >= limit) {
            realWriteBytes(ByteBuffer.wrap(src, off, limit));
            len = len - limit;
            off = off + limit;
        }

        if (len > 0) {
            transfer(src, off, len, bb);
        }
    }

我們發現,實際上,tomcat幫我們向socket寫入資料的時候,是每8k寫入一次SendQ(但是真正TCP傳送資料,可能是分很多塊去傳送到對端的ReceQ的)

後來我們將內建web容器換成了undertow,發現依舊會發生這種情況,應該預設也是一次寫入8k吧,具體還沒有debug。

所以當使用的容器是tomcat的時候,只要介面返回資料的大小大於8k,就可能會出現broken pipe。