[转]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
[cpp] 
switch (errno) {  
case ECONNRESET:  
case EPIPE:  
    JNU_ThrowByName(env, "sun/net/ConnectionResetException",      
    "Connection reset");  
    break;  
                  ....  
 
SocketOutputStream.c
[cpp] 
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 里面调用函数
[cpp]  
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
[cpp]  
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
[cpp]  
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的程序
[cpp]  
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
[cpp] 
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
[cpp]  
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 是一样的
[cpp] 
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自己提供的信息
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值