Java socket有以下两种timeout:html
当不设置该参数时,指客户端请求和服务端创建tcp链接时,会一直阻塞直到链接创建成功,或抛异常。当设置了connectTimeout, 客户端请求和服务端创建链接时,阻塞时间超过connectTimeout时,就会抛出异常java.net.ConnectException: Connection timed out: connect。java
咱们看以下精简后的代码,首先是服务端:mysql
serverSocket = new ServerSocket(8080); Socket socket = serverSocket.accept();
服务端开启ServerSocket监听8080端口,再看客户端:redis
socket = new Socket(); socket.connect(new InetSocketAddress("localhost", 8080)); System.out.println("Connected.");
打印“Connected.”,修改客户端代码中的主机名为一个不存在的主机:sql
socket = new Socket(); long t1 = 0; try { t1 = System.currentTimeMillis(); socket.connect(new InetSocketAddress("www.ss.ssss", 8080)); } catch (IOException e) { long t2 = System.currentTimeMillis(); e.printStackTrace(); System.out.println("Connect failed, take time -> " + (t2 - t1) + "ms."); }
抛出异常:java.net.ConnectException: Connection timed out: connect,并打印:Connect failed, take time -> 18532ms. 也就是当未设置connect timeout时,connect方法会阻塞直到底层异常抛出。通过测试socket有个默认的超时时间,大概在20秒左右(测试的值,不必定准确,待研究JVM源码)。下面咱们来设置connect timeout,再看看效果:网络
socket = new Socket(); long t1 = 0; try { t1 = System.currentTimeMillis(); // 设置connect timeout 为2000毫秒 socket.connect(new InetSocketAddress("www.ss.ssss", 8080), 2000); } catch (IOException e) { long t2 = System.currentTimeMillis(); e.printStackTrace(); System.out.println("Connect failed, take time -> " + (t2 - t1) + "ms."); }
抛出异常:java.net.SocketTimeoutException: connect timed out,并打印:Connect failed, take time -> 2014ms. 这里就是connect timeout发挥做用了。socket
先看下jdk源码注释:tcp
Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout.
这个参数经过socket.setSoTimeout(int timeout)方法设置,能够看出它的意思是,socket关联的InputStream的read()方法会阻塞,直到超过设置的so timeout,就会抛出SocketTimeoutException。当不设置这个参数时,默认值为无穷大,即InputStream的read方法会一直阻塞下去,除非链接断开。ide
下面经过代码来看下效果:工具
服务端代码:
serverSocket = new ServerSocket(8080); Socket socket = serverSocket.accept();
服务端只接受socket但不发送任何数据给客户端。客户端代码:
socket = new Socket(); socket.connect(new InetSocketAddress("localhost", 8080)); System.out.println("Connected."); in = socket.getInputStream(); System.out.println("reading..."); in.read(); System.out.println("read end");
客户端创建链接就开始读取InputStream。打印:
Connected.
reading...
而且一直阻塞在in.read(); 上。接下来我设置so timeout,代码以下:
long t1 = 0; try { socket = new Socket(); socket.connect(new InetSocketAddress("localhost", 8080)); // 设置so timeout 为2000毫秒 socket.setSoTimeout(2000); System.out.println("Connected."); in = socket.getInputStream(); System.out.println("reading..."); t1 = System.currentTimeMillis(); in.read(); } catch (IOException e) { long t2 = System.currentTimeMillis(); System.out.println("read end, take -> " + (t2 - t1) + "ms"); e.printStackTrace(); } finally { if (this.reader != null) { try { this.reader.close(); } catch (IOException e) { } } }
抛出异常:java.net.SocketTimeoutException: Read timed out, 打印:read end, take -> 2000ms , 说明so timeout起做用了。
咱们能够经过设置connect timeout来控制链接创建的超时时间(不是绝对的,当设置的主机名不合法,好比我设置主机名为abc,会抛异常java.net.UnknownHostException: abc,可是此时connect timeout设置是不起做用的,测试得出的结论,仅供参考)。
经过设置so timeout能够控制流读取数据的超时时间。
查阅MySQL Connector/J 5.1 Developer Guide 中的jdbc配置参数,有
connectTimeout Timeout for socket connect (in milliseconds), with 0 being no timeout. Only works on JDK-1.4 or newer. Defaults to '0'. Default: 0 Since version: 3.0.1 |
socketTimeout Timeout on network socket operations (0, the default means no timeout). Default: 0 Since version: 3.0.1 |
这两个参数分别就是对应上面咱们分析的connect timeout和so timeout。
参数的设置方法有两种,一种是经过url设置,
jdbc:mysql://[host1][:port1][,[host2][:port2]]...[/[database]] [?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]
即在url后面经过?加参数,好比jdbc:mysql://192.168.1.1:3306/test?connectTimeout=2000&socketTime=2000
还有一种方式是:
Properties info = new Properties(); info.put("user", this.username); info.put("password", this.password); info.put("connectTimeout", "2000"); info.put("socketTime", "2000"); return DriverManager.getConnection(this.url, info);
Jedis是最流行的redis java客户端工具,redis.clients.jedis.Jedis对象的构造器中就有参数设置,
public Jedis(final String host, final int port, final int connectionTimeout, final int soTimeout) { super(host, port, connectionTimeout, soTimeout); }
// 用一个参数timeout同时设置connect timeout 和 so timeout public Jedis(final String host, final int port, final int timeout) { super(host, port, timeout); }
Jedis中so timeout我的以为是有比较重要意义的,首先jedis so timeout默认值为2000毫秒,jedis的操做流程是客户端发送命令给客户端执行,而后客户端就开始执行InputStream.read()读取响应,当某个命令比较耗时(好比数据很是多的状况下执行“keys *”),而致使客户端迟迟没有收到响应,就可能致使java.net.SocketTimeoutException: Read timed out异常抛出。通常是不建议客户端执行很是耗时的命令,可是也不排除有这种特殊逻辑,那这时候就有可能须要修改Jeids中这个so timeout的值。
了解了这两个timeout以后,能够更好的处理一些网络服务的客户端和服务端,同时对排查一些问题也颇有帮助。通常的成熟的网络服务和客户端都应该有这两个参数的配置方法,当使用遇到相似问题能够从这个方向去考虑下。