杰瑞科技汇

java socket 超时设置

为什么需要设置 Socket 超时?

在网络通信中,可能会遇到各种不可预见的情况,

java socket 超时设置-图1
(图片来源网络,侵删)
  • 目标主机不在线或未响应。
  • 网络中间设备(如路由器、防火墙)阻塞了数据包。
  • 网络连接不稳定,数据包丢失。
  • 服务器处理缓慢,无法及时返回数据。

如果没有超时机制,当发生上述情况时,调用 Socket 相关方法(如 connect(), read(), write())的线程将会永久阻塞,等待一个可能永远不会到来的响应,导致整个应用程序挂起。

Java Socket 提供了两种主要的超时设置:

  1. 连接超时:在尝试建立连接时等待的最长时间。
  2. 读写超时:在进行数据读取或写入时等待的最长时间。

连接超时设置

连接超时通常在 Socket 对象创建之前进行设置,我们通过 Socket 类的构造方法来实现。

关键方法:Socket(InetAddress address, int port, int timeout)

这个构造方法允许你直接在创建 Socket 时指定连接超时(单位:毫秒)。

java socket 超时设置-图2
(图片来源网络,侵删)

示例代码:

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
public class SocketConnectTimeoutExample {
    public static void main(String[] args) {
        String host = "www.google.com"; // 目标主机
        int port = 80;                // 目标端口
        int timeout = 5000;           // 超时时间:5秒
        try {
            System.out.println("正在尝试连接到 " + host + ":" + port + "...");
            // 在创建Socket对象时指定超时
            Socket socket = new Socket(host, port, timeout);
            System.out.println("连接成功!");
            // ... 在这里进行数据通信 ...
            socket.close();
        } catch (UnknownHostException e) {
            System.err.println("无法找到主机: " + host);
        } catch (SocketTimeoutException e) {
            // 捕获超时异常
            System.err.println("连接超时!在 " + timeout + " 毫秒内未能建立连接。");
        } catch (IOException e) {
            System.err.println("发生I/O错误: " + e.getMessage());
        }
    }
}

另一种常见方式(先创建无连接的Socket,再设置超时并连接):

这种方式更灵活,因为它允许你先配置 Socket 的其他属性(如 SO_REUSEADDR),然后再连接。

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class SocketConnectTimeoutExample2 {
    public static void main(String[] args) {
        String host = "www.google.com";
        int port = 80;
        int timeout = 3000; // 3秒超时
        try {
            // 1. 创建一个未连接的Socket对象
            Socket socket = new Socket();
            // 2. 设置连接超时
            // 这个方法必须在 connect() 调用之前执行
            socket.setSoTimeout(timeout); // 注意:这个方法主要是用来设置读写超时,但连接超时通常通过下面的 connect(timeout) 方法设置
            // 3. 调用带超时的connect方法
            // 这是设置连接超时的标准方式
            socket.connect(new java.net.InetSocketAddress(host, port), timeout);
            System.out.println("连接成功!");
            socket.close();
        } catch (SocketTimeoutException e) {
            System.err.println("连接超时!在 " + timeout + " 毫秒内未能建立连接。");
        } catch (IOException e) {
            System.err.println("发生I/O错误: " + e.getMessage());
        }
    }
}

注意: socket.setSoTimeout() 主要用于设置后续的 I/O 操作(读/写)的超时,而 socket.connect(..., timeout) 才是专门用于设置连接超时的,第一种构造方法 new Socket(host, port, timeout) 实际上是第二种方式的简写。

java socket 超时设置-图3
(图片来源网络,侵删)

读写超时设置

连接成功后,在进行 InputStream.read()OutputStream.write() 操作时,也可能因为网络问题而阻塞,这时就需要设置读写超时。

关键方法:socket.setSoTimeout(int timeout)

这个方法用于设置 SocketSO_TIMEOUT选项,单位是毫秒,它影响的是所有基于此 SocketInputStream 的读操作。

  • 0:表示禁用超时,即无限期等待(默认行为)。
  • > 0:表示设置指定的超时时间,如果在此时间内没有数据可读,InputStream.read() 方法将抛出 java.net.SocketTimeoutException

重要提示: setSoTimeout() 只影响读操作,不影响写操作,写操作的超时通常需要通过更底层的实现或使用 NIO 来实现。

示例代码:

import java.io.*;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class SocketReadTimeoutExample {
    public static void main(String[] args) {
        String host = "time.nist.gov"; // 一个提供时间服务的服务器
        int port = 13;                // Daytime protocol 端口
        int readTimeout = 5000;       // 读写超时:5秒
        try (Socket socket = new Socket(host, port)) {
            // 设置读写超时
            socket.setSoTimeout(readTimeout);
            System.out.println("已连接到 " + host + ":" + port);
            System.out.println("设置了 " + readTimeout + " 毫秒的读写超时。");
            InputStream in = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            System.out.println("正在等待服务器响应...");
            // readLine() 方法可能会在这里阻塞,直到有数据到达或超时
            String line = reader.readLine();
            if (line != null) {
                System.out.println("服务器响应: " + line);
            } else {
                System.out.println("服务器没有返回数据。");
            }
        } catch (SocketTimeoutException e) {
            System.err.println("读取数据超时!在 " + readTimeout + " 毫秒内未收到数据。");
        } catch (IOException e) {
            System.err.println("发生I/O错误: " + e.getMessage());
        }
    }
}

完整示例:连接和读写超时

下面是一个结合了连接超时和读写超时的完整示例。

import java.io.*;
import java.net.*;
public class ComprehensiveSocketTimeoutExample {
    public static void main(String[] args) {
        String host = "www.baidu.com";
        int port = 80;
        int connectTimeout = 3000; // 3秒连接超时
        int readTimeout = 5000;     // 5秒读写超时
        try {
            // 1. 创建Socket并设置连接超时
            System.out.println("正在尝试连接到 " + host + ":" + port + "...");
            Socket socket = new Socket();
            socket.connect(new InetSocketAddress(host, port), connectTimeout);
            System.out.println("连接成功!");
            // 2. 设置读写超时
            socket.setSoTimeout(readTimeout);
            System.out.println("已设置读写超时为 " + readTimeout + " 毫秒。");
            // 3. 发送HTTP请求 (示例)
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            out.println("GET / HTTP/1.1");
            out.println("Host: " + host);
            out.println("Connection: close");
            out.println(); // 空行表示头部结束
            // 4. 读取响应
            System.out.println("正在读取服务器响应...");
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String inputLine;
            int count = 0;
            while ((inputLine = in.readLine()) != null) {
                System.out.println(inputLine); // 打印响应内容
                count++;
                // 防止输出过多,可以限制读取的行数
                if (count > 10) {
                    System.out.println("...(已读取10行,停止打印后续内容)");
                    break;
                }
            }
            System.out.println("通信完成。");
            socket.close();
        } catch (SocketTimeoutException e) {
            // 这个异常可能是连接超时,也可能是读写超时
            // 可以通过判断操作类型来更精确地处理
            System.err.println("操作超时!可能是连接或读写超时。");
        } catch (IOException e) {
            System.err.println("发生I/O错误: " + e.getMessage());
        }
    }
}

总结与最佳实践

超时类型 设置方法 作用时机 异常
连接超时 new Socket(host, port, timeout) 创建 Socket SocketTimeoutException
socket.connect(addr, timeout) 调用 connect() 方法前 SocketTimeoutException
读写超时 socket.setSoTimeout(timeout) connect() 成功后,任何 read() 操作前 SocketTimeoutException

最佳实践:

  1. 总是设置超时:在生产环境中,为所有网络操作设置合理的超时是必须的,以避免程序挂起。
  2. 区分超时类型:清晰地设置连接超时和读写超时,连接超时通常较短(如3-5秒),而读写超时可以根据业务需求设置得更长。
  3. 捕获 SocketTimeoutException:务必用 try-catch 块来捕获 SocketTimeoutException,并在异常发生时采取相应措施(如重试、记录日志、通知用户等)。
  4. 关闭资源:使用 try-with-resources 语句或在 finally 块中确保 Socket 和相关的 InputStream/OutputStream 被正确关闭,以释放系统资源。
  5. 考虑 NIO:对于需要更高性能和更精细超时控制的场景,可以考虑使用 Java NIO (New I/O) 的 SelectorChannel,它们提供了非阻塞 I/O 和更灵活的超时管理机制。
分享:
扫描分享到社交APP
上一篇
下一篇