Java異常報錯:?java.io.IOException:?Broken?pipe解決方案
背景
客戶生產(chǎn)環(huán)境的業(yè)務系統(tǒng)能夠正常訪問,但是業(yè)務無法正常進行,排查日志發(fā)現(xiàn)大量的java.io.IOException: Broken pipe 的異常,處理問題后以此文章作為記錄。
業(yè)務系統(tǒng)的前端頁面接收后端傳來的圖片顯示在前端頁面進行業(yè)務操作,但是現(xiàn)在前端頁面的圖片無法正常顯示,業(yè)務系統(tǒng)可正常訪問,其他功能均正常。
原因分析
我們需要從以下幾個角度來分析
1 . 首先排查日志根據(jù)異常的拋出點判斷問題發(fā)生原因

排查問題發(fā)生時間的日志發(fā)現(xiàn)拋出了一個io異常 java.io.IOException: Broken pipe 斷開的管道
在查看最底部的堆棧信息看看是哪里拋出的異常

觀察最底部的堆棧信息發(fā)現(xiàn)異常是在 OutputBuffer 類的 realWriteBytes 方法中拋出的。
2 . 從源碼角度進一步分析
首先我們要清楚realWriteBytes 方法是 Tomcat 中 OutputBuffer 類的一部分,它用于將數(shù)據(jù)從緩沖區(qū)寫入到目標輸出流(通常是網(wǎng)絡套接字)。這個方法是 OutputBuffer 類的核心方法之一,用于處理數(shù)據(jù)寫入的底層細節(jié)。

在源碼中可以看到realWriteBytes 方法調(diào)用 doWrite(buf) 方法,實際的數(shù)據(jù)寫入由 coyoteResponse 的 doWrite 方法完成,該方法將數(shù)據(jù)從緩沖區(qū)寫入到網(wǎng)絡套接字。如果在寫入過程中拋出了 CloseNowException,則關閉輸出流并重新拋出異常,如果發(fā)生 IOException,則會調(diào)用 setErrorException(e) 來記錄錯誤,然后拋出 ClientAbortException。
3 . 網(wǎng)絡套接字 (Socket)和通信管道 (Pipe)定義
在了解Broken Pipe異常的含義前首先讓我們了解一下網(wǎng)絡通信過程中網(wǎng)絡套接字(Socket)和通信管道(Pipe)的使用
網(wǎng)絡套接字 (Socket)
• 定義:網(wǎng)絡套接字是一個通信端點,它用于在網(wǎng)絡上建立和管理連接。一個套接字代表通信的一個端點,它是由IP地址和端口號的組合所標識的。套接字允許在不同的計算機之間發(fā)送和接收數(shù)據(jù)。網(wǎng)絡通信的大多數(shù)協(xié)議(如TCP、UDP)都依賴于套接字來實現(xiàn)數(shù)據(jù)傳輸。
• 工作方式:
• TCP連接:在TCP協(xié)議中,套接字提供了一種面向連接的、可靠的通信方式??蛻舳撕头掌魍ㄟ^套接字建立連接,然后在這個連接上進行數(shù)據(jù)交換。
• UDP連接:在UDP協(xié)議中,套接字提供了無連接的、盡力而為的通信方式。數(shù)據(jù)通過UDP套接字發(fā)送到目標地址,而不保證可靠的傳輸。
通信管道 (Pipe)
• 定義:在計算機科學中,通信管道是一種用于在同一臺計算機上的進程之間進行數(shù)據(jù)傳輸?shù)臋C制。管道提供了一個雙向或單向的數(shù)據(jù)流通道,進程可以通過管道發(fā)送和接收數(shù)據(jù)。
• 工作方式:
• 匿名管道:通常用于在父子進程之間傳輸數(shù)據(jù),數(shù)據(jù)從一端寫入,從另一端讀取。
• 命名管道 (FIFO):可以用于不同進程之間的數(shù)據(jù)傳輸,甚至可以在不同用戶之間共享。
Pipe 在網(wǎng)絡通信中的抽象意義
現(xiàn)在本文中我們討論的網(wǎng)絡通信的上下文的術語“pipe”并不是指進程間通信的物理管道,而是指客戶端和服務器之間的通過網(wǎng)絡套接字建立的數(shù)據(jù)傳輸路徑。在實際的網(wǎng)絡通信中,客戶端與服務器之間通過套接字建立連接,這個連接就是通信的“管道”。數(shù)據(jù)通過這個“管道”進行傳輸。可以將它看作是一條“虛擬通道”,通過這個通道,客戶端和服務器之間可以進行雙向數(shù)據(jù)傳輸。
由于**套接字(Socket)**并不是嚴格意義上發(fā)生在單一層的概念,而是跨越了傳輸層和應用層。套接字編程發(fā)生在應用層。開發(fā)人員使用編程語言的套接字API來創(chuàng)建套接字、連接到遠程主機、發(fā)送和接收數(shù)據(jù)。傳輸層協(xié)議(如TCP或UDP)則在傳輸層實現(xiàn)實際的網(wǎng)絡通信,而套接字在傳輸層的角色是管理端到端的通信連接,并確保數(shù)據(jù)可靠傳輸或快速發(fā)送。
套接字作為一種抽象概念,連接了應用層與傳輸層。它為應用層提供了一個接口,使應用程序能夠利用傳輸層協(xié)議實現(xiàn)網(wǎng)絡通信。
在本文中為了直觀的展示套接字在網(wǎng)絡通信中的使用將應用層和傳輸層抽象為同一層網(wǎng)絡模型,這一層網(wǎng)絡模型通過**網(wǎng)絡套接字 (Socket)建立的通信管道 (Pipe)**進行數(shù)據(jù)傳輸

4 . Broken Pipe 異常的含義
• 定義:Broken Pipe 異常發(fā)生在服務器端嘗試向已經(jīng)關閉的套接字寫入數(shù)據(jù)時。通常情況下,這是由于客戶端在服務器完成數(shù)據(jù)發(fā)送之前已經(jīng)關閉了連接,導致服務器無法繼續(xù)發(fā)送數(shù)據(jù)。
在網(wǎng)絡通信中,套接字是通信的基礎,它為客戶端和服務器之間的數(shù)據(jù)交換提供了通道。當客戶端或服務器中斷連接時,就會出現(xiàn) Broken Pipe 異常。這意味著數(shù)據(jù)流的“管道”已經(jīng)被破壞,數(shù)據(jù)無法傳輸。

這種情況可能發(fā)生在以下幾種場景中:
1. 客戶端意外斷開連接:
• 客戶端可能由于網(wǎng)絡故障、應用程序崩潰或用戶主動關閉應用程序而斷開連接。
2. 長時間未響應:
• 如果客戶端長時間沒有響應,服務器端可能繼續(xù)嘗試向客戶端發(fā)送數(shù)據(jù),這時會導致 Broken pipe 異常。
3. 緩沖區(qū)未及時 flush:
• 如果緩沖區(qū)中的數(shù)據(jù)未及時寫入到套接字,當對端關閉連接時,再次嘗試寫入可能導致異常。
5 . 判斷問題來源
服務端問題的可能性:
• 處理時間過長: 服務器處理請求時可能因為資源不足、鎖定資源、復雜的計算任務等原因?qū)е马憫舆t。如果超過了瀏覽器或服務器的超時時間,就會中斷連接,服務器就可能會報告 Broken pipe 。
• 資源限制: 服務器可能因為負載過高,無法在合理時間內(nèi)響應請求。這在高并發(fā)場景下尤其常見。
• 配置問題: 服務器配置錯誤或不當(例如超時時間過短)也可能導致連接被意外關閉。
客戶端問題的可能性:
• 瀏覽器等待時間短: 如果請求需要很長時間來完成,而瀏覽器的等待時間(例如90秒)不足以等待響應,就可能會出現(xiàn)超時錯誤。但通常瀏覽器的默認超時時間是相對寬松的,除非請求需要極長的處理時間。
• 網(wǎng)絡連接不穩(wěn)定: 客戶端(瀏覽器)的網(wǎng)絡連接不穩(wěn)定,可能導致連接中斷。
• 中間代理或防火墻: 有時中間的代理服務器、防火墻或負載均衡器可能會因各種原因中斷連接。
解決方案
針對上面提到的服務端問題和客戶端問題的可能性分別提出方案進行解決
1.模擬事故代碼
首先我們模擬事故代碼,使測試環(huán)境能夠拋出同樣的異常
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@RestController
public class HaHa {
@GetMapping("/h")
public void sendFile(HttpServletRequest req, HttpServletResponse resp) {
FileInputStream fis = null;
try {
// 設置要發(fā)送的文件路徑(使用一個大文件)
File file = new File("/opt/SoundSource.zip"); // 替換為你的文件路徑
fis = new FileInputStream(file);
// 設置響應的內(nèi)容類型和文件名
resp.setContentType("application/octet-stream");
resp.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
// 獲取響應輸出流
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
resp.getOutputStream().write(buffer, 0, bytesRead);
// 模擬發(fā)送過程中的延遲,便于在中途斷開連接
Thread.sleep(100);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上面的代碼中我們提供了/h 這個路徑進行訪問,訪問這個路徑服務器端就會向客戶端發(fā)送一個大文件,而我們只要在接收時斷開連接即可觸發(fā)同樣的異常報錯。


2 . 服務端問題的解決方案
(1)優(yōu)化處理時間
• 減少單次請求的處理時間:優(yōu)化代碼,減少單次請求的處理時間,使用異步處理或多線程處理,并及時調(diào)用flush()方法,將緩沖區(qū)中的數(shù)據(jù)強制寫入到目標流中。在Spring Boot中,可以使用@Async注解來實現(xiàn)異步處理。
package com.example.demo.controller;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@RestController
public class HaHa {
@Async // 使用異步處理請求
@GetMapping("/h")
public void sendFile(HttpServletRequest req, HttpServletResponse resp) {
FileInputStream fis = null;
try {
// 設置要發(fā)送的文件路徑(使用一個大文件)
File file = new File("/opt/SoundSource.zip"); // 替換為你的文件路徑
fis = new FileInputStream(file);
// 設置響應的內(nèi)容類型和文件名
resp.setContentType("application/octet-stream");
resp.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
// 獲取響應輸出流
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
resp.getOutputStream().write(buffer, 0, bytesRead);
resp.getOutputStream().flush(); // 及時調(diào)用flush() 方法將緩沖區(qū)中的數(shù)據(jù)強制寫入到目標流中
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
(2)增加超時時間
• 增加服務器端超時時間:可以通過配置Tomcat或其他應用服務器,增加連接的超時時間。這樣即使處理時間較長,連接也不會被意外關閉。
• 對于Tomcat,可以通過修改server.xml中的connectionTimeout屬性來增加超時時間。
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000" <!-- 設置超時時間為20秒 -->
redirectPort="8443" />
• 在Spring Boot中,可以通過修改application.properties文件來增加超時時間:
server.tomcat.connection-timeout=20000ms # 設置Tomcat連接超時時間為20秒
通過這些設置,可以有效增加服務器的超時時間,避免由于超時導致的連接中斷。
(3) 資源配置調(diào)整
• 增加服務器資源:在高并發(fā)情況下,適當增加服務器的資源(如CPU、內(nèi)存)或者擴展集群,減少服務器資源不足導致的響應延遲。
• 負載均衡:使用負載均衡器分發(fā)請求,減少單個服務器的壓力。
(4) 重試機制
• 實現(xiàn)重試機制:在捕獲到Broken pipe異常時,可以實現(xiàn)重試機制,以避免用戶請求失敗。例如,可以在捕獲到異常后,重新發(fā)送響應。
package com.example.demo.controller;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@RestController
public class HaHa {
@Async // 異步處理請求
@GetMapping("/h")
public void sendFile(HttpServletRequest req, HttpServletResponse resp) {
FileInputStream fis = null;
int maxRetries = 3; // 設置最大重試次數(shù)
int retryCount = 0;
boolean success = false;
try {
// 設置要發(fā)送的文件路徑(使用一個大文件)
File file = new File("/opt/SoundSource.zip"); // 替換為你的文件路徑
fis = new FileInputStream(file);
// 設置響應的內(nèi)容類型和文件名
resp.setContentType("application/octet-stream");
resp.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
// 獲取響應輸出流
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
resp.getOutputStream().write(buffer, 0, bytesRead);
resp.getOutputStream().flush(); // 及時調(diào)用flush() 方法將緩沖區(qū)中的數(shù)據(jù)強制寫入到目標流中
}
} catch (IOException | InterruptedException e) {
retryCount++;
System.err.println("發(fā)送過程中出現(xiàn)異常,重試次數(shù):" + retryCount);
e.printStackTrace();
if (retryCount == maxRetries) {
System.err.println("最大重試次數(shù)達到,停止重試");
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
(5)監(jiān)控和報警
• 建立監(jiān)控機制:對服務器的處理時間、連接數(shù)量、資源使用情況進行監(jiān)控,及時發(fā)現(xiàn)問題并報警。
3 . 客戶端問題的解決方案
1. 增加瀏覽器等待時間:
• 修改客戶端代碼,可以增加瀏覽器的等待時間(如通過JavaScript的XMLHttpRequest或fetch設置更長的超時時間)。
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 20000); // 20 秒超時
fetch('/yourApi', { signal: controller.signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Fetch error:', error));
PS:為什么這里不直接查看和調(diào)整瀏覽器的超時時間
• Chrome 瀏覽器:默認連接超時時間是 300 秒(5 分鐘)。這個時間只能通過命令行參數(shù)或網(wǎng)絡代理配置來間接調(diào)整,瀏覽器本身不提供內(nèi)置選項來更改這一時間。
• Firefox 瀏覽器:默認超時時間是 90 秒??梢酝ㄟ^訪問 about:config 來查看和調(diào)整一些網(wǎng)絡相關的設置。 在地址欄中輸入 about:config 并回車來查看配置。搜索 network.http.connection-timeout,可以修改該值來調(diào)整連接超時時間。其他相關設置如 network.http.keep-alive.timeout 可以調(diào)整 Keep-Alive 連接的超時時間。
• Edge 和 Safari:這些瀏覽器的超時設置與 Chrome 類似,也不提供直接的用戶可調(diào)選項。
2. 優(yōu)化網(wǎng)絡連接:
• 如果是網(wǎng)絡連接不穩(wěn)定引起的問題,建議使用更穩(wěn)定的網(wǎng)絡環(huán)境,或者盡量使用有線連接,減少無線網(wǎng)絡帶來的不穩(wěn)定性。
3. 避免大文件傳輸:
• 盡量避免在單次請求中傳輸超大文件,如果必須傳輸,可以考慮將文件壓縮后再傳輸,或者采用分片傳輸技術。
4. 中間代理配置:
• 如果網(wǎng)絡中存在代理、防火墻或者負載均衡器,可以在相關服務中配置網(wǎng)絡超時時間,例如在Nginx 中的 client_body_timeout 和 client_header_timeout 設置了客戶端請求正文和頭部的超時時間。
問題總結(jié)
總結(jié):Broken Pipe 異常是網(wǎng)絡通信中常見的一種異常,通常在服務器嘗試向已經(jīng)關閉的客戶端連接發(fā)送數(shù)據(jù)時發(fā)生。在設計和實現(xiàn)系統(tǒng)時,應該考慮到這種異常情況,進行相應的優(yōu)化和配置。并確??蛻舳撕头掌鞫说木W(wǎng)絡穩(wěn)定,避免因網(wǎng)絡波動導致連接中斷。
到此這篇關于Java異常報錯:java.io.IOException: Broken pipe解決方案的文章就介紹到這了,更多相關Java異常報錯java.io.IOException: Broken pipe內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringSecurity自定義AuthenticationProvider無法@Autowire的解決
這篇文章主要介紹了SpringSecurity自定義AuthenticationProvider無法@Autowire的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
java 序列化對象 serializable 讀寫數(shù)據(jù)的實例
java 序列化對象 serializable 讀寫數(shù)據(jù)的實例,需要的朋友可以參考一下2013-03-03

