1. /**
    2. * Enable/disable {@link SocketOptions#SO_TIMEOUT SO_TIMEOUT}
    3. * with the specified timeout, in milliseconds. With this option set
    4. * to a non-zero timeout, a read() call on the InputStream associated with
    5. * this Socket will block for only this amount of time. If the timeout
    6. * expires, a <B>java.net.SocketTimeoutException</B> is raised, though the
    7. * Socket is still valid. The option <B>must</B> be enabled
    8. * prior to entering the blocking operation to have effect. The
    9. * timeout must be {@code > 0}.
    10. * A timeout of zero is interpreted as an infinite timeout.
    11. *
    12. * @param timeout the specified timeout, in milliseconds.
    13. * @exception SocketException if there is an error
    14. * in the underlying protocol, such as a TCP error.
    15. * @since JDK 1.1
    16. * @see #getSoTimeout()
    17. */
    18. public synchronized int getSoTimeout() throws SocketException {
    19. if (isClosed())
    20. throw new SocketException("Socket is closed");
    21. Object o = getImpl().getOption(SocketOptions.SO_TIMEOUT);
    22. /* extra type safety */
    23. if (o instanceof Integer) {
    24. return ((Integer) o).intValue();
    25. } else {
    26. return 0;
    27. }
    28. }
    29. public synchronized void setSoTimeout(int timeout) throws SocketException {
    30. if (isClosed())
    31. throw new SocketException("Socket is closed");
    32. if (timeout < 0)
    33. throw new IllegalArgumentException("timeout can't be negative");
    34. getImpl().setOption(SocketOptions.SO_TIMEOUT, new Integer(timeout));
    35. }
    1. public void setSoTimeout(int timeout) throws SocketException
    2. 使用指定的超时时间启用/禁用SO_TIMEOUT(以毫秒为单位)。 使用此选项设置为非零超时时,
    3. 与此Socket相关联的InputStream上的read()调用将仅阻止此时间。
    4. 如果超时超时,则引发java.net.SocketTimeoutException ,尽管Socket仍然有效。
    5. 必须先启用该选项才能进入阻止操作才能生效。 超时时间必须为> 0 超时为零被解释为无限超时。
    6. 参数: timeout - 指定的超时时间,以毫秒为单位。
    7. 异常: SocketException - 如果底层协议有错误,如TCP错误。
    1. /** Set a timeout on blocking Socket operations:
    2. * <PRE>
    3. * ServerSocket.accept();
    4. * SocketInputStream.read();
    5. * DatagramSocket.receive();
    6. * </PRE>
    7. *
    8. * <P> The option must be set prior to entering a blocking
    9. * operation to take effect. If the timeout expires and the
    10. * operation would continue to block,
    11. * <B>java.io.InterruptedIOException</B> is raised. The Socket is
    12. * not closed in this case.
    13. *
    14. * <P> Valid for all sockets: SocketImpl, DatagramSocketImpl
    15. *
    16. * @see Socket#setSoTimeout
    17. * @see ServerSocket#setSoTimeout
    18. * @see DatagramSocket#setSoTimeout
    19. */
    20. @Native public final static int SO_TIMEOUT = 0x1006;
    1. @Native
    2. static final int SO_TIMEOUT
    3. 在阻塞套接字操作时设置超时:
    4. ServerSocket.accept();
    5. SocketInputStream.read();
    6. DatagramSocket.receive();
    7. 必须先设置该选项才能进入阻止操作才能生效。 如果超时过期,并且操作将继续阻止,
    8. 则引发java.io.InterruptedIOException 在这种情况下,Socket不关闭。
    9. 适用于所有套接字:SocketImplDatagramSocketImpl
    10. 另请参见:
    11. Socket.setSoTimeout(int) ServerSocket.setSoTimeout(int)
    12. DatagramSocket.setSoTimeout(int) Constant Field Values

    如果输入缓冲队列RecvQ中没有数据,read操作会一直阻塞而挂起线程,直到有新的数据到来或者有异常产生。调用setSoTimeout(int timeout)可以设置超时时间,如果到了超时时间仍没有数据,read会抛出一个SocketTimeoutException,程序需要捕获这个异常,但是当前的socket连接仍然是有效的。

    如果对方进程崩溃、对方机器突然重启、网络断开,本端的read会一直阻塞下去,这时设置超时时间是非常重要的,否则调用read的线程会一直挂起。

    TCP模块把接收到的数据放入RecvQ中,直到应用层调用输入流的read方法来读取。如果RecvQ队列被填满了,这时TCP会根据滑动窗口机制通知对方不要继续发送数据,本端停止接收从对端发送来的数据,直到接收者应用程序调用输入流的read方法后腾出了空间。


    代码说明SoTimeout

    1. import java.io.IOException;
    2. import java.io.InputStream;
    3. import java.net.ServerSocket;
    4. import java.net.Socket;
    5. public class ServerMain {
    6. public static void main(String[] args) throws IOException {
    7. ServerSocket serverSocket = new ServerSocket(8888);
    8. long t1 = 0;
    9. try {
    10. Socket socket = serverSocket.accept();
    11. System.out.println("服务端接收到一个连接");
    12. t1 = System.currentTimeMillis();
    13. //设置该通道的read()方法超时
    14. socket.setSoTimeout(5000);
    15. InputStream inputStream = accept.getInputStream();
    16. //read阻塞
    17. inputStream.read();
    18. } finally {
    19. System.out.println("服务端setSoTimeout 耗时:"
    20. + (System.currentTimeMillis() - t1));
    21. }
    22. }
    23. }
    1. import java.io.InputStream;
    2. import java.net.InetSocketAddress;
    3. import java.net.Socket;
    4. public class ClientMain {
    5. public static void main(String[] args) throws Exception {
    6. Socket socket = new Socket();
    7. socket.connect(new InetSocketAddress(8888));
    8. //设置超时时间
    9. socket.setSoTimeout(10000);
    10. InputStream inputStream = socket.getInputStream();
    11. long t1 = System.currentTimeMillis();
    12. try {
    13. inputStream.read();
    14. } finally {
    15. System.out.println("客户端setSoTimeout 耗时:"
    16. + (System.currentTimeMillis() - t1));
    17. }
    18. }
    19. }

    启动以后
    服务端日志

    1. 服务端接收到一个连接
    2. 服务端setSoTimeout 耗时:5007
    3. Exception in thread "main" java.net.SocketTimeoutException: Read timed out
    4. at java.net.SocketInputStream.socketRead0(Native Method)
    5. at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    6. at java.net.SocketInputStream.read(SocketInputStream.java:171)
    7. at java.net.SocketInputStream.read(SocketInputStream.java:141)
    8. at java.net.SocketInputStream.read(SocketInputStream.java:224)
    9. at cn.java.money.bio.demo9.ServerMain.main(ServerMain.java:21)

    客户端日志

    1. 客户端setSoTimeout 耗时:5496
    2. Exception in thread "main" java.net.SocketException: Connection reset
    3. at java.net.SocketInputStream.read(SocketInputStream.java:210)
    4. at java.net.SocketInputStream.read(SocketInputStream.java:141)
    5. at java.net.SocketInputStream.read(SocketInputStream.java:224)
    6. at cn.java.money.bio.demo9.ClientMain.main(ClientMain.java:17)

    说明:
    soTimeout默认值是0,也就是没有超时时间,会无限的等待。
    服务端抛出异常 java.net.SocketTimeoutException: Read timed out 是因为我们设置了soTimeout socket.setSoTimeout(5000); 但是客户端一致没有写数据,服务端读数据等待5000毫秒,超时就抛出异常SocketTimeoutException,抛出异常以后,该连接就会被关闭,向客户端发送了RST包。因此客户端收到的是java.net.SocketException: Connection reset (RST包)https://www.yuque.com/protocal/tcp/nq74g5