轉(zhuǎn)載自:https://www.cnblogs.com/metoy/p/6565486.html
想必或多或少在Java的服務(wù)器都會(huì)遇到過(guò)這種異常浴捆,如下圖
由于Java偏上層,日常開(kāi)發(fā)接觸系統(tǒng)底層的機(jī)會(huì)偏少面殖,要搞清楚什么原因?qū)е碌倪@種異常,肯定是先要百度google一番苗踪。
網(wǎng)絡(luò)解釋云里霧里
百度+google下颠猴,巴拉巴拉還真不少介紹這個(gè)錯(cuò)誤的文章洛巢。欣喜地翻了一篇又一篇,但好像我依舊不明白具體什么原因?qū)е碌南寐耍评镬F里啊援岩。好吧,舉兩個(gè)例子:
例子一:
這上邊說(shuō)的好像有點(diǎn)道理掏导,寫個(gè)代碼做個(gè)試驗(yàn)驗(yàn)證下吧享怀!直接上代碼:
//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();? ? ? ? }? ? }
代碼邏輯比較簡(jiǎn)單吧,client向server發(fā)送請(qǐng)求趟咆,然后調(diào)用close()關(guān)閉連接添瓷,服務(wù)端收到請(qǐng)求打印到控制臺(tái),等待10秒(保證client關(guān)閉了連接)值纱,然后繼續(xù)向client發(fā)數(shù)據(jù)鳞贷。看一下控制臺(tái)的結(jié)果:
挺討厭虐唠,就是不報(bào)Broken pipe異常搀愧。上邊的文章,想說(shuō)相信你真的好難敖ァ妈橄!那再看另一篇文章吧
例二:
這篇文章倒列舉了好幾種原因,點(diǎn)擊了stop按鈕翁脆?被tomcat停掉眷蚓?線程機(jī)制產(chǎn)生jvm出錯(cuò)?真不知他媽的在說(shuō)什么反番,難道就不能再具體點(diǎn)嗎沙热?
這樣的文章看不上幾篇就煩了。
意外發(fā)現(xiàn)
網(wǎng)上找不到滿意的解釋罢缸,那就硬著頭皮翻翻講解底層一點(diǎn)的書籍吧篙贸。還真巧,在一本叫《UNIX網(wǎng)絡(luò)編程卷1》中獲得了一點(diǎn)靈感枫疆。如下截圖:
如下劃線部分所說(shuō):向某個(gè)已收到RST的連接執(zhí)行寫操作時(shí)爵川,將會(huì)返回EPIPE錯(cuò)誤。EPIPE息楔!PIPE寝贡!第一百零一靈感告訴我這與Broken pipe錯(cuò)誤有關(guān)系扒披。好了,有了新的發(fā)現(xiàn)就程序驗(yàn)證吧圃泡。
為了順利實(shí)驗(yàn)碟案,先把實(shí)驗(yàn)用到的兩個(gè)知識(shí)點(diǎn)說(shuō)一下吧。
知識(shí)準(zhǔn)備之RST報(bào)文
終止一個(gè)TCP連接的正常方式是發(fā)送FIN颇蜡。在發(fā)送緩沖區(qū)中所有排隊(duì)數(shù)據(jù)都已發(fā)送之后才發(fā)送FIN价说,正常情況下沒(méi)有任何數(shù)據(jù)丟失。但我們有時(shí)也可能發(fā)送一個(gè)RST報(bào)文段而不是FIN來(lái)中途關(guān)閉一個(gè)連接风秤。這稱為異常關(guān)閉鳖目。
現(xiàn)在知道RST報(bào)文的作用了,那就在大致列一下出現(xiàn)RST報(bào)文的場(chǎng)景吧:
1.connect一個(gè)不存在的端口缤弦;
2.向一個(gè)已經(jīng)關(guān)掉的連接send數(shù)據(jù)领迈;
3.向一個(gè)已經(jīng)崩潰的對(duì)端發(fā)送數(shù)據(jù)(連接之前已經(jīng)被建立);
4.close(sockfd)時(shí)甸鸟,直接丟棄接收緩沖區(qū)未讀取的數(shù)據(jù)惦费,并給對(duì)方發(fā)一個(gè)RST。這個(gè)是由SO_LINGER選項(xiàng)來(lái)控制的抢韭;
5.a重啟薪贫,收到b的保活探針刻恭,a發(fā)rst瞧省,通知b。
模擬出現(xiàn)RST報(bào)文的場(chǎng)景鳍贾,最簡(jiǎn)單地方法感覺(jué)就是使用SO_LINGER選項(xiàng)來(lái)控制鞍匾,那接下來(lái)再了解下SO_LINGER選項(xiàng)吧!
知識(shí)準(zhǔn)備之SO_LINGER參數(shù)
? ?SO_LINGER是用來(lái)設(shè)置函數(shù)close()關(guān)閉TCP連接時(shí)的行為骑科。缺省close()的行為是橡淑,如果有數(shù)據(jù)殘留在socket發(fā)送緩沖區(qū)中則系統(tǒng)將繼續(xù)發(fā)送這些數(shù)據(jù)給對(duì)方,等待被確認(rèn)咆爽,然后返回梁棠。
設(shè)置此選項(xiàng)并把超時(shí)時(shí)間設(shè)置為零,調(diào)用close()會(huì)立即關(guān)閉該連接斗埂,通過(guò)發(fā)送RST分組(而不是用正常的FIN|ACK|FIN|ACK四個(gè)分組)來(lái)關(guān)閉該連接符糊。至于發(fā)送緩沖區(qū)中如果有未發(fā)送完的數(shù)據(jù),則丟棄呛凶。
知識(shí)準(zhǔn)備的差不多了男娄,好了,準(zhǔn)備開(kāi)森的實(shí)驗(yàn)了。
實(shí)驗(yàn)驗(yàn)證
? ?這里再將實(shí)驗(yàn)代碼貼一份吧模闲,跟上邊的實(shí)驗(yàn)代碼唯一的區(qū)別就是這里設(shè)置了SO_LINGER選項(xiàng)建瘫。
//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í)你是不是也有點(diǎn)好奇翁授,真的是設(shè)置了SO_LINGER產(chǎn)生了RST報(bào)文嗎?client和server之間到底進(jìn)行了怎么樣的交互呢晾咪?
想看清具體client和server期間進(jìn)行了怎樣的交互收擦,那就只好抓包了。就用tcpdump抓包看吧谍倦,不管你會(huì)不會(huì)用塞赂,它都是簡(jiǎn)單方便快捷的好工具,絕對(duì)是分析TCP的好幫手昼蛀。
抓包分析
就按照上邊的實(shí)驗(yàn)程序抓個(gè)包吧宴猾,又大又清晰地截圖^_^
簡(jiǎn)單解釋下:localhost.50387是client端,localhost.cs-auth-svr是server端叼旋。
第一行:client向server發(fā)送SYN請(qǐng)求建立連接
第二行:server向client發(fā)送SYN也請(qǐng)求建立連接
第三行:client向server返回ACK表示同意連接
第四行:server向client發(fā)送ack?什么仇哆?TCP三步握手建立連接怎么變成四步了?啥時(shí)候的事啊咋沒(méi)通知我胺蛑病讹剔?難道我的mac不在狀態(tài)手滑了就發(fā)出去了?算了先不care這個(gè)問(wèn)題了详民,知道的可以告訴下我延欠。
第五行:看到Flags [P.]了嗎,P是push的意思就是發(fā)數(shù)據(jù)沈跨,這里就是client向server發(fā)送數(shù)據(jù)由捎,length 5就是client發(fā)送的hello的長(zhǎng)度,沒(méi)錯(cuò)吧
第六行:這里是server向client發(fā)送ac表示已經(jīng)接收了hello
第七行:這是重點(diǎn)饿凛,F(xiàn)lags[R.],R就代表RST報(bào)文狞玛,client向server發(fā)送了RST報(bào)文。
現(xiàn)在應(yīng)該一切云開(kāi)月明了吧笤喳。^_^
收到RST包为居,繼續(xù)向?qū)Ψ綄憯?shù)據(jù)就一定會(huì)報(bào)Broken pipe嗎?還真的被我試出個(gè)不會(huì)的情況杀狡。
特殊情況
? ?這個(gè)特殊情況也很好理解蒙畴,按照上邊說(shuō)的:向一個(gè)已經(jīng)關(guān)掉的連接send數(shù)據(jù)時(shí)會(huì)收到對(duì)方的RST報(bào)文。此時(shí)再向其sends數(shù)據(jù)就不會(huì)報(bào)Broken pipe。直接上測(cè)試程序和抓包吧
//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報(bào)文,繼續(xù)向client發(fā)送hello2蹬音。
? ?上邊流程可以看到上煤,client向server發(fā)送了RST報(bào)文,但是服務(wù)器繼續(xù)寫也不會(huì)報(bào)錯(cuò)著淆,畢竟誰(shuí)讓client之前就向server發(fā)送了FIN表示正常關(guān)閉呢劫狠。
尾言
? ? 分析到這里,Broken pipe錯(cuò)誤的原因應(yīng)該很清楚了吧永部。但是還需要強(qiáng)調(diào)独泞,上邊的實(shí)驗(yàn)分析過(guò)程是在UNIX(MAC)下完成的,這個(gè)實(shí)驗(yàn)對(duì)windows不成立苔埋,咱們Java都是跑在linux上可以先不care懦砂。Linux應(yīng)該跟UNIX差不多,當(dāng)然這里我沒(méi)有測(cè)驗(yàn)组橄,測(cè)出差異來(lái)的可以分享下荞膘。就這樣吧