当前位置: 移动技术网 > IT编程>开发语言>Java > java中的connection reset 异常处理分析

java中的connection reset 异常处理分析

2019年07月22日  | 移动技术网IT编程  | 我要评论

在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自己提供的信息

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持移动技术网!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网