Java 实例 – ServerSocket 和 Socket 通信实例(建议收藏)

Java 实例 – ServerSocket 和 Socket 通信实例:从零开始搭建简单网络聊天系统

你有没有想过,手机上微信聊天、网页上在线客服,它们背后是怎么实现的?其实,这些功能的核心原理,都离不开一种基础的网络通信机制——客户端与服务器之间的 Socket 通信。今天我们就来深入实践一次完整的 Java 实例,手把手带你搭建一个基于 ServerSocket 和 Socket 的简易聊天程序。

整个过程不依赖任何框架,纯原生 Java 实现,适合初学者理解底层原理,也适合中级开发者温习网络编程的核心逻辑。文章中会穿插关键知识点、代码注释和实际运行效果,确保你“看得懂、学得会、用得上”。


什么是 ServerSocket 和 Socket?它们的关系是什么?

在开始编码前,先来搞清楚两个核心概念。

Socket 就像是一根“通信管道”,是网络通信的最小单位。你可以把它想象成两个城市之间的一条高速公路,车(数据)可以在这条路上来回跑。每个 Socket 都由 IP 地址和端口号共同标识,例如 192.168.1.100:8080

ServerSocket 则是服务器端的“门卫”。它不直接参与通信,而是负责监听某个端口,等待客户端的连接请求。就像你家门铃响了,门卫(ServerSocket)会去开门,让来访者(客户端 Socket)进来。

简单比喻:ServerSocket 是“等电话”的老板,Socket 是“打电话的人”。电话接通后,双方才开始交流。


搭建服务器端:ServerSocket 的基本使用

我们先写一个服务器程序,它会启动一个服务,监听指定端口,等待客户端连接。

import java.io.*;
import java.net.*;

public class SimpleServer {
    public static void main(String[] args) {
        // 定义服务器监听的端口号
        int port = 8080;

        // 创建 ServerSocket 实例,绑定到指定端口
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("服务器已启动,正在监听端口 " + port + " ...");

            // 无限循环,等待客户端连接
            while (true) {
                // accept() 方法会阻塞,直到有客户端连接进来
                Socket clientSocket = serverSocket.accept();

                // 打印连接信息
                System.out.println("客户端已连接:" + clientSocket.getInetAddress());

                // 为每个客户端创建独立的处理线程
                new Thread(new ClientHandler(clientSocket)).start();
            }

        } catch (IOException e) {
            System.err.println("服务器启动失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

代码详解:

  • ServerSocket serverSocket = new ServerSocket(port):创建服务器监听对象,绑定到 8080 端口。如果端口被占用,会抛出异常。
  • serverSocket.accept():这是一个阻塞方法,服务器会一直等待,直到有客户端发起连接。这是 ServerSocket 的核心功能。
  • new Thread(new ClientHandler(clientSocket)).start():每个客户端连接都会启动一个新线程处理,避免阻塞其他客户端。这是多线程编程的典型应用。

客户端连接:Socket 的建立与通信

接下来,我们写一个客户端程序,它主动连接服务器,发送消息。

import java.io.*;
import java.net.*;

public class SimpleClient {
    public static void main(String[] args) {
        // 服务器地址和端口
        String serverAddress = "localhost";
        int port = 8080;

        try (Socket socket = new Socket(serverAddress, port);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {

            // 发送消息到服务器
            out.println("你好,我是客户端!");

            // 读取服务器的响应
            String response = in.readLine();
            System.out.println("服务器回复:" + response);

            // 保持连接,可继续发送消息(这里简化为一次)
            Thread.sleep(3000); // 延迟3秒后关闭

        } catch (IOException | InterruptedException e) {
            System.err.println("客户端连接失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

代码详解:

  • new Socket(serverAddress, port):客户端主动连接服务器,建立 Socket 连接。如果服务器未启动或地址错误,会抛出异常。
  • InputStreamReader(socket.getInputStream()):获取服务器发送过来的数据流。
  • PrintWriter(socket.getOutputStream(), true):用于向服务器发送数据,true 表示自动刷新,即写入后立即发送。
  • in.readLine():读取服务器返回的一行文本,同样会阻塞直到收到数据。

多客户端并发处理:使用线程池提升性能

上面的例子虽然能运行,但每次连接都创建一个新线程,长期运行可能造成资源浪费。我们来优化一下,使用线程池管理连接。

import java.io.*;
import java.net.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MultiClientServer {
    // 使用线程池管理客户端连接
    private static final ExecutorService threadPool = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
        int port = 8080;

        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("多客户端服务器启动,监听端口 " + port);

            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("新客户端连接:" + clientSocket.getInetAddress());

                // 提交任务到线程池处理
                threadPool.submit(new ClientHandler(clientSocket));
            }

        } catch (IOException e) {
            System.err.println("服务器启动失败:" + e.getMessage());
            e.printStackTrace();
        } finally {
            threadPool.shutdown(); // 关闭线程池
        }
    }
}

// 每个客户端的处理逻辑
class ClientHandler implements Runnable {
    private final Socket socket;

    public ClientHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {

            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                System.out.println("收到客户端消息:" + inputLine);
                out.println("服务器已收到:" + inputLine);
            }

        } catch (IOException e) {
            System.err.println("客户端通信异常:" + e.getMessage());
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

优化点说明:

  • Executors.newFixedThreadPool(10):创建一个大小为 10 的线程池,避免无限制创建线程。
  • threadPool.submit(...):将客户端处理任务提交给线程池,由线程池调度执行。
  • finally 块中关闭 Socket,确保资源释放。

运行流程与测试步骤

我们来一步步测试这个 Java 实例 – ServerSocket 和 Socket 通信实例。

  1. 先编译服务器类:

    javac MultiClientServer.java ClientHandler.java
    
  2. 启动服务器:

    java MultiClientServer
    
  3. 打开另一个终端,启动客户端:

    java SimpleClient
    
  4. 观察控制台输出:

    • 服务器会显示“新客户端连接”
    • 客户端会打印“服务器回复:服务器已收到:你好,我是客户端!”
  5. 你还可以用多个客户端同时连接,验证多线程处理能力。


常见问题与解决方案

问题 原因 解决方案
无法连接,提示“Connection refused” 服务器未启动或端口被占用 检查服务器是否运行,使用 `netstat -an
客户端卡住,无法收到响应 服务器未调用 out.println() 或未刷新 确保 PrintWriter 构造时传入 true 参数
程序崩溃,提示“Too many open files” 未关闭 Socket 或流 在 finally 块中统一关闭资源
多个客户端连接后,消息混乱 未使用线程隔离 每个客户端使用独立线程处理,避免共享资源

实际应用场景延伸

虽然我们只实现了一个“打招呼”的小功能,但这个基础架构可以扩展到很多真实场景:

  • 即时通讯系统(类似微信聊天)
  • 文件传输服务(客户端上传,服务器接收)
  • 多人在线游戏的同步逻辑
  • 物联网设备的远程控制(如温控器、摄像头)

只要理解了 ServerSocket 和 Socket 的通信模型,后续接入消息协议(如 JSON、Protobuf)、加密传输(SSL/TLS)、心跳检测等就变得容易多了。


总结:掌握核心,灵活应用

通过这次完整的 Java 实例 – ServerSocket 和 Socket 通信实例,我们不仅学会了如何搭建一个基础的网络服务,还深入理解了:

  • 服务器如何监听端口等待连接
  • 客户端如何发起连接并通信
  • 多客户端并发处理的线程模型
  • 资源管理与异常处理的最佳实践

网络编程看似复杂,但拆解开来,就是“连接 → 通信 → 关闭”三个步骤。只要掌握 ServerSocket 和 Socket 的核心用法,你就能轻松应对大多数场景。

希望这篇文章能帮你打通 Java 网络编程的“任督二脉”。动手试试吧,把代码跑起来,你会发现,原来网络通信也没那么神秘。