ServerSocket可实现简易TCP聊天室:服务端用accept()阻塞监听,每客户端分配ClientHandler线程,广播消息需线程安全集合存储PrintWriter并及时移除断开连接;客户端需双线程收发,注意UTF-8编码与资源关闭。

用 ServerSocket 实现基础 TCP 聊天室服务器
Java 原生 ServerSocket 就够用,不需要 Spring Boot 或 Netty——只要支持多客户端连接、广播消息,就能跑通简易聊天室。核心是“一个服务端线程监听连接,每个客户端分配一个独立线程处理读写”,适合学习网络模型,也足够应付几十人小规模测试。
注意:这不是生产级方案(无心跳、无断线重连、无消息序列化),但能清晰看到 Socket、InputStream、OutputStream 如何协作。
- 服务端启动后,调用
serverSocket.accept()阻塞等待连接,每接受一个Socket就新建一个ClientHandler线程 - 所有已连接的
Socket输出流需存入线程安全集合(如Collections.synchronizedList(new ArrayList())) - 广播时遍历该集合,对每个
PrintWriter调用println()并flush(),否则客户端收不到消息 - 客户端断开时,必须从集合中移除对应
PrintWriter,否则后续广播会触发IOException: Broken pipe
public class ChatServer {
private static final List clients = Collections.synchronizedList(new ArrayList<>());
public static void main(String[] args) throws IOException {
try (ServerSocket serverSocket = new ServerSocket(8080)) {
System.out.println("Chat server started on port 8080");
while (true) {
Socket clientSocket = serverSocket.accept();
new ClientHandler(clientSocket).start();
}
}
}
static class ClientHandler extends Thread {
private final Socket socket;
private final BufferedReader in;
private final PrintWriter out;
ClientHandler(Socket socket) throws IOException {
this.socket = socket;
this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
this.out = new PrintWriter(socket.getOutputStream(), true);
}
public void run() {
try {
clients.add(out);
String msg;
while ((msg = in.readLine()) != null) {
System.out.println("Received: " + msg);
for (PrintWriter client : clients) {
client.println(msg); // 广播给所有人
}
}
} catch (IOException e) {
// 客户端异常断开,清理资源
clients.remove(out);
} finally {
try { socket.close(); } catch (IOException ignored) {}
}
}
}
}
客户端用 Socket 连接并收发纯文本
简易聊天室客户端只需一个 Socket、一个 Scanner(读控制台)、一个 BufferedReader(收服务端消息)。关键在于两个线程:一个负责读用户输入并发送,另一个负责监听服务端推送——不能串行阻塞。
- 发送线程用
System.in读取nextLine(),避免nextInt()后残留换行符导致下一次读取为空 - 接收线程用
BufferedReader.readLine()持续监听,遇到null表示服务端关闭连接 - 客户端退出前必须调用
socket.close(),否则服务端无法感知断开,clients列表持续膨胀 - 不要在
main线程里直接readLine()后再println(),会导致“自己发的消息自己收不到”(因为没启接收线程)
常见错误:中文乱码和连接拒绝
乱码基本是字符集不一致:InputStreamReader 默认用平台编码(Windows 是 GBK),而现代 IDE 和终端多用 UTF-8。必须显式指定:
立即学习“Java免费学习笔记(深入)”;
- 服务端构造
BufferedReader时用new InputStreamReader(socket.getInputStream(), "UTF-8") - 客户端同理,且
PrintWriter构造需加true参数启用自动 flush,并指定编码:new OutputStreamWriter(socket.getOutputStream(), "UTF-8") - “Connection refused” 错误只说明服务端没在目标端口监听:检查
ServerSocket是否已启动、端口是否被占用(netstat -an | grep 8080)、防火墙是否拦截 - 启动顺序必须是先运行服务端,再启动多个客户端;反向操作必然报错
扩展性差在哪?别急着重构
这个实现撑不过 100 个并发连接——每个客户端占一个线程,线程创建/切换开销大,JVM 线程数也有上限(默认约 1024)。但对理解“连接管理”“消息分发”“资源清理”已经足够。
真正容易被忽略的是:没有区分发送者身份。当前所有消息都是“匿名广播”,如果要显示“[Alice]: hello”,就得在服务端为每个 ClientHandler 记录昵称(比如首条消息作为用户名),并在广播时拼接前缀——这需要修改 clients 存储结构,改用 Map,且首次注册时做重复校验。










