轉(zhuǎn)載自:https://www.cnblogs.com/metoy/p/6565486.html
想必或多或少在Java的服務(wù)器都會遇到過這種異常,如下圖

由于Java偏上層,日常開發(fā)接觸系統(tǒng)底層的機會偏少,要搞清楚什么原因?qū)е碌倪@種異常,肯定是先要百度google一番。
網(wǎng)絡(luò)解釋云里霧里
百度+google下,巴拉巴拉還真不少介紹這個錯誤的文章。欣喜地翻了一篇又一篇,但好像我依舊不明白具體什么原因?qū)е碌?,云里霧里啊。好吧,舉兩個例子:
例子一:

這上邊說的好像有點道理,寫個代碼做個試驗驗證下吧!直接上代碼:
//client程序publicstaticvoidmain(String[] args){try{? ? ? ? ? ? Socket s =newSocket();? ? ? ? ? ? s.connect(newInetSocketAddress("127.0.0.1",3113));? ? ? ? ? ? OutputStream os = s.getOutputStream();? ? ? ? ? ? os.write("hello".getBytes());? ? ? ? ? ? s.close();? ? ? ? ? ? System.in.read();//防止程序退出}catch(Exception e){? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }//server程序publicstaticvoidmain(String[] args){try{? ? ? ? ? ? ServerSocket ss =newServerSocket(3113);? ? ? ? ? ? Socket s = ss.accept();? ? ? ? ? ? InputStreamis= s.getInputStream();byte[] buf =newbyte[1024];intlen =is.read(buf);? ? ? ? ? ? System.out.println("recv:"+newString(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發(fā)送請求,然后調(diào)用close()關(guān)閉連接,服務(wù)端收到請求打印到控制臺,等待10秒(保證client關(guān)閉了連接),然后繼續(xù)向client發(fā)數(shù)據(jù)。看一下控制臺的結(jié)果:

挺討厭,就是不報Broken pipe異常。上邊的文章,想說相信你真的好難?。∧窃倏戳硪黄恼掳?/p>
例二:

這篇文章倒列舉了好幾種原因,點擊了stop按鈕?被tomcat停掉?線程機制產(chǎn)生jvm出錯?真不知他媽的在說什么,難道就不能再具體點嗎?
這樣的文章看不上幾篇就煩了。
意外發(fā)現(xiàn)
網(wǎng)上找不到滿意的解釋,那就硬著頭皮翻翻講解底層一點的書籍吧。還真巧,在一本叫《UNIX網(wǎng)絡(luò)編程卷1》中獲得了一點靈感。如下截圖:

如下劃線部分所說:向某個已收到RST的連接執(zhí)行寫操作時,將會返回EPIPE錯誤。EPIPE!PIPE!第一百零一靈感告訴我這與Broken pipe錯誤有關(guān)系。好了,有了新的發(fā)現(xiàn)就程序驗證吧。
為了順利實驗,先把實驗用到的兩個知識點說一下吧。
知識準備之RST報文
終止一個TCP連接的正常方式是發(fā)送FIN。在發(fā)送緩沖區(qū)中所有排隊數(shù)據(jù)都已發(fā)送之后才發(fā)送FIN,正常情況下沒有任何數(shù)據(jù)丟失。但我們有時也可能發(fā)送一個RST報文段而不是FIN來中途關(guān)閉一個連接。這稱為異常關(guān)閉。
現(xiàn)在知道RST報文的作用了,那就在大致列一下出現(xiàn)RST報文的場景吧:
1.connect一個不存在的端口;
2.向一個已經(jīng)關(guān)掉的連接send數(shù)據(jù);
3.向一個已經(jīng)崩潰的對端發(fā)送數(shù)據(jù)(連接之前已經(jīng)被建立);
4.close(sockfd)時,直接丟棄接收緩沖區(qū)未讀取的數(shù)據(jù),并給對方發(fā)一個RST。這個是由SO_LINGER選項來控制的;
5.a重啟,收到b的保活探針,a發(fā)rst,通知b。
模擬出現(xiàn)RST報文的場景,最簡單地方法感覺就是使用SO_LINGER選項來控制,那接下來再了解下SO_LINGER選項吧!
知識準備之SO_LINGER參數(shù)
? ?SO_LINGER是用來設(shè)置函數(shù)close()關(guān)閉TCP連接時的行為。缺省close()的行為是,如果有數(shù)據(jù)殘留在socket發(fā)送緩沖區(qū)中則系統(tǒng)將繼續(xù)發(fā)送這些數(shù)據(jù)給對方,等待被確認,然后返回。
設(shè)置此選項并把超時時間設(shè)置為零,調(diào)用close()會立即關(guān)閉該連接,通過發(fā)送RST分組(而不是用正常的FIN|ACK|FIN|ACK四個分組)來關(guān)閉該連接。至于發(fā)送緩沖區(qū)中如果有未發(fā)送完的數(shù)據(jù),則丟棄。
知識準備的差不多了,好了,準備開森的實驗了。
實驗驗證
? ?這里再將實驗代碼貼一份吧,跟上邊的實驗代碼唯一的區(qū)別就是這里設(shè)置了SO_LINGER選項。
//client程序publicstaticvoidmain(String[] args){try{? ? ? ? ? ? Socket s =newSocket();? ? ? ? ? ? s.setSoLinger(true,0);//設(shè)置調(diào)用close就發(fā)送RSTs.connect(newInetSocketAddress("127.0.0.1",3113));? ? ? ? ? ? OutputStream os = s.getOutputStream();? ? ? ? ? ? os.write("hello".getBytes());? ? ? ? ? ? s.close();? ? ? ? ? ? System.in.read();//防止程序退出}catch(Exception e){? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }//server程序publicstaticvoidmain(String[] args){try{? ? ? ? ? ? ServerSocket ss =newServerSocket(3113);? ? ? ? ? ? Socket s = ss.accept();? ? ? ? ? ? InputStreamis= s.getInputStream();byte[] buf =newbyte[1024];intlen =is.read(buf);? ? ? ? ? ? System.out.println("recv:"+newString(buf,0,len));? ? ? ? ? ? Thread.sleep(10000);? ? ? ? ? ? s.getOutputStream().write("hello".getBytes());? ? ? ? ? ? System.out.println("send over");? ? ? ? ? ? System.in.read();? ? ? ? }catch(Exception e){? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }
這次果不其然,終于遇到了期盼的異常。不信?那我截圖你看:
這下你信了吧。這時你是不是也有點好奇,真的是設(shè)置了SO_LINGER產(chǎn)生了RST報文嗎?client和server之間到底進行了怎么樣的交互呢?

想看清具體client和server期間進行了怎樣的交互,那就只好抓包了。就用tcpdump抓包看吧,不管你會不會用,它都是簡單方便快捷的好工具,絕對是分析TCP的好幫手。
抓包分析
就按照上邊的實驗程序抓個包吧,又大又清晰地截圖^_^

簡單解釋下:localhost.50387是client端,localhost.cs-auth-svr是server端。
第一行:client向server發(fā)送SYN請求建立連接
第二行:server向client發(fā)送SYN也請求建立連接
第三行:client向server返回ACK表示同意連接
第四行:server向client發(fā)送ack?什么?TCP三步握手建立連接怎么變成四步了?啥時候的事啊咋沒通知我???難道我的mac不在狀態(tài)手滑了就發(fā)出去了?算了先不care這個問題了,知道的可以告訴下我。
第五行:看到Flags [P.]了嗎,P是push的意思就是發(fā)數(shù)據(jù),這里就是client向server發(fā)送數(shù)據(jù),length 5就是client發(fā)送的hello的長度,沒錯吧
第六行:這里是server向client發(fā)送ac表示已經(jīng)接收了hello
第七行:這是重點,F(xiàn)lags[R.],R就代表RST報文,client向server發(fā)送了RST報文。
現(xiàn)在應(yīng)該一切云開月明了吧。^_^
收到RST包,繼續(xù)向?qū)Ψ綄憯?shù)據(jù)就一定會報Broken pipe嗎?還真的被我試出個不會的情況。
特殊情況
? ?這個特殊情況也很好理解,按照上邊說的:向一個已經(jīng)關(guān)掉的連接send數(shù)據(jù)時會收到對方的RST報文。此時再向其sends數(shù)據(jù)就不會報Broken pipe。直接上測試程序和抓包吧
//client程序publicstaticvoidmain(String[] args){try{? ? ? ? ? ? Socket s =newSocket();? ? ? ? ? ? s.connect(newInetSocketAddress("127.0.0.1",3113));? ? ? ? ? ? OutputStream os = s.getOutputStream();? ? ? ? ? ? os.write("hello".getBytes());? ? ? ? ? ? s.close();? ? ? ? ? ? System.in.read();//防止程序退出}catch(Exception e){? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }//server程序publicstaticvoidmain(String[] args){try{? ? ? ? ? ? ServerSocket ss =newServerSocket(3113);? ? ? ? ? ? Socket s = ss.accept();? ? ? ? ? ? InputStreamis= s.getInputStream();byte[] buf =newbyte[1024];intlen =is.read(buf);? ? ? ? ? ? System.out.println("recv:"+newString(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調(diào)用close向server發(fā)送FIN,server向client發(fā)送hello,然后收到client的RST報文,繼續(xù)向client發(fā)送hello2。

? ?上邊流程可以看到,client向server發(fā)送了RST報文,但是服務(wù)器繼續(xù)寫也不會報錯,畢竟誰讓client之前就向server發(fā)送了FIN表示正常關(guān)閉呢。
尾言
? ? 分析到這里,Broken pipe錯誤的原因應(yīng)該很清楚了吧。但是還需要強調(diào),上邊的實驗分析過程是在UNIX(MAC)下完成的,這個實驗對windows不成立,咱們Java都是跑在linux上可以先不care。Linux應(yīng)該跟UNIX差不多,當然這里我沒有測驗,測出差異來的可以分享下。就這樣吧