Socket超时时间设置

5,009 阅读5分钟

你知道在 Java 中怎么对 Socket 设置超时时间吗?他们的区别是什么?想一想和女朋友打电话的场景就知道了,如果实在想不到,那我们就一起来看一下是咋回事吧。

设置方式

主要有以下两种方式

方式1:

Socket socket = new Socket(); 
socket.connect(new InetSocketAddress(host,port),10000);

方式2:

Socket socket = new Socket("127.0.0.1",8080);
socket.setSoTimeout(10000);

那么这两种方式设置的超时时间各自代表了什么意义呢?有什么区别呢?

实际测试

第1种方式

我们先来看一下第一种方式,我们来测试一下:在main方法中我们创建 Socket 连接到 ip :29.212.19.201,端口:2132

public static void main(String[] args) {
    Socket socket = new Socket();
    SocketAddress endpoint = new InetSocketAddress("29.212.19.201", 2132);
    long timeMillis = System.currentTimeMillis();
    try {
      socket.connect(endpoint, 10000);
    } catch (IOException e) {
      e.printStackTrace();
    }
    System.out.println(System.currentTimeMillis()-timeMillis);
    System.out.println("end");
  }

运行这段代码,控制台10秒之前没有任何信息输出,10秒后打印如下信息:

10002
java.net.SocketTimeoutException: connect timed out
  at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
  at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
  at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
  at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
  at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
  at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
  at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
  at java.net.Socket.connect(Socket.java:589)
  at com.wakling.cn.SocketSever.main(SocketSever.java:33)
end

可以看出,我们尝试连接到29.212.19.201:2132时,连接了10秒都没有连接上,于是就报了 java.net.SocketTimeoutException: connect timed out 连接超时的异常。

解释一下,上述的 IP 是一个未知的 IP ,即本机的 IP 在当前网络环境中访问不到上述的 IP ,这样我们的这个 Socket 才会去一直尝试连接到此 IP ,直到超时。如果上述 IP 是一个已知的 IP ,例如本地 127.0.0.1 加上一个未知的端口,那么此 Socket 连接会立马报错。

另外,在不设置连接超时时间的情况下,Socket 默认大概是21s(测试了3次都是21020毫秒)连接超时。如下是不设置连接超时时间的代码:

Socket socket = new Socket("29.212.19.201", 2132);

第2种方式

然后我们来看一下第2种方式,这时候我们需要在我们本地写一套 Socket 服务端+客户端来模拟这个场景。 我们让客户端设置 setSoTimeout 为10s,在服务端拿到客户端请求信息后,服务端休眠10s后再处理客户端请求,再返回响应。

我们来看一下效果,关键代码如下:

//服务端
System.out.println("进入休眠,10s后醒来");
Thread.sleep(10000);//休眠10秒
System.out.println("休眠结束");
//返回响应
OutputStream outputStream = socket.getOutputStream();// 获取一个输出流,向服务端发送信息
PrintWriter printWriter = new PrintWriter(outputStream);// 将输出流包装成打印流
printWriter.print("你好,服务端已接收到您的信息");
printWriter.flush();

//客户端
Socket socket = new Socket("127.0.0.1",2132);
socket.setSoTimeout(10000);//超时时间,10秒

运行后,等待客户端输出,10s后客户端控制台输出信息如下:

java.net.SocketTimeoutException: Read timed out
  at java.net.SocketInputStream.socketRead0(Native Method)
  at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
  at java.net.SocketInputStream.read(SocketInputStream.java:171)
  at java.net.SocketInputStream.read(SocketInputStream.java:141)
  at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
  at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
  at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
  at java.io.InputStreamReader.read(InputStreamReader.java:184)
  at java.io.BufferedReader.fill(BufferedReader.java:161)
  at java.io.BufferedReader.readLine(BufferedReader.java:324)
  at java.io.BufferedReader.readLine(BufferedReader.java:389)
  at com.wakling.cn.SocketClient.main(SocketClient.java:36)
10002
end

这里10s后客户端抛出 java.net.SocketTimeoutException: Read timed out 读取超时的异常。查看服务端端控制台信息,是正常输出的,即使客户端已报超时,服务端仍然继续往下走,只是客户端已经收不到服务端10s后发给自己的消息。

另外经测试发现,服务端休眠很久很久,如500s,在客户端不设置 setSoTimeout 时,客户端默认120s超时。 对于 setSoTimeout 方法官方给的解释是这样的,我不知道超时时间设置为0是否真的为无穷大超时,感兴趣的可以试一下。

setSoTimeout
public void setSoTimeout(int timeout)
throws SocketException启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位。将此选项设为非零的超时值时,在与此 Socket 关联的 InputStream 上调用 read() 将只阻塞此时间长度。
如果超过超时值,将引发 java.net.SocketTimeoutException,虽然 Socket 仍旧有效。选项必须在进入阻塞操作前被启用才能生效。
超时值必须是 > 0 的数。超时值为 0 被解释为无穷大超时值
参数:timeout - 指定的以毫秒为单位的超时值。
抛出:SocketException - 如果底层协议出现错误,例如 TCP 错误。
从以下版本开始:JDK 1.1
另请参见:getSoTimeout()

区别和意义

下面我们就来说一说二者的区别和意义。

方式1是客户端与服务端进行连接的超时时间,即10秒内建立不了连接就报 java.net.SocketTimeoutException: connect timed out 连接超时的异常。此时二者未建立连接,更别说服务端收到客户端的消息了。

方式2是设置 inputStream.read() 方法的阻塞时间,即客户端发出请求后等待服务端返回响应的等待时间,超过这个时长将会引发 java.net.SocketTimeoutException: Read timed out 异常。此时二者正常建立连接,服务端接收到了客户端的请求。

合理设置超时时间对增加程序的吞吐量和加强程序的健壮性有较为重大的意义。

两种方式控制超时的侧重点不同,就像女朋友给你打电话一样,她是有小脾气的。如果她按方法1对待你,那就是拨出去电话10秒内你不接电话她就挂了,如果她按方法2,那就是打电话接通后,假如你正在忙她就等你10秒,10秒内不说话就挂,10秒后说不说话她都听不到了,你就悲催了。

【END】

封图来源于网络,侵权联系删除。