java中的connection reset 异常处理分析
在Java中常看见的几个connectionrestexception,Brokenpipe,Connectionreset,Connectionresetbypeer
Sockedresetcase
Linux中会有2个常见的sockreset情况下的错误代码
ECONNRESET
该错误被描述为“connectionresetbypeer”,即“对方复位连接”,这种情况一般发生在服务进程较客户进程提前终止。当服务进程终止时会向客户TCP发送FIN分节,客户TCP回应ACK,服务TCP将转入FIN_WAIT2状态。此时如果客户进程没有处理该FIN(如阻塞在其它调用上而没有关闭Socket时),则客户TCP将处于CLOSE_WAIT状态。当客户进程再次向FIN_WAIT2状态的服务TCP发送数据时,则服务TCP将立刻响应RST。一般来说,这种情况还可以会引发另外的应用程序异常,客户进程在发送完数据后,往往会等待从网络IO接收数据,很典型的如read或readline调用,此时由于执行时序的原因,如果该调用发生在RST分节收到前执行的话,那么结果是客户进程会得到一个非预期的EOF错误。此时一般会输出“serverterminatedprematurely”-“服务器过早终止”错误。
EPIPE
错误被描述为“brokenpipe”,即“管道破裂”,这种情况一般发生在客户进程不理会(或未及时处理)Socket错误,继续向服务TCP写入更多数据时,内核将向客户进程发送SIGPIPE信号,该信号默认会使进程终止(此时该前台进程未进行coredump)。结合上边的ECONNRESET错误可知,向一个FIN_WAIT2状态的服务TCP(已ACK响应FIN分节)写入数据不成问题,但是写一个已接收了RST的Socket则是一个错误。
Java中的socketinputstream/outputstream的处理
先看代码片段
SocketInputStream.c
switch(errno){ caseECONNRESET: caseEPIPE: JNU_ThrowByName(env,"sun/net/ConnectionResetException", "Connectionreset"); break; ....
SocketOutputStream.c
if(errno==ECONNRESET){ JNU_ThrowByName(env,"sun/net/ConnectionResetException", "Connectionreset"); }else{ NET_ThrowByNameWithLastError(env,"java/net/SocketException", "Writefailed"); }
可以看到java在读和写的情况关于EPIPE的情况是处理不一样的
在read的情况中,Reset是全部抛出ConnectionResetException,提示的错误信息是ConnectionReset
在write的情况下,Reset对ECONNRESET的是抛出ConnectionResetException,而对EPIPE抛出的是SocketException,错误信息是Brokenpipe
如何打印出信息Brokenpipe
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(intsig, siginfo_t*info, void*ucVoid, intabort_if_unrecognized){ ucontext_t*uc=(ucontext_t*)ucVoid; Thread*t=ThreadLocalStorage::get_thread_slow(); SignalHandlerMarkshm(t); //Note:it'snotuncommonthatJNIcodeusessignal/sigsettoinstall //thenrestorecertainsignalhandler(e.g.totemporarilyblockSIGPIPE, //orhaveaSIGILLhandlerwhendetectingCPUtype).Whenthathappens, //JVM_handle_linux_signal()mightbeinvokedwithjunkinfo/ucVoid.To //avoidunnecessarycrashwhenlibjsigisnotpreloaded,tryhandlesignals //thatdonotrequiresiginfo/ucontextfirst. if(sig==SIGPIPE||sig==SIGXFSZ){ //allowchainedhandlertogofirst if(os::Linux::chained_handler(sig,info,ucVoid)){ returntrue; }else{ if(PrintMiscellaneous&&(WizardMode||Verbose)){ charbuf[64]; warning("Ignoring%s-seebugs4229104or646499219", os::exception_name(sig,buf,sizeof(buf))); } returntrue; } } ... }
对信号SIGPIPE使用了chainedhandler处理,也就是使用了系统的原来信号处理函数,也就证明了异常并不是信号处理函数抛出的
NET_ThrowByNameWithLastError函数
既然不是信号处理函数抛出的异常,继续查看原来的outputstream的程序
if(errno==ECONNRESET){ JNU_ThrowByName(env,"sun/net/ConnectionResetException", "Connectionreset"); }else{ NET_ThrowByNameWithLastError(env,"java/net/SocketException", "Writefailed"); }
也就是else的情况,那么针对EPIPE的错误,java抛出的socketexception,错误信息是Writefailed,事实上我们可以看到的却是SockedException,异常对对上了,但信息显示是Brokenpipe,而不是Writefailed.
关键点就在函数NET_ThrowByNameWithLastError
void NET_ThrowByNameWithLastError(JNIEnv*env,constchar*name, constchar*defaultDetail){ charerrmsg[255]; sprintf(errmsg,"errno:%d,error:%s\n",errno,defaultDetail); JNU_ThrowByNameWithLastError(env,name,errmsg); }
函数JNU_ThrowByNameWithLastError
JNIEXPORTvoidJNICALL JNU_ThrowByNameWithLastError(JNIEnv*env,constchar*name, constchar*defaultDetail) { charbuf[256]; intn=JVM_GetLastErrorString(buf,sizeof(buf)); if(n>0){ jstrings=JNU_NewStringPlatform(env,buf); if(s!=NULL){ jobjectx=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的异常信息,也就是开始对应的Writefailed!
JVM_GetLastErrorString使用hpi::lasterror,也就是函数sysGetLastErrorString在linux和solaris是一样的
int sysGetLastErrorString(char*buf,intlen) { if(errno==0){ return0; }else{ constchar*s=strerror(errno); intn=strlen(s); if(n>=len)n=len-1; strncpy(buf,s,n); buf[n]='\0'; returnn; } }
原来是strerror(errno),也就是直接显示linuxkernel对应这个errornumber的错误内容
结论:Brokenpipe是内核对应的错误信息,并不是java自己提供的信息
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持毛票票!