Java实现局域网C/S群聊:服务端与客户端完整代码
本文将演示如何使用 Java 的 Socket 和网络 IO 流构建一个基础的局域网多人群聊应用。该系统基于 TCP 协议,采用客户端/服务器(C/S)架构,实现了用户上线通知、消息广播、断线检测及简单的黑名单屏蔽功能。
实现目标与技术准备
本实例旨在解决如何在本地网络环境中让多个终端进行文字聊天。核心技术点包括:
- TCP 连接管理:利用
ServerSocket监听端口,Socket处理具体通信链路。 - 并发处理:服务端需为每个连接的客户端分配独立线程,保证多人同时聊天不阻塞。
- 数据同步:使用
synchronized关键字确保共享的客户端列表在增删时的线程安全。 - I/O 流:结合
Scanner读取控制台输入,PrintStream发送数据,BufferedReader异步接收数据。
系统架构说明
系统由以下五个核心类组成:
Server:服务端入口,负责监听端口并维护在线客户端集合。Client:客户端入口,启动发送与接收两个子线程。ClientSend:负责将用户控制台输入的文本发送至服务端。ClientReceive:负责从服务端循环读取消息并打印到控制台。HandleSocket:服务端线程类,负责处理单个客户端的生命周期(上线、发消息、下线)及广播逻辑。
第一步:编写服务端 Server
服务端需要创建一个永久运行的循环,等待客户端连接。每当有新连接加入,将其加入同步列表,并启动一个 HandleSocket 线程来处理该客户端的具体业务。
package com.dragon.java.tcpchat;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* 服务器端主程序
*/
public class Server {
public static void main(String[] args) throws IOException,
InterruptedException {
// 使用 ArrayList 存储所有已连接的客户端 Socket
List<Socket> list = new ArrayList<>();
// 创建服务器端的套接字,监听 10000 端口
ServerSocket server = new ServerSocket(10000);
System.out.println("服务器已启动,等待客户端连接...");
while (true) {
// 阻塞等待客户端连接
Socket socket = server.accept();
// 将新连接的 socket 加入列表,操作需同步
synchronized (list) {
list.add(socket);
}
// 启动新线程处理该客户端的逻辑
new HandleSocket(socket, list).start();
}
}
}
第二步:编写客户端 Client
客户端连接到指定 IP 和端口后,不能关闭输出流和 Socket,否则无法进行双向通信。因此需要启动两个后台线程,分别负责发送和接收消息。
package com.dragon.java.tcpchat;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 客户端主程序
*/
public class Client {
public static void main(String args[]) throws UnknownHostException,
IOException {
// 连接到服务器,请修改 IP 为实际服务端地址
Socket client = new Socket("192.168.1.188", 10000);
// 启动发送线程
new ClientSend(client).start();
// 启动接收线程
new ClientReceive(client).start();
// 注意:聊天过程中保持长连接,不执行 shutdownOutput 或 close
}
}
第三步:实现客户端发送逻辑 ClientSend
该类继承自 Thread。它在构造函数中获取 Socket 引用,在 run 方法中包装 System.in 为 Scanner,逐行读取用户输入并写入 Socket 的输出流。
package com.dragon.java.tcpchat;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端发送信息的线程
*/
public class ClientSend extends Thread {
private Scanner scanner;
private Socket socket;
public ClientSend(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
scanner = new Scanner(System.in);
try {
// 获取输出流,用于发送数据
PrintStream ps = new PrintStream(socket.getOutputStream());
String line = "";
// 持续读取控制台输入
while ((line = scanner.nextLine()) != null) {
ps.println(line); // 发送一行消息
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
第四步:实现客户端接收逻辑 ClientReceive
此线程包裹 Socket 的输入流。由于是长期运行,它会在循环中调用 readLine() 阻塞等待服务端发来的消息。一旦有数据到达,即刻打印到屏幕。
package com.dragon.java.tcpchat;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* 客户端接收信息的线程
*/
public class ClientReceive extends Thread {
private Socket socket;
public ClientReceive(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 获取输入流,编码建议使用 UTF-8 或 GBK
BufferedReader br = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
String line = "";
// 持续读取服务端推送的消息
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
第五步:实现服务端核心处理类 HandleSocket
这是最关键的类。每个接入的用户都会生成一个 HandleSocket 实例。其主要职责包括:
1. 记录并提示用户上线。
2. 实现简单的 IP 黑名单过滤。
3. 循环读取该用户的消息,格式化后发送给其他所有人(广播)。
4. 捕获异常处理断连情况,并通知下线。
package com.dragon.java.tcpchat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.List;
/**
* 处理单个客户端连接的线程
*/
public class HandleSocket extends Thread {
private Socket socket;
private List<Socket> list;
public HandleSocket(Socket socket, List<Socket> list) {
this.socket = socket;
this.list = list;
}
/**
* 线程主体:处理上线、收发消息、下线流程
*/
@Override
public void run() {
// 获取客户端 IP
InetAddress address = socket.getInetAddress();
String ip = address.getHostAddress();
System.out.println(ip + " 上线了!");
// 示例:黑名单逻辑,此处仅作为演示,IP硬编码
if (ip.equals("192.168.1.117")) {
synchronized (list) {
sendToAll(ip + " 由于违规操作,已被拉入黑名单!");
list.remove(socket);
}
return; // 直接结束线程
}
try {
// 读取该客户端发来的数据,设置字符集防止乱码
BufferedReader br = new BufferedReader(new InputStreamReader(
socket.getInputStream(), "gbk"));
String line = "";
// 循环读取该用户的消息
while ((line = br.readLine()) != null) {
String msg = ip + ": " + line;
System.out.println(msg); // 服务器控制台显示
// 将该消息广播给其他所有客户端
sendToAll(msg);
}
} catch (IOException e) {
// 出现IO异常通常意味着连接断开
System.out.println(ip + " 掉线了!");
synchronized (list) {
list.remove(socket);
}
}
}
/**
* 将消息发送给除了自己以外的所有在线客户端
*/
private void sendToAll(String msg) {
synchronized (list) {
for (Socket s : list) {
if (s != socket) {
try {
PrintStream ps = new PrintStream(s.getOutputStream());
ps.println(msg); // 发送消息
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
排错与注意事项
- 启动顺序:务必先运行
Server.java,再运行Client.java。如果服务端未启动,客户端连接时会抛出ConnectException。 - IP 地址配置:示例代码中使用了固定 IP
192.168.1.188。请根据运行服务端电脑的局域网 IP 进行修改,或使用localhost进行单机测试。 - 字符编码:
HandleSocket中使用了 "gbk" 编码。如果是 Windows 系统默认中文环境通常兼容,若发现乱码可尝试改为 "UTF-8" 并保持客户端与服务端一致。 - 死锁风险:代码中大量使用了
synchronized(list)。在实际生产环境中,建议改用Collections.synchronizedList或CopyOnWriteArrayList以减少手动锁定的复杂度。 - 资源释放:示例为了简化逻辑,在循环内部并未显式关闭流。实际应用中应在客户端关闭或断连时妥善处理流资源。
总结
通过以上步骤,我们实现了一个具备基础广播功能的局域网聊天室。其核心在于利用 Java 的多线程机制隔离不同用户的 I/O 操作,并通过同步集合保证消息分发的原子性。以上就是 Java 实现小型局域网 C/S 群聊功能的详细内容,更多关于 Java 网络编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













