现在项目中出现一个问题,我们项目中有一个外部系统是通过socket接口通讯的,而且接口是异步的,就是说一个线程负责把消息发送到外部系统中,一个线程用来取,两个线程公用同一个socket,采用长连接的方式发送线程通过socket.getOutputStream().write(byte[] data)来发送的。
接受线程线程通过socket.getInputStream().read(byte[] buffer)来收取对方的响应。现在出现一个奇怪的问题,如果这个连接长时间不用,连接估计会被链路中的某些防火墙中断掉,奇怪的是
socket.getOutputStream().write(byte[] data)不会出现网络异常,造成已经发送出去的假象,
而且通过抓包工具抓,确实没有发送出去。到了socket.getInputStream().read(byte[] buffer)的时候才会出现网络异常。
接受线程线程通过socket.getInputStream().read(byte[] buffer)来收取对方的响应。现在出现一个奇怪的问题,如果这个连接长时间不用,连接估计会被链路中的某些防火墙中断掉,奇怪的是
socket.getOutputStream().write(byte[] data)不会出现网络异常,造成已经发送出去的假象,
而且通过抓包工具抓,确实没有发送出去。到了socket.getInputStream().read(byte[] buffer)的时候才会出现网络异常。
socket绑定的系统资源【主要是端口看看是否处于监听】
OutputStream对象回收方法重写。
发送线程发过的请求包,如果接收线程得到了相应的应答包,那么就标记为成功。这样在网络异常后,你就知道哪些发送成功了(但是不能准确知道哪些没发送成功)。2. 如果接收线程阻塞在read方法上,那么一旦socket断开,read在读完缓存数据后应该立刻抛出异常的。
TCP提供点对点的可靠的传输,其可靠性是通过TCP报文的响应报文保证的。TCP层发送的报文,需要接受到对应的确认报文,以保证TCP报正确发送,否则会重发。
向一个关闭的连接发送报文,会产生“连接等同复位”的错误,主要是由于对端接受到发往本地关闭端口的报文时,均会返回此错误。
但楼主这样向处于关闭状态的链路发送报文,却不发生异常的,相反,read操作却能接受到异常,这实在超出我的范围。这样的问题可能和运行环境有关:
1.用netstat命令,查一下本地是否监听该端口,如果没有监听,那么是JVM以上的问题;
但是,如果收发线程中一方出现问题,怎样将这个信息扩散给另一个线程?
(毕竟两个线程维护的是同一个Socket)
估计可能还要设计一个监控线程,时刻监控那个Socket的IO状况,并负责收发线程的Socket状态同步。下面说说,我对楼主出现问题的理解:
程序已经执行了socket.getOutputStream().write(byte [] b)方法,
但是,通过抓包工具却没有发现有报文发出。
这个应该是没有清空系统的发送缓冲区的结果。也就是说,你在编写上面的那个发送语句之后,
再追加一条socket.getOutputStream().flush();语句。
这样会立即清空发送缓冲区,抓包工具就能抓到包了。
“如果这个连接长时间不用,连接估计会被链路中的某些防火墙中断掉”
楼主可能对网络协议模型以及TCP/IP的实现原理不太清楚,
Socket长时间不用,就会抛出异常,这种情况一般是由于Socket超时而造成的,一般和防火墙没关系。
一般的情况下防火墙在,只负责什么样的Socket能够建立,或者,什么方向的信息能够穿越防火墙。
看到楼主给出的编码,应该都使用的是 直接阻塞读 方式来获取数据包的。
如果对方没有发送任何信息给接收方,接收方却仍使用阻塞读方式来读取IO数据,
那么,持续一段时间,就会抛出读超时的异常。
(这个异常的意思是,对方明明已经发送了信息,我却长时间没有收取到对方的信息,所以,出现异常了。
但,实际上,你根本就没有确定对方已经给你发送数据了。
所以,这种情况下使用阻塞读就不太科学了。)
也就是说,阻塞读,这种方式,最好是在你已经确定对方已经将数据写入IO流了、或者对方会在非常短的时间(至少要比读超时的时间短得多)内将数据写入IO流,这两种情况下使用。为了避免接收方采用直接阻塞读的方式编写程序而使程序产生读超时异常,当然,也避免TCP的连接断开,我们会让发送方定时发送维持连接包。这个就是维持连接包的作用,它没啥具体的意义。如果真如楼主所说的,Socket被防火墙断开,那么,抛出异常的就不仅仅是read方法了,write方法也会抛出异常的。
(write抛出的写超时异常,意思是,我明明已经将数据写入IO流了,可对方却迟迟没有收到,注意,是迟迟没有收到,不是立即收到,由于操作系统底层采用缓冲区或者动态滑动窗口机制来批量发送数据,使得该异常会对数据有滞后,也就是说,异常抛出以后,对方没收到的数据不仅包括当前数据,前几次在同一缓冲区中的数据,某些也有可能没有收到。)
怎么避免读超时 ?
一种是采用非阻塞读的方式来编程;
另一种是在调用阻塞读方法之前,先判断缓冲区里面是否有数据了,如果没有,就不要阻塞读了。提示:
判断接收缓冲区有多少数据的方法,貌似是InputStream类里面一个a开头的方法,长时间不用,记忆模糊了。
注意循环体中,适当使用Thread.sleep()或者Thread.yeid()方法。
以上都是我个人的见解,欢迎大家共同讨论
给你介绍个教学网站吧
http://rupeng.com/forum/jian-23703.html
里面的 JAVA应该这样学 教学视频应该对你有帮助
同时我贴一下自己模拟的向断开连接读的异常
客户端处于循环读状态,同时将服务器端kill -9(进程无法获取信号,不可能完成TCP连接断开的四次握手协议)
Caused by: java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:168)
at com.sun.net.ssl.internal.ssl.InputRecord.readFully(InputRecord.java:284)
at com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:319)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:720)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:677)
at com.sun.net.ssl.internal.ssl.AppInputStream.read(AppInputStream.java:75)
at sun.nio.cs.StreamDecoder$CharsetSD.readBytes(StreamDecoder.java:411)
at sun.nio.cs.StreamDecoder$CharsetSD.implRead(StreamDecoder.java:453)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:183)
at java.io.InputStreamReader.read(InputStreamReader.java:167)
at java.io.BufferedReader.fill(BufferedReader.java:136)
at java.io.BufferedReader.readLine(BufferedReader.java:299)
at java.io.BufferedReader.readLine(BufferedReader.java:362)
at com.spdb.com.ssl.SimpleSSLClient.send(SimpleSSLClient.java:67)
... 1 more
客户端受到“java.net.SocketException: Connection reset”异常,是由于对端返回连接等同复位消息所致,首先不是超时读,但不清楚为何抛出该异常,换句话受:客户端如何完成服务器端状态读取的。