尊重博主原创,特贴博客链接。copy下来只怕以后链接失效或删掉。
一、ScoketTimeout Exception浅析
原文连接:
https://www.cnblogs.com/huanghongbo/p/7890520.html
https://www.iteye.com/blog/bupt04406-1733001
Client端只有一个put请求,往server端写数据,server端处理时间过长,导致client端SocketTimeoutException
Client端发生异常SocketTimeoutException:
12/11/20 19:03:18 WARN client.HConnectionManager$HConnectionImplementation: Failed all from region=myLittleHBaseTable,,1353401128907.2879da4a2d609943473a2421520732cb., hostname=ubuntu, port=60020
java.util.concurrent.ExecutionException: java.net.SocketTimeoutException: Call to ubuntu/0:0:0:0:0:0:0:1:60020 failed on socket timeout exception: java.net.SocketTimeoutException: 60000 millis timeout while waiting for channel to be ready for read. ch : java.nio.channels.SocketChannel[connected local=/0:0:0:0:0:0:0:1:42426 remote=ubuntu/0:0:0:0:0:0:0:1:60020]
at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:222)
at java.util.concurrent.FutureTask.get(FutureTask.java:83)
at org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation.processBatchCallback(HConnectionManager.java:1553)
at org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation.processBatch(HConnectionManager.java:1376)
at org.apache.hadoop.hbase.client.HTable.flushCommits(HTable.java:937)
at org.apache.hadoop.hbase.client.HTable.doPut(HTable.java:793)
at org.apache.hadoop.hbase.client.HTable.put(HTable.java:768)
at updateTest.put(updateTest.java:61)
at updateTest.main(updateTest.java:96)
Caused by: java.net.SocketTimeoutException: Call to ubuntu/0:0:0:0:0:0:0:1:60020 failed on socket timeout exception: java.net.SocketTimeoutException: 60000 millis timeout while waiting for channel to be ready for read. ch : java.nio.channels.SocketChannel[connected local=/0:0:0:0:0:0:0:1:42426 remote=ubuntu/0:0:0:0:0:0:0:1:60020]
at org.apache.hadoop.hbase.ipc.HBaseClient.wrapException(HBaseClient.java:960)
at org.apache.hadoop.hbase.ipc.HBaseClient.call(HBaseClient.java:933)
at org.apache.hadoop.hbase.ipc.WritableRpcEngine$Invoker.invoke(WritableRpcEngine.java:147)
at $Proxy4.multi(Unknown Source)
at org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation$3$1.call(HConnectionManager.java:1353)
at org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation$3$1.call(HConnectionManager.java:1351)
at org.apache.hadoop.hbase.client.ServerCallable.withoutRetries(ServerCallable.java:210)
at org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation$3.call(HConnectionManager.java:1360)
at org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation$3.call(HConnectionManager.java:1348)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
Caused by: java.net.SocketTimeoutException: 60000 millis timeout while waiting for channel to be ready for read. ch : java.nio.channels.SocketChannel[connected local=/0:0:0:0:0:0:0:1:42426 remote=ubuntu/0:0:0:0:0:0:0:1:60020]
at org.apache.hadoop.net.SocketIOWithTimeout.doIO(SocketIOWithTimeout.java:164)
at org.apache.hadoop.net.SocketInputStream.read(SocketInputStream.java:155)
at org.apache.hadoop.net.SocketInputStream.read(SocketInputStream.java:128)
at java.io.FilterInputStream.read(FilterInputStream.java:116)
at java.io.FilterInputStream.read(FilterInputStream.java:116)
at org.apache.hadoop.hbase.ipc.HBaseClient$Connection$PingInputStream.read(HBaseClient.java:318)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
at java.io.BufferedInputStream.read(BufferedInputStream.java:237)
at java.io.DataInputStream.readInt(DataInputStream.java:370)
at org.apache.hadoop.hbase.ipc.HBaseClient$Connection.receiveResponse(HBaseClient.java:578)
at org.apache.hadoop.hbase.ipc.HBaseClient$Connection.run(HBaseClient.java:512)
HBaseClient的Connection一直在等待response,然后过了60000ms后,接收到SocketTimeoutException异常
client端关闭connection后,server端会抛出异常:
2012-11-20 20:54:46,825 WARN org.apache.hadoop.ipc.HBaseServer: (responseTooSlow): {"processingtimems":60370,"call":"multi(#size=1#myLittleHBaseTable,,1353401128907.2879da4a2d609943473a2421520732cb.{\"totalColumns\":1,\"families\":{\"myLittleFamily\":[{\"timestamp\":1353473626453,\"qualifier\":\"someQualifier\",\"vlen\":14}]},\"row\":\"myLittleRow1010001\"}#), rpc version=1, client version=29, methodsFingerPrint=-56040613","client":"0:0:0:0:0:0:0:1:42779","starttimems":1353473626452,"queuetimems":0,"class":"HRegionServer","responsesize":0,"method":"multi"}
2012-11-20 20:54:47,256 WARN org.apache.hadoop.ipc.HBaseServer: IPC Server listener on 60020: readAndProcess threw exception java.io.IOException: Connection reset by peer. Count of bytes read: 0
java.io.IOException: Connection reset by peer
at sun.nio.ch.FileDispatcher.read0(Native Method)
at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:21)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:202)
at sun.nio.ch.IOUtil.read(IOUtil.java:175)
at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:243)
at org.apache.hadoop.hbase.ipc.HBaseServer.channelRead(HBaseServer.java:2119)
at org.apache.hadoop.hbase.ipc.HBaseServer$Connection.readAndProcess(HBaseServer.java:1394)
at org.apache.hadoop.hbase.ipc.HBaseServer$Listener.doRead(HBaseServer.java:956)
at org.apache.hadoop.hbase.ipc.HBaseServer$Listener$Reader.doRunLoop(HBaseServer.java:745)
at org.apache.hadoop.hbase.ipc.HBaseServer$Listener$Reader.run(HBaseServer.java:720)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
2012-11-20 20:55:48,532 WARN org.apache.hadoop.ipc.HBaseServer: (responseTooSlow): {"processingtimems":60252,"call":"multi(#size=1#myLittleHBaseTable,,1353401128907.2879da4a2d609943473a2421520732cb.{\"totalColumns\":1,\"families\":{\"myLittleFamily\":[{\"timestamp\":1353473688277,\"qualifier\":\"someQualifier\",\"vlen\":14}]},\"row\":\"myLittleRow1010001\"}#), rpc version=1, client version=29, methodsFingerPrint=-56040613","client":"0:0:0:0:0:0:0:1:42785","starttimems":1353473688277,"queuetimems":0,"class":"HRegionServer","responsesize":0,"method":"multi"}
2012-11-20 20:55:48,548 WARN org.apache.hadoop.ipc.HBaseServer: IPC Server Responder, call multi(#size=1#myLittleHBaseTable,,1353401128907.2879da4a2d609943473a2421520732cb.{"totalColumns":1,"families":{"myLittleFamily":[{"timestamp":1353473688277,"qualifier":"someQualifier","vlen":14}]},"row":"myLittleRow1010001"}#), rpc version=1, client version=29, methodsFingerPrint=-56040613 from 0:0:0:0:0:0:0:1:42785: output error
2012-11-20 20:55:48,549 WARN org.apache.hadoop.ipc.HBaseServer: IPC Server handler 5 on 60020 caught a ClosedChannelException, this means that the server was processing a request but the client went away. The error message was: null
所以Server端的异常: Connection reset by peer,以及ClosedChannelException可能是因为server端处理时间过长,导致client端close掉connection,server端处理结束后再往client发送response时而出现。
还有的原因就是kill掉了client,server端处理结束后再往client发送response时而出现。
二、JAVA Socket超时浅析
原文链接:https://blog.csdn.net/sureyonder/article/details/5633647
套接字或插座(socket)是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。JAVA有两个基于数据流的套接字类:ServerSocket,服务器用它“侦听”进入的连接;Socket,客户端用它初始一次连接。侦听套接字只能接收新的连接请求,不能接收实际的数据包。
套接字是基于TCP/IP实现的,它是用来提供一个访问TCP的服务接口,或者说套接字socket是TCP的应用编程接口API,通过它应用层就可以访问TCP提供的服务。
在JAVA中,我们用ServerSocket、Socket类创建一个套接字连接,从套接字得到的结果是一个InputStream以及OutputStream对象,以便将连接作为一个IO流对象对待。通过IO流可以从流中读取数据或者写数据到流中,读写IO流会有异常IOException产生。
套接字底层是基于TCP的,所以socket的超时和TCP超时是相同的。下面先讨论套接字读写缓冲区,接着讨论连接建立超时、读写超时以及JAVA套接字编程的嵌套异常捕获和一个超时例子程序的抓包示例。
1 socket读写缓冲区
一旦创建了一个套接字实例,操作系统就会为其分配缓冲区以存放接收和要发送的数据。
JAVA可以设置读写缓冲区的大小-setReceiveBufferSize(int size), setSendBufferSize(int size)。
向输出流写数据并不意味着数据实际上已经被发送,它们只是被复制到了发送缓冲区队列SendQ,就是在Socket的OutputStream上调用flush()方法,也不能保证数据能够立即发送到网络。真正的数据发送是由操作系统的TCP协议栈模块从缓冲区中取数据发送到网络来完成的。
当有数据从网络来到时,TCP协议栈模块接收数据并放入接收缓冲区队列RecvQ,输入流InputStream通过read方法从RecvQ中取出数据。
2 socket连接建立超时
socket连接建立是基于TCP的连接建立过程。TCP的连接需要通过3次握手报文来完成,开始建立TCP连接时需要发送同步SYN报文,然后等待确认报文SYN+ACK,最后再发送确认报文ACK。TCP连接的关闭通过4次挥手来完成,主动关闭TCP连接的一方发送FIN报文,等待对方的确认报文;被动关闭的一方也发送FIN报文,然等待确认报文。
正在等待TCP连接请求的一端有一个固定长度的连接队列,该队列中的连接已经被TCP接受(即三次握手已经完成),但还没有被应用层所接受。TCP接受一个连接是将其放入这个连接队列,而应用层接受连接是将其从该队列中移出。应用层可以通过设置backlog变量来指明该连接队列的最大长度,即已被TCP接受而等待应用层接受的最大连接数。
当一个连接请求SYN到达时,TCP确定是否接受这个连接。如果队列中还有空间,TCP模块将对SYN进行确认并完成连接的建立。但应用层只有在三次握手中的第三个报文收到后才会知道这个新连接。如果队列没有空间,TCP将不理会收到的SYN。
如果应用层不能及时接受已被TCP接受的连接,这些连接可能占满整个连接队列,新的连接请求可能不被响应而会超时。如果一个连接请求SYN发送后,一段时间后没有收到确认SYN+ACK,TCP会重传这个连接请求SYN两次,每次重传的时间间隔加倍,在规定的时间内仍没有收到SYN+ACK,TCP将放弃这个连接请求,连接建立就超时了。
JAVA Socket连接建立超时和TCP是相同的,如果TCP建立连接时三次握手超时,那么导致Socket连接建立也就超时了。可以设置Socket连接建立的超时时间-
connect(SocketAddress endpoint, int timeout)
如果在timeout内,连接没有建立成功,在TimeoutException异常被抛出。如果timeout的值小于三次握手的时间,那么Socket连接永远也不会建立。
不同的应用层有不同的连接建立过程,Socket的连接建立和TCP一样-仅仅需要三次握手就完成连接,但有些应用程序需要交互很多信息后才能成功建立连接,比如Telnet协议,在TCP三次握手完成后,需要进行选项协商之后,Telnet连接才建立完成。
3 socket读超时
如果输入缓冲队列RecvQ中没有数据,read操作会一直阻塞而挂起线程,直到有新的数据到来或者有异常产生。调用setSoTimeout(int timeout)可以设置超时时间,如果到了超时时间仍没有数据,read会抛出一个SocketTimeoutException,程序需要捕获这个异常,但是当前的socket连接仍然是有效的。
如果对方进程崩溃、对方机器突然重启、网络断开,本端的read会一直阻塞下去,这时设置超时时间是非常重要的,否则调用read的线程会一直挂起。
TCP模块把接收到的数据放入RecvQ中,直到应用层调用输入流的read方法来读取。如果RecvQ队列被填满了,这时TCP会根据滑动窗口机制通知对方不要继续发送数据,本端停止接收从对端发送来的数据,直到接收者应用程序调用输入流的read方法后腾出了空间。
4 socket写超时
socket的写超时是基于TCP的超时重传。超时重传是TCP保证数据可靠性传输的一个重要机制,其原理是在发送一个数据报文后就开启一个计时器,在一定时间内如果没有得到发送报文的确认ACK,那么就重新发送报文。如果重新发送多次之后,仍没有确认报文,就发送一个复位报文RST,然后关闭TCP连接。首次数据报文发送与复位报文传输之间的时间差大约为9分钟,也就是说如果9分钟内没有得到确认报文,就关闭连接。但是这个值是根据不同的TCP协议栈实现而不同。
如果发送端调用write持续地写出数据,直到SendQ队列被填满。如果在SendQ队列已满时调用write方法,则write将被阻塞,直到SendQ有新的空闲空间为止,也就是说直到一些字节传输到了接收者套接字的RecvQ中。如果此时RecvQ队列也已经被填满,所有操作都将停止,直到接收端调用read方法将一些字节传输到应用程序。
当Socket的write发送数据时,如果网线断开、对端进程崩溃或者对端机器重启动,TCP模块会重传数据,最后超时而关闭连接。下次如再调用write会导致一个异常而退出。
Socket写超时是基于TCP协议栈的超时重传机制,一般不需要设置write的超时时间,也没有提供这种方法。
5 双重嵌套异常捕获
如果ServerSocket、Socket构造失败,只需要仅仅捕获这个构造失败异常而不需要调用套接字的close方法来释放资源(必须保证构造失败后不会留下任何需要清除的资源),因为这时套接字内部资源没有被成功分配。如果构造成功,必须进入一个try finally语句块里调用close释放套接字。请参照下面例子程序。
import java.net.*;
import java.io.*;
public class SocketClientTest
{
public static final int PORT = 8088;
public static void main( String[] args ) throws Exception
{
InetAddress addr = InetAddress.getByName( "127.0.0.1" );
Socket socket = new Socket();
try
{
socket.connect( new InetSocketAddress( addr, PORT ), 30000 );
socket.setSendBufferSize(100);
BufferedWriter out = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) );
int i = 0;
while( true )
{
System.out.println( "client sent --- hello *** " + i++ );
out.write( "client sent --- hello *** " + i );
out.flush();
Thread.sleep( 1000 );
}
}
finally
{
socket.close();
}
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServerTest
{
public static final int PORT = 8088;
public static final int BACKLOG = 2;
public static void main( String[] args ) throws IOException
{
ServerSocket server = new ServerSocket( PORT, BACKLOG );
System.out.println("started: " + server);
try
{
Socket socket = server.accept();
try
{
BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
String info = null;
while( ( info = in.readLine() ) != null )
{
System.out.println( info );
}
}
finally
{
socket.close();
}
}
finally
{
server.close();
}
}
}
执行上面的程序,在程序运行一会儿之后,断开client和server之间的网络连接,在机器上输出如下:
Server上的输出:
Echoing:client sent -----hello0
Echoing:client sent -----hello1
Echoing:client sent -----hello2
Echoing:client sent -----hello3
Echoing:client sent -----hello4
Echoing:client sent -----hello5
Echoing:client sent -----hello6
---->> 断开了网络连接之后没有数据输出
Client上的输出:
socket default timeout = 0
socket = Socket[addr=/10.15.9.99,port=8088,localport=4691]
begin to read
client sent --- hello *** 0
client sent --- hello *** 1
client sent --- hello *** 2
client sent --- hello *** 3
client sent --- hello *** 4
client sent --- hello *** 5
client sent --- hello *** 6
client sent --- hello *** 7
client sent --- hello *** 8
client sent --- hello *** 9
client sent --- hello *** 10
---->> 断开网络连接后客户端进程挂起
-------------------------------------------------------------------------------
java.net.SocketException : Connection reset by peer: socket write error
at java.net.SocketOutputStream.socketWrite0( Native Method )
at java.net.SocketOutputStream.socketWrite( SocketOutputStream.java:92 )
at java.net.SocketOutputStream.write( SocketOutputStream.java:136 )
at sun.nio.cs.StreamEncoder.writeBytes( StreamEncoder.java:202 )
at sun.nio.cs.StreamEncoder.implFlushBuffer( StreamEncoder.java:272 )
at sun.nio.cs.StreamEncoder.implFlush( StreamEncoder.java:276 )
at sun.nio.cs.StreamEncoder.flush( StreamEncoder.java:122 )
at java.io.OutputStreamWriter.flush( OutputStreamWriter.java:212 )
at java.io.BufferedWriter.flush( BufferedWriter.java:236 )
at com.xtera.view.SocketClientTest.main( SocketClientTest.java:99 )
当hello6被发送到server端后,网络连接被断开,这时server端不能接收任何数据而挂起。client端仍然继续发送数据,实际上hello7、hello8、hello9、hello10都被复制到SendQ队列中,write方法立即返回。当client的SendQ队列被填满之后,write方法就被阻塞。TCP模块在发送报文hello7之后,没有收到确认而超时重传,再重传几次之后关闭了TCP连接,同时导致被阻塞的write方法异常返回。
通过抓包工具,我们可以看到超时重传的报文。
setSendBufferSize 和 ReceiveBufferSize
TCP发送缓存区和接收缓存区,默认是8192,一般情况下足够了,而且就算你增加了发送缓存区,对方没有增加它对应的接收缓冲,那么在TCP三握手时,最后确定的最大发送窗口还是双方最小的那个缓冲区,就算你无视,发了更多的数据,那么多出来的数据也会被丢弃。除非双方都协商好。
————————————————
版权声明:本文为CSDN博主「huang_xw」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/huang_xw/article/details/7338668