您的位置首页  科技解读

connection(connection reset翻译中文)

1、前置知识1.1、TCP首部格式从这张图可以看出IP报文由首部+数据组成,而IP数据又是TCP首部+数据的组成,首部就是对数据的描述,可以称之

connection(connection reset翻译中文)

 

1、前置知识1.1、TCP首部格式

从这张图可以看出IP报文由首部+数据组成,而IP数据又是TCP首部+数据的组成,首部就是对数据的描述,可以称之为元数据实际上发送方是一层层封包,也即在每一层加上自己的元数据而接收方是一层层解包,就是在每一层根据元数据将其拆分,在运输层将其合并,在等待缓冲区填满时一并交给应用层。

源端口和目的端口:端口指定唯一的进程,双方通讯时可以通过端口找到进程序号(seq):占4个字节,范围是[0,2^32-1]TCP面向字节流传输的,seq序号就是对字节的编号确认号(ack):占4个字节期待收到对方下一个报文段的第一个数据字节的序号。

比如接收到的报文中的seq为500,那么发送确认报文时ack为501,表示可以从501开始传给我数据偏移:占4位TCP首部占用字节数,也即报文中的数据离报文首字节偏移的长度下面有6个控制位,说明报文段的性质。

URG:当URG=1时,表明紧急指针字段有效告诉系统此报文段有紧急数据,应该尽快传输,具有高优先级,不是按照队列顺序传输比如给远程的主机发送很长的数据,等待很长的时间,这时可以通过Control+C中断传输,而这个命令就是紧急数据,排在要传输的数据的前面。

ACK:只有大写的ACK=1时,小写的ack才生效也即ACK=0时,ack无效当然TCP规定,在连接建立后必须将ACK置为1PSH:发送方将PSH置为1,立即创建报文段将其发送出去接收方收到PSH=1的报文段时,会尽快将接收缓冲区的数据推给应用程序,不需要等到缓冲区填满再交给上层。

RST:当RST=1时,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立连接RST置为1还用来拒绝一个非法的报文段或拒绝打开一个连接SYN:在连接建立的过程中使用,表示想与对方建立连接当SYN=1,ACK=0时,表明是一个请求连接的报文。

当SYN=1,ACK=1时,表明这是响应报文段,同意与之建立连接FIN:用来释放一个连接当FIN=1时,表明数据已经发送完毕,要求关闭连接窗口:占用2字节这是接收方的窗口,是接收方当前可以接收的字节数,而发送方根据此窗口控制发送数据的速度。

这个窗口是动态变化的,最终也是接收方缓存的剩余空间决定的以上是基础知识,从本文的知识点来说,RST是重中之重。1.2、三次握手

在客户端请求与服务端连接时,要经过三次握手服务器肯定要先处于监听状态,不然无法监听客户端的连接客户端发送请求报文:SYN=1,seq=x表明客户端想要与服务端建立连接服务端发送确认报文:SYN=1,ACK=1,seq=y,ack=x+1。

表明服务端同意与客户端建立连接注意:只有ACK=1时,ack才生效客户端也发送确认报文:ACK=1,seq=x+1,ack=y+1表明客户端收到服务端的确认报文双方建立连接,可以进行数据传输问题1:seq是否不必要?重要的是SYN和ACK的标识符?肯定不是。

seq作用在于同步双方的初始序号,Linux一般是提供随机数,而Windows从0开始区分新旧连接,重建连接时初始序号的不同可以做区分问题2:如果只有两次握手可以吗?不可以,会出现以下问题:连接状态不确定。

服务端处于连接状态,但客户端可能未收到响应报文,处于SYN-SENT状态,导致服务端一直未收到客户端的数据冗余连接请求如果服务端发送确认报文,而客户端未响应,等待超时后会重发确认报文给客户端,直到最大的重试次数。

可靠性问题。如果客户端未收到确认报文,seq会不一致,加上网络影响,可能会出现丢包1.3、四次挥手

当客户端已经传输完数据后,会主动关闭连接,然后经历四次挥手的过程客户端主动关闭连接时,发送终止报文给服务端:FIN=1,seq=uFIN=1是告诉服务器要关闭连接seq=u表明客户端最后发送的字节号为u。

服务端发送确认报文:ACK=1,seq=v,ack=u+1ACK=1表明服务端知晓你要关闭连接,我会将缓冲区的数据发给你seq=v表明给你发送的最大的字节序号为vack=u+1表明收到客户端的序列号为u。

服务端数据发送完成的报文:FIN=1,ACK=1,seq=w,ack=u+1FIN=1表明数据传输完毕,可以关闭连接了ACK=1表明是确认报文客户端收到确认报文后回一个确认报文:ACK=1,seq=u+1,ack=w+1。

ack=w+1表明客户端收到了序号到w的字节客户端处于TIME_WAIT,等待报文往返时间然后客户端关闭连接问题1:为何存在TIME_WAIT状态?2MSL是报文最大的往返时间,超过这个时间,报文会被丢弃,超时等待是保证服务器收到最后的ACK报文。

问题2:为何要挥手四次?我们就看下哪次可以省略第一次挥手是由客户端发送终止报文,无法省略,不然服务器怎么知晓你要关闭连接第二次挥手是用于确认收到客户端终止报文的,无法省略那么如果没有数据要发送,直接发送FIN=1,ACK=1的确认报文给客户端可以吗?当然不行,第二次挥手是给客户端的确认报文,如果加上FIN=1,说明这是服务器的终止报文,就不是确认报文了。

第三次挥手是在无数据发送时的服务器终止报文,无法省略如果省略会导致客户端无法知晓服务器是否将数据传输完毕,然后终止连接第四次挥手是客户端确认收到服务器的终止报文,无法省略如果省略,服务器不知道客户端是否收到终止报文,客户端独自关闭连接,然后服务器一直挂着连接,浪费资源。

TIME_WAIT也会进一步保证服务器收到最后的ACK报文2、异常解析2.1、抓包工具winshark是在Windows中的抓包工具,开启捕获操作时,通过过滤可以得到我们想要的抓包数据

tcp.port==8080,表明抓包TCP连接中端口号为8080的报文2.2、RST报文import java.net.InetSocketAddress; import java.nio.ByteBuffer;

import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; publicclassServerDemo

{ publicstatic void main(String[] args) throwsException { ServerSocketChannel serverSocketChannel =

ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(8080)); SocketChannel

socketChannel = serverSocketChannel.accept(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024

); for(;;) { Thread.sleep(1000); socketChannel.read(byteBuffer); socketChannel.write(

ByteBuffer.wrap("hello client".getBytes())); System.out.println("read success"); } } }

import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel;

publicclassClientDemo{ publicstatic void main(String[] args) throwsException { SocketChannel

socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress(8080));

for (int i = 0; i < 2; i++) { Thread.sleep(1000); socketChannel.write(ByteBuffer

.wrap("hello server".getBytes())); System.out.println("write success"); }

ByteBuffer byteBuffer = ByteBuffer.allocate(1024); socketChannel.read(byteBuffer); socketChannel.close();

Thread.sleep(10000); } }服务器出现异常:

有个小疑问:为何所有write方法都会将PSH置1哪位大佬看到这里能解答一下?通过winshark抓包:56590端口是客户端的,8080是服务端的在这里只需关注控制位(FIN、ACK、RST)客户端端口是内核随机分配的,每次运行时端口都会不一样。

为啥客户端会发送RST报文?我们猜一下是由于服务器还有数据发送导致客户端发送RST强制中断连接那么简化一下代码,看看是否有RST报文出现改成服务器读取一次,客户端写一次,再看下客户端是否调用close的情况。

客户端调用close:由于客户端要经历四次挥手,但服务端进程已经结束,内核会发送RST报文给客户端,强制关闭连接

客户端未调用close:客户端结束进程,给服务端发送RST报文表明强制关闭连接

注意:如果一方已经发送RST报文,而另一方还是调用read/write操作,会有异常抛出否则就没有异常客户端未调用close的情况下,是不是也有可能是服务端给客户端发送RST报文给客户端加个睡眠,看看结果如何?。

当客户端增加睡眠时间,服务器给客户端发送RST。我们让服务端在最后睡眠,客户端直接结束,会发生什么情况?

给服务端增加睡眠时间,客户端先结束,会给服务端发送RST报文。进一步说明先结束的一方会给另一方发送RST以上代码服务端均未调用close关闭与客户端的连接,如果调用close会不会有RST报文?

当双方都调用close将客户端连接关闭时,报文显示有完整地四次挥手动作小结:从当前的分析来看,如果没有正常调用close方法(服务端和客户端将socketChannel关闭),就结束进程,先结束的一方会发送RST报文给另一方,强制关闭连接

2.3、异常信息来源

我们很好奇Connection reset by peer异常字段从哪来的?从程序中全局搜索不到那么最大可能性是由底层传给程序的,所以我们来追踪源码,找到来源后续代码涉及到C/C++ 语言,以及JVM、glibc知识点,如果不懂的小伙伴,可以直接跳到小结处,我会简略总结一下。

1、异常堆栈中显示最近一次调用是FileDispatcherImpl.read0,这是JNI方法,就从FileDispatcherImpl.c找到read0方法read(fd, buf, len)是系统调用,没有任何异常信息输出。

可能性在于convertReturnVal函数中

2、在IOUtil.c中找到convertReturnVal函数前面几个判断都是特殊情况,而像我们这种异常会在最后一个else中,找一下JNU_ThrowIOExceptionWithLastError函数。

3、获取最近一次错误信息,如果有就转换成java的String类型,调用Throw抛出异常jin_util.c

4、errno是C函数库的全局变量,存储最近一次的错误号。strerror是C函数库中的全局函数,根据错误号获取对应的错误信息jvm.cpp

os_linux.cpp

5、涉及到C函数库,我们从glibc中寻找。当接收到RST报文时,C函数库会转换成ECONNRESET错误信息errlist.c

6、全局变量errno存放最近一次的错误号,如果多线程情况下,errno是不是会被覆盖extern:全局标识符__thread:声明为线程局部变量attribute_tls_model_ie:指定TLS模型。

所以errno是TLS变量,也就是线程本地变量,并不会被其他线程覆盖include/errno.h

小结:异常关闭连接时会由先结束的一方发送RST报文,如果另一方还继续操作,jvm会从glibc获取最近的错误号glibc使用TLS机制,errno(错误号)是线程本地变量,不用担心会被其他线程覆盖根据错误号从定义的错误列表中找到对应的错误信息(Connection reset by peer)

JVM抛出IOException异常,填入获取到的错误信息3、总结抛出Connection reset by peer异常是由于RST报文导致的,服务端或客户端的一方异常关闭连接时,另一方依旧发送报文,这时只能收到RST报文,表明强制关闭连接。

JVM根据glibc最近一次的错误号(ECONNRESET)从错误列表查询到错误信息,抛出IOException

免责声明:本站所有信息均搜集自互联网,并不代表本站观点,本站不对其真实合法性负责。如有信息侵犯了您的权益,请告知,本站将立刻处理。联系QQ:1640731186