java中的connection reset 异常处理分析

发布时间 - 2026-01-11 00:29:44    点击率:

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

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


# connection  # reset  # 详解Java异常处理中throw与throws关键字的用法区别  # Java程序常见异常及处理汇总  # Java中异常处理之try和catch代码块的使用  # 深入探讨JAVA中的异常与错误处理  # java异常处理机制示例(java抛出异常、捕获、断言)  # Java异常分类及统一处理详解  # java异常与错误处理基本知识  # Java一些常见的出错异常处理方法总结  # Java异常(Exception)处理以及常见异常总结  # 抛出  # 信号处理  # 错误信息  # 可以看到  # 这种情况  # 的是  # 情况下  # 发生在  # 几个  # 使用了  # 会有  # 就在  # 还可以  # 却是  # 也就  # 上了  # 则是  # 我们可以  # 会使  # 看一下 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: java获取注册ip实例  Laravel如何使用Contracts(契约)进行编程_Laravel契约接口与依赖反转  网站制作公司哪里好做,成都网站制作公司哪家做得比较好,更正规?  Laravel怎么集成Vue.js_Laravel Mix配置Vue开发环境  标题:Vue + Vuex + JWT 身份认证的正确实践与常见误区解析  Laravel项目怎么部署到Linux_Laravel Nginx配置详解  如何在香港服务器上快速搭建免备案网站?  Laravel如何创建自定义Facades?(详细步骤)  如何批量查询域名的建站时间记录?  如何挑选优质建站一级代理提升网站排名?  微信小程序 HTTPS报错整理常见问题及解决方案  Laravel如何使用Collections进行数据处理?(实用方法示例)  Laravel Octane如何提升性能_使用Laravel Octane加速你的应用  Win11怎么关闭透明效果_Windows11辅助功能视觉效果设置  java中使用zxing批量生成二维码立牌  php增删改查怎么学_零基础入门php数据库操作必知基础【教程】  php8.4header发送头信息失败怎么办_php8.4header函数问题解决【解答】  Laravel怎么多语言本地化设置_Laravel语言包翻译与Locale动态切换【手册】  Laravel如何使用Service Provider注册服务_Laravel服务提供者配置与加载  高防服务器:AI智能防御DDoS攻击与数据安全保障  Laravel怎么实现前端Toast弹窗提示_Laravel Session闪存数据Flash传递给前端【方法】  如何用手机制作网站和网页,手机移动端的网站能制作成中英双语的吗?  SQL查询语句优化的实用方法总结  如何用好域名打造高点击率的自主建站?  Midjourney怎么调整光影效果_Midjourney光影调整方法【指南】  网页制作模板网站推荐,网页设计海报之类的素材哪里好?  Laravel如何实现RSS订阅源功能_Laravel动态生成网站XML格式订阅内容【教程】  Laravel如何升级到最新版本?(升级指南和步骤)  Laravel如何实现全文搜索_Laravel Scout集成Algolia或Meilisearch教程  如何在HTML表单中获取用户输入并结合JavaScript动态控制复利计算循环  如何获取免费开源的自助建站系统源码?  浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】  香港服务器选型指南:免备案配置与高效建站方案解析  Laravel如何实现密码重置功能_Laravel密码找回与重置流程  在Oracle关闭情况下如何修改spfile的参数  Laravel怎么实现支付功能_Laravel集成支付宝微信支付  网易LOFTER官网链接 老福特网页版登录地址  国美网站制作流程,国美电器蒸汽鍋怎么用官方网站?  Laravel如何实现多语言支持_Laravel本地化与国际化(i18n)配置教程  Win11怎么关闭资讯和兴趣_Windows11任务栏设置隐藏小组件  Laravel如何实现邮箱地址验证功能_Laravel邮件验证流程与配置  Laravel怎么做数据加密_Laravel内置Crypt门面的加密与解密功能  Python自动化办公教程_ExcelWordPDF批量处理案例  简历没回改:利用AI润色让你的文字更专业  如何在宝塔面板创建新站点?  如何在宝塔面板中修改默认建站目录?  如何使用 Go 正则表达式精准提取括号内首个纯字母标识符(忽略数字与嵌套)  Claude怎样写结构化提示词_Claude结构化提示词写法【教程】  如何在搬瓦工VPS快速搭建网站?  Laravel怎么创建自己的包(Package)_Laravel扩展包开发入门到发布