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自己提供的信息
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持毛票票!