java中的connection reset 異常處理分析
在Java中??匆姷膸讉€connection rest exception, Broken pipe, Connection reset,Connection reset by peer
Socked reset case
Linux中會有2個常見的sock reset 情況下的錯誤代碼
ECONNRESET
該錯誤被描述為“connection reset by peer”,即“對方復(fù)位連接”,這種情況一般發(fā)生在服務(wù)進(jìn)程較客戶進(jìn)程提前終止。當(dāng)服務(wù)進(jìn)程終止時會向客戶 TCP 發(fā)送 FIN 分節(jié),客戶 TCP 回應(yīng) ACK,服務(wù) TCP 將轉(zhuǎn)入 FIN_WAIT2 狀態(tài)。此時如果客戶進(jìn)程沒有處理該 FIN (如阻塞在其它調(diào)用上而沒有關(guān)閉 Socket 時),則客戶 TCP 將處于 CLOSE_WAIT 狀態(tài)。當(dāng)客戶進(jìn)程再次向 FIN_WAIT2 狀態(tài)的服務(wù) TCP 發(fā)送數(shù)據(jù)時,則服務(wù) TCP 將立刻響應(yīng) RST。一般來說,這種情況還可以會引發(fā)另外的應(yīng)用程序異常,客戶進(jìn)程在發(fā)送完數(shù)據(jù)后,往往會等待從網(wǎng)絡(luò)IO接收數(shù)據(jù),很典型的如 read 或 readline 調(diào)用,此時由于執(zhí)行時序的原因,如果該調(diào)用發(fā)生在 RST 分節(jié)收到前執(zhí)行的話,那么結(jié)果是客戶進(jìn)程會得到一個非預(yù)期的 EOF 錯誤。此時一般會輸出“server terminated prematurely”-“服務(wù)器過早終止”錯誤。
EPIPE
錯誤被描述為“broken pipe”,即“管道破裂”,這種情況一般發(fā)生在客戶進(jìn)程不理會(或未及時處理)Socket 錯誤,繼續(xù)向服務(wù) TCP 寫入更多數(shù)據(jù)時,內(nèi)核將向客戶進(jìn)程發(fā)送 SIGPIPE 信號,該信號默認(rèn)會使進(jìn)程終止(此時該前臺進(jìn)程未進(jìn)行 core dump)。結(jié)合上邊的 ECONNRESET 錯誤可知,向一個 FIN_WAIT2 狀態(tài)的服務(wù) TCP(已 ACK 響應(yīng) FIN 分節(jié))寫入數(shù)據(jù)不成問題,但是寫一個已接收了 RST 的 Socket 則是一個錯誤。
Java 中的socket input stream/output stream 的處理
先看代碼片段
SocketInputStream.c
switch (errno) {
case ECONNRESET:
case EPIPE:
JNU_ThrowByName(env, "sun/net/ConnectionResetException",
"Connection reset");
break;
....
SocketOutputStream.c
if (errno == ECONNRESET) {
JNU_ThrowByName(env, "sun/net/ConnectionResetException",
"Connection reset");
} else {
NET_ThrowByNameWithLastError(env, "java/net/SocketException",
"Write failed");
}
可以看到j(luò)ava 在讀和寫的情況關(guān)于EPIPE的情況是處理不一樣的
在read 的情況中,Reset 是全部拋出 ConnectionResetException, 提示的錯誤信息是 Connection Reset
在write的情況下,Reset 對ECONNRESET的是拋出ConnectionResetException, 而對EPIPE 拋出的是SocketException ,錯誤信息是Broken pipe
如何打印出信息Broken pipe
SIGPIPE信號處理函數(shù)
當(dāng)在收到reset包后,如果在讀寫socket,會出現(xiàn)錯誤EPIPE,同時經(jīng)常收到SIGPIPE信號
在程序中可以看到j(luò)ava 并沒有對write的情況下沒有處理錯誤EPIPE,開始的時候錯誤的以拋出的異常是信號處理函數(shù)拋出的
先來看一下關(guān)于信號SIGPIPE的處理函數(shù),在Linux::install_signal_handlers 里面調(diào)用函數(shù)
set_signal_handler(SIGSEGV, true); set_signal_handler(SIGPIPE, true); set_signal_handler(SIGBUS, true); set_signal_handler(SIGILL, true); set_signal_handler(SIGFPE, true); set_signal_handler(SIGXFSZ, true);
而函數(shù)set_signal_handler,中對對應(yīng)的信號處理函數(shù)是signalHandler
sigAct.sa_handler = SIG_DFL;
if (!set_installed) {
sigAct.sa_flags = SA_SIGINFO|SA_RESTART;
} else {
sigAct.sa_sigaction = signalHandler;
sigAct.sa_flags = SA_SIGINFO|SA_RESTART;
}
最終還是調(diào)用了函數(shù) JVM_handle_linux_signal
在X86架構(gòu)下, 函數(shù)JVM_handle_linux_signal
extern "C" int
JVM_handle_linux_signal(int sig,
siginfo_t* info,
void* ucVoid,
int abort_if_unrecognized) {
ucontext_t* uc = (ucontext_t*) ucVoid;
Thread* t = ThreadLocalStorage::get_thread_slow();
SignalHandlerMark shm(t);
// Note: it's not uncommon that JNI code uses signal/sigset to install
// then restore certain signal handler (e.g. to temporarily block SIGPIPE,
// or have a SIGILL handler when detecting CPU type). When that happens,
// JVM_handle_linux_signal() might be invoked with junk info/ucVoid. To
// avoid unnecessary crash when libjsig is not preloaded, try handle signals
// that do not require siginfo/ucontext first.
if (sig == SIGPIPE || sig == SIGXFSZ) {
// allow chained handler to go first
if (os::Linux::chained_handler(sig, info, ucVoid)) {
return true;
} else {
if (PrintMiscellaneous && (WizardMode || Verbose)) {
char buf[64];
warning("Ignoring %s - see bugs 4229104 or 646499219",
os::exception_name(sig, buf, sizeof(buf)));
}
return true;
}
}
...
}
對信號SIGPIPE 使用了chained handler處理,也就是使用了系統(tǒng)的原來信號處理函數(shù),也就證明了異常并不是信號處理函數(shù)拋出的
NET_ThrowByNameWithLastError函數(shù)
既然不是信號處理函數(shù)拋出的異常,繼續(xù)查看原來的outputstream的程序
if (errno == ECONNRESET) {
JNU_ThrowByName(env, "sun/net/ConnectionResetException",
"Connection reset");
} else {
NET_ThrowByNameWithLastError(env, "java/net/SocketException",
"Write failed");
}
也就是else 的情況,那么針對EPIPE的錯誤,java拋出的socketexception, 錯誤信息是Write failed ,事實上我們可以看到的卻是SockedException,異常對對上了, 但信息顯示是Broken pipe,而不是Write failed.
關(guān)鍵點就在函數(shù) NET_ThrowByNameWithLastError
void
NET_ThrowByNameWithLastError(JNIEnv *env, const char *name,
const char *defaultDetail) {
char errmsg[255];
sprintf(errmsg, "errno: %d, error: %s\n", errno, defaultDetail);
JNU_ThrowByNameWithLastError(env, name, errmsg);
}
函數(shù)JNU_ThrowByNameWithLastError
JNIEXPORT void JNICALL
JNU_ThrowByNameWithLastError(JNIEnv *env, const char *name,
const char *defaultDetail)
{
char buf[256];
int n = JVM_GetLastErrorString(buf, sizeof(buf));
if (n > 0) {
jstring s = JNU_NewStringPlatform(env, buf);
if (s != NULL) {
jobject x = JNU_NewObjectByName(env, name,
"(Ljava/lang/String;)V", s);
if (x != NULL) {
(*env)->Throw(env, x);
}
}
}
if (!(*env)->ExceptionOccurred(env)) {
JNU_ThrowByName(env, name, defaultDetail);
}
}
程序可以看到先顯示 JVM_GetLastErrorString 的信息,如果信息是空的情況下才顯示defaultDetail的異常信息,也就是開始對應(yīng)的Write failed!
JVM_GetLastErrorString 使用hpi::lasterror ,也就是函數(shù)sysGetLastErrorString 在linux和solaris 是一樣的
int
sysGetLastErrorString(char *buf, int len)
{
if (errno == 0) {
return 0;
} else {
const char *s = strerror(errno);
int n = strlen(s);
if (n >= len) n = len - 1;
strncpy(buf, s, n);
buf[n] = '\0';
return n;
}
}
原來是strerror(errno) ,也就是直接顯示linux kernel 對應(yīng)這個error number 的錯誤內(nèi)容
結(jié)論:Broken pipe 是內(nèi)核對應(yīng)的錯誤信息,并不是java自己提供的信息
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
相關(guān)文章
Java實現(xiàn)上傳網(wǎng)絡(luò)圖片到七牛云存儲詳解
這篇文章主要為大家詳細(xì)介紹了Java如何實現(xiàn)上傳網(wǎng)絡(luò)圖片到七牛云存儲,文中的示例代碼講解詳細(xì),具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-12-12
Spring Boot連接超時導(dǎo)致502錯誤的實戰(zhàn)案例
這篇文章主要給大家介紹了關(guān)于Spring Boot連接超時導(dǎo)致502錯誤的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
Java日常練習(xí)題,每天進(jìn)步一點點(42)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你2021-07-07
Spring的@Transactional注解使用詳細(xì)解析
這篇文章主要介紹了Spring的@Transactional注解使用詳細(xì)解析,@Transactional 注解相信大家并不陌生,平時開發(fā)中很常用的一個注解,它能保證方法內(nèi)多個數(shù)據(jù)庫操作要么同時成功、要么同時失敗,需要的朋友可以參考下2023-11-11
詳述IntelliJ IDEA遠(yuǎn)程調(diào)試Tomcat的方法(圖文)
本篇文章主要介紹了詳述IntelliJ IDEA遠(yuǎn)程調(diào)試Tomcat的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12
Struts2之Action接收請求參數(shù)和攔截器詳解
這篇文章主要介紹了Struts2之Action接收請求參數(shù)和攔截器詳解,非常具有實用價值,需要的朋友可以參考下2017-05-05

