libnet发包java语言_java.net.SocketException:Connection reset 原因分析与故障重现

本文详细分析了在Java应用中遇到的`java.net.SocketException: Connection reset`异常,介绍了该异常在源码层面的原因,并通过三种场景(到不存在的端口的连接请求、异常终止一个连接、检测半开连接)进行故障重现。解决方案包括检查端口错误、使用降级重试策略、设置Http连接池等。
摘要由CSDN通过智能技术生成

前两天同事问了一个 java.net.SocketException: Connection reset 的问题,场景是在一个 Java 应用中使用 httpClient 请求远程一个 RestfulApi 接口,但返回报错 Connection reset。由于没有到现场查看日志,只能通过查看源码和资料进行故障重现,本文记录了问题的分析过程。

异常信息

[2019-10-24 14:55:34.390][HttpClientUtils.java:797]-doPost 发送 POST 请求出现异常:请求参数:{aaa:bbb,ccc:ddd},url:http://xxx.xxx.com/xxx/xxx, 时间秒:0.019 秒,Connection reset java.net.SocketException: Connection reset

在源码中定位异常信息

查看 jdk10 源码,发现 Connection reset 信息出现于方法 java.net.SocketInputStream#read(byte[], int, int, int) 。

此方法通过间接调用 socketRead-->socketRead0-->NET_Read-->read 读取内核态缓存区数据。

如果 NET_Read 函数(unix 系统)检测 Connection reset 或 Broken pipe,将抛出 sun/net/ConnectionResetException,相应地在 jvm 中会抛出异常 SocketException("Connection reset")。

详情请见下方两段源码中文注释部分。

/**

* Reads into a byte array b at offset off,

* length bytes of data.

* @param b the buffer into which the data is read

* @param off the start offset of the data

* @param length the maximum number of bytes read

* @return the actual number of bytes read, -1 is

* returned when the end of the stream is reached.

* @exception IOException If an I/O error has occurred.

*/

public int read(byte b[], int off, int length) throws IOException {

return read(b, off, length, impl.getTimeout());

}

int read(byte b[], int off, int length, int timeout) throws IOException {

int n;

// EOF already encountered

if (eof) {

return -1;

}

// connection reset

if (impl.isConnectionReset()) {

throw new SocketException("Connection reset");

}

// bounds check

if (length <= 0 || off < 0 || length > b.length - off) {

if (length == 0) {

return 0;

}

throw new ArrayIndexOutOfBoundsException("length == " + length

+ " off == " + off + " buffer length == " + b.length);

}

boolean gotReset = false;

// acquire file descriptor and do the read

FileDescriptor fd = impl.acquireFD();

try {

// 间接调用socketRead0函数,从内核态缓存区读取数据

n = socketRead(fd, b, off, length, timeout);

if (n > 0) {

return n;

}

} catch (ConnectionResetException rstExc) {

// 如果socketRead0抛出ConnectionResetException,此处进行捕获。后续再重试一次读取操作。

gotReset = true;

} finally {

impl.releaseFD();

}

/*

* We receive a "connection reset" but there may be bytes still

* buffered on the socket

* 重试读取操作

*/

if (gotReset) {

impl.setConnectionResetPending();

impl.acquireFD();

try {

n = socketRead(fd, b, off, length, timeout);

if (n > 0) {

return n;

}

} catch (ConnectionResetException rstExc) {

} finally {

impl.releaseFD();

}

}

/*

* If we get here we are at EOF, the socket has been closed,

* or the connection has been reset.

*/

if (impl.isClosedOrPending()) {

throw new SocketException("Socket closed");

}

if (impl.isConnectionResetPending()) {

impl.setConnectionReset();

}

if (impl.isConnectionReset()) {

// 最终抛出异常

throw new SocketException("Connection reset");

}

eof = true;

return -1;

}

/*

* Class: java_net_SocketInputStream

* Method: socketRead0

* Signature: (Ljava/io/FileDescriptor;[BIII)I

*/

JNIEXPORT jint JNICALL

Java_java_net_SocketInputStream_socketRead0(JNIEnv *env, jobject this,

jobject fdObj, jbyteArray data,

jint off, jint len, jint timeout)

{

char BUF[MAX_BUFFER_LEN];

char *bufP;

jint fd, nread;

if (IS_NULL(fdObj)) {

JNU_ThrowByName(env, "java/net/SocketException",

"Socket closed");

return -1;

}

fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);

if (fd == -1) {

JNU_ThrowByName(env, "java/net/SocketException", "Socket closed");

return -1;

}

/*

* If the read is greater than our stack allocated buffer then

* we allocate from the heap (up to a limit)

*/

if (len > MAX_BUFFER_LEN) {

if (len > MAX_HEAP_BUFFER_LEN) {

len = MAX_HEAP_BUFFER_LEN;

}

bufP = (char *)malloc((size_t)len);

if (bufP == NULL) {

bufP = BUF;

len = MAX_BUFFER_LEN;

}

} else {

bufP = BUF;

}

if (timeout) {

nread = NET_ReadWithTimeout(env, fd, bufP, len, timeout);

if ((*env)->ExceptionCheck(env)) {

if (bufP != BUF) {

free(bufP);

}

return nread;

}

} else {

nread = NET_Read(fd, bufP, len);

}

if (nread <= 0) {

if (nread < 0) {

switch (errno) {

case ECONNRESET:

case EPIPE:

/* 抛出异常的情况对应为Connection reset和Broken pipe

* ECONNRESET Connection reset (POSIX.1-2001).

* EPIPE Broken pipe (POSIX.1-2001).

* @see http://man7.org/linux/man-pages/man3/errno.3.html

*/

JNU_ThrowByName(env, "sun/net/ConnectionResetException",

"Connection reset");

break;

case EBADF:

JNU_ThrowByName(env, "java/net/SocketException",

"Socket closed");

break;

case EINTR:

JNU_ThrowByName(env, "java/io/InterruptedIOException",

"Operation interrupted");

break;

default:

JNU_ThrowByNameWithMessageAndLastError

(env, "java/net/SocketException", "Read failed");

}

}

} else {

(*env)->SetByteArrayRegion(env, data, off, nread, (jbyte *)bufP);

}

if (bufP != BUF) {

free(bufP);

}

return nread;

}

java.net.SoecketException:Connection reset 出现原因

根据 IBM 开发者社区博客 Understanding some common SocketExceptions in JAVA 描述,在 Java 中出现 Connnection reset 的原因为本地 socket 在接收或发送数据时收到远端系统带 RST 位的数据包。

This exception appears when the remote connection is unexpectedly and forcefully closed due to various reasons like application crash, system reboot, hard close of remote host. Kernel from the remote system sends out a packets with RST bit to the local system. The local socket on performing any SEND (could be a Keep-alive packet) or RECEIVE operations subsequently fail with this error. Certain combinations of linger settings can also result in packets with RST bit set.

RST 报文段出现的场景

根据《TCP/IP 详解。卷一:协议》,复位报文段(RST)可能在以下三种情况下出现:

到不存在的端口的连接请求

当连接请求到达时,目的端口没有进程正在监听。即 TCP 连接建立时,客户端发送 SYN 报文段,服务端的 TCP 端软件回复 RST 报文段,此过程对应 Socket API 的 connect 函数,抛出异常为:Connection refused

出现阶段:建立连接时

异常终止一个连接

正常终止一个连接的方式是一方发送 FIN。因为在所有排队数据都已经发送之后才发送 FIN,正常情况下没有任何数据丢失。

存在一种异常终止连接的方式:一方发送一个复位报文段(RST)而不是 FIN 来中途释放一个连接。其行为表现为:一方丢弃任何待发数据并立即发送复位报文段,收到 RST 的一方不会再回 ACK 报文段,而是终止该连接,并通知应用层连接复位,抛出异常 Connection reset by peer。

Socket API 通过“linger on close”选项(SO_LINGER)提供了异常终止连接的能力。

出现阶段:终止连接时

检测半开连接

半开连接:如果一方已经关闭或异常终止连接而另一方却不知道,这样的 TCP 连接称为半开连接。即异常的一方没有发出 FIN 报文段来通知对方开始终止连接。

现象: 只要不打算在半开连接上传输数据,仍处于连接状态的一方就不会检测

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值