关闭

java 中的connection reset 异常处理分析

28347人阅读 评论(2) 收藏 举报
分类:

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







1
2
查看评论

java socket报 connection reset的原因和解决方式

Java服务器使用Socket时,如果报错: Connection reset; 我遇到的一个原因是服务器端主动断开连接了,没有循环等待accept,当然常规做法是在While(true)里开启线程,为了简化看到问题,就没写线程;  即服务器端的accept必须用while (true...
  • changliangdoscript
  • changliangdoscript
  • 2015-11-26 17:54
  • 6819

关于JAVA httpclient connection reset的问题分析和解决

近日,在和第三方进行接口联调的时候,第三方抱怨我方提供的http服务接口经常发生connection reset的情况,导致第三方不能正常的使用我方服务接口,而我方在对该接口进行各种压力测试后,均没有重现该问题,双方开发人员都不承认是自身的问题,问题难以解决。    ...
  • ericjohn8086
  • ericjohn8086
  • 2015-03-04 11:15
  • 5501

URLConnection 的陷阱 java_关于connection reset 服务器cgi

URLConnection 的陷阱 java_关于connection reset 服务器cgi   java.net.ProtocolException: Can't reset method: already connected at java.net.HttpURL...
  • shukebai
  • shukebai
  • 2015-04-10 14:52
  • 1922

java.net.SocketException: Connection reset问题解决总结

解决办法:   1、网络编程时未正确捕获java.net.SocketException, 客户端先关闭就会报这个异常;(检查发送数据是否符合服务器接收格式,不然的话服务器会自动断开连接,这样,socket就会被关闭,那么你就不能使用IO流读取数据,这样就会报这个错误) &#...
  • u013766398
  • u013766398
  • 2016-05-12 10:15
  • 5873

http client遭遇Connection reset问题,以及解决方法

客户端使用200个线程往服务器端发送数据,每次数据量约100K. 发送的时候使用了多线程连接池管理器 private MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpCon...
  • lcx46
  • lcx46
  • 2014-09-01 19:32
  • 42964

FTP上传文件 SocketException: Connection reset

FTP上传文件报的异常,我基本代码如下: FTPClient client = new FTPClient(); InputStream in = new ByteArrayInputStream("IT WORKS! :D".getBytes()); try { c...
  • fly_air
  • fly_air
  • 2016-08-11 12:51
  • 2429

java ftpclient问题

<br />java ftpclient问题(sun.net.ftp.FtpClient) <br />1 sun.net.ftp.FtpProtocolException: PORT :501 PORT not allowed after EPSV ALL, active ...
  • shangzhiliang_2008
  • shangzhiliang_2008
  • 2010-09-27 16:57
  • 6896

关于 java.net.SocketException: Connection reset错误

今天在客户这边调试程序是:发现老是报下面的2种错误: java.net.SocketException: Connection reset java.net.SocketException: Software caused connection abort: recv failed 运行环...
  • haolyj98
  • haolyj98
  • 2016-11-02 18:53
  • 1678

ftp ftpclient异常解决、远程命令已经错误代码

ftp ftpclient异常解决、远程命令已经错误代码 目录(?)[+] 1.FTP异常以及解决办法 1 sun.net.ftp.FtpProtocolException: PORT :501 PORT not allowed after EPSV ALL, acti...
  • yucaifu1989
  • yucaifu1989
  • 2016-05-23 16:52
  • 2111

Connection reset原因分析和解决方案

Connection reset原因分析和解决方案
  • cwclw
  • cwclw
  • 2016-10-12 14:41
  • 28884
    个人资料
    • 访问:500635次
    • 积分:5449
    • 等级:
    • 排名:第5869名
    • 原创:100篇
    • 转载:3篇
    • 译文:0篇
    • 评论:73条
    最新评论