Java网络编程的基本语法与Socket使用

Java客户端必须用Socket而非ServerSocket建立连接,因后者仅用于服务端监听;Socket需显式设超时、用字节数组读写、包装BufferedReader处理换行,并通过异常捕获和资源关闭保障健壮性。

Java中创建TCP客户端用Socket而不是ServerSocket

很多人一看到“网络编程”就下意识用ServerSocket,其实那是服务端监听用的。客户端主动连接服务器,必须用Socket类实例化连接。

常见错误是写成new ServerSocket(8080)去连远程地址,结果抛出java.net.BindException: Address already in use——因为ServerSocket是在本机绑定端口监听,不是发起连接。

  • 客户端连接示例:
    Socket socket = new Socket("127.0.0.1", 8080);
  • 连接超时要显式设置:socket.connect(new InetSocketAddress("example.com", 80), 5000);
  • 不设超时,Socket默认阻塞直到建立连接或系统超时(可能长达数分钟)

InputStreamOutputStream不能直接读写字符串

Socket的getInputStream()返回的是字节流,read()方法返回int(-1表示流结束),不是字符;write()也只接受byte[]或单个int。直接传入"hello"会编译失败。

常见误操作:用socket.getOutputStream().write("hello"),报错cannot resolve method write(java.lang.String)

  • 正确做法是转字节数组:outputStream.write("hello\n".getBytes(StandardCharsets.UTF_8));
  • 如果想按行读,得包装成BufferedReader
    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
  • 注意换行符:很多协议依赖\n\r\n作为消息边界,别漏写

服务端用ServerSocket accept后必须为每个连接启新线程或用NIO

ServerSocket.accept()是阻塞调用,一次只返回一个Socket。如果在主线程里直接read()处理,后续连接会被卡住,无法并发响应。

典型现象:第二个客户端连上来一直卡在connect(),服务端日志停在第一个请求处理中。

  • 最简并发方案:每accept一个连接,就new Thread(() -> handleConnection(clientSocket)).start();
  • 但线程开销大,高并发下容易OOM,生产环境应改用ExecutorServicejava.nio.channels.ServerSocketChannel
  • 别忘了关闭资源:clientSocket.close()inputStream.close()等,否则文件描述符泄漏,跑一阵就报java.io.IOException: Too many open files

异常处理必须覆盖IOExceptionSocketTimeoutException

网络不稳定是常态,但新手常只捕获Exception或干脆忽略异常,导致连接中断后程序静默卡死或崩溃。

比如设置了socket.setSoTimeout(3000),读取超时时抛的是SocketTimeoutException(它是IOException子类),不是InterruptedExceptionRuntimeException

  • 必须显式处理:
    try {
        int b = inputStream.read();
    } catch (SocketTimeoutException e) {
        System.err.p

    rintln("Read timeout, reconnecting..."); } catch (IOException e) { System.err.println("Connection broken: " + e.getMessage()); }
  • Socket.isClosed()Socket.isConnected()返回值有误导性:isConnected()只表示曾经连过,断连后仍返回true;判断是否可用得靠socket.getInputStream().read()是否抛异常
真正难的不是写通第一对收发,而是让连接在丢包、闪断、防火墙干扰下还能自动恢复、不堆积线程、不泄露fd。这些细节不在语法里,而在每次read()之后的判断和每次close()之前的检查里。