您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關深入淺析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”,即“對方復位連接”,這種情況一般發生在服務進程較客戶進程提前終止。當服務進程終止時會向客戶 TCP 發送 FIN 分節,客戶 TCP 回應 ACK,服務 TCP 將轉入 FIN_WAIT2 狀態。此時如果客戶進程沒有處理該 FIN (如阻塞在其它調用上而沒有關閉 Socket 時),則客戶 TCP 將處于 CLOSE_WAIT 狀態。當客戶進程再次向 FIN_WAIT2 狀態的服務 TCP 發送數據時,則服務 TCP 將立刻響應 RST。一般來說,這種情況還可以會引發另外的應用程序異常,客戶進程在發送完數據后,往往會等待從網絡IO接收數據,很典型的如 read 或 readline 調用,此時由于執行時序的原因,如果該調用發生在 RST 分節收到前執行的話,那么結果是客戶進程會得到一個非預期的 EOF 錯誤。此時一般會輸出“server terminated prematurely”-“服務器過早終止”錯誤。
EPIPE
錯誤被描述為“broken pipe”,即“管道破裂”,這種情況一般發生在客戶進程不理會(或未及時處理)Socket 錯誤,繼續向服務 TCP 寫入更多數據時,內核將向客戶進程發送 SIGPIPE 信號,該信號默認會使進程終止(此時該前臺進程未進行 core dump)。結合上邊的 ECONNRESET 錯誤可知,向一個 FIN_WAIT2 狀態的服務 TCP(已 ACK 響應 FIN 分節)寫入數據不成問題,但是寫一個已接收了 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"); }
可以看到java 在讀和寫的情況關于EPIPE的情況是處理不一樣的
在read 的情況中,Reset 是全部拋出 ConnectionResetException, 提示的錯誤信息是 Connection Reset
在write的情況下,Reset 對ECONNRESET的是拋出ConnectionResetException, 而對EPIPE 拋出的是SocketException ,錯誤信息是Broken pipe
如何打印出信息Broken pipe
SIGPIPE信號處理函數
當在收到reset包后,如果在讀寫socket,會出現錯誤EPIPE,同時經常收到SIGPIPE信號
在程序中可以看到java 并沒有對write的情況下沒有處理錯誤EPIPE,開始的時候錯誤的以拋出的異常是信號處理函數拋出的
先來看一下關于信號SIGPIPE的處理函數,在Linux::install_signal_handlers 里面調用函數
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);
而函數set_signal_handler,中對對應的信號處理函數是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; }
最終還是調用了函數 JVM_handle_linux_signal
在X86架構下, 函數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處理,也就是使用了系統的原來信號處理函數,也就證明了異常并不是信號處理函數拋出的
NET_ThrowByNameWithLastError函數
既然不是信號處理函數拋出的異常,繼續查看原來的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.
關鍵點就在函數 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); }
函數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的異常信息,也就是開始對應的Write failed!
JVM_GetLastErrorString 使用hpi::lasterror ,也就是函數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 對應這個error number 的錯誤內容
結論:Broken pipe 是內核對應的錯誤信息,并不是java自己提供的信息
看完上述內容,你們對深入淺析java中connection reset 的異常處理有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。