Java网络编程实现简易群聊系统:服务端与客户端代码解析
在Java网络编程中,构建一个简易的群聊系统是理解TCP/IP通信、I/O流操作及多线程并发控制的经典实战案例。本文将提供完整的服务器端与客户端代码实现,涵盖连接建立、消息广播、私聊分发及资源释放等关键环节。
一、系统架构设计
群聊系统的核心在于“一对多”的消息分发机制。我们采用基于TCP的Socket编程模型,分为服务器端(Server)负责接收所有客户端连接并维护在线用户列表,以及客户端(Client)负责用户的输入输出交互。为了处理高并发场景下的用户数据变更,服务端将使用线程安全的集合容器来存储活跃的聊天会话对象。
二、服务端实现:连接管理与消息路由
服务端是群聊的核心枢纽,主要职责包括监听端口、接受新连接、初始化用户会话以及将消息分发给其他在线用户。
第1步:创建服务器主入口
目的:启动监听服务,维持系统运行。
参数:指定监听端口号(如 9999)。
代码逻辑:
package networkCoding;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;
public class ChatRoomServer {
// 使用线程安全列表存储所有在线的Chat会话对象
private static CopyOnWriteArrayList all = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws IOException {
System.out.println("----- Server Started -----");
// 1. 绑定端口创建ServerSocket
ServerSocket server = new ServerSocket(9999);
// 2. 循环阻塞等待客户端连接
while(true) {
Socket client = server.accept();
// 为每个连接创建一个独立的Chat处理对象
Chat chat = new Chat(client);
// 加入容器统一管理
all.add(chat);
// 启动新线程处理该客户端的数据收发
new Thread(chat).start();
}
}
}
第2步:封装单客户端会话处理逻辑
目的:定义单个用户从连接、认证、收发消息到离线的完整生命周期。
关键动作:
- 初始化:构造方法中获取输入输出流,读取用户名并向其发送欢迎语。
- 广播与私聊:根据消息格式判断是普通群发还是针对特定用户的私聊(以
@开头)。 - 异常处理:捕获IOException进行资源释放,并从列表中移除掉线用户。
static class Chat implements Runnable {
private DataOutputStream dos;
private DataInputStream dis;
private Socket client;
private boolean isRuning = true;
private String name;
public Chat(Socket client) {
this.client = client;
try {
// 初始化流
this.dis = new DataInputStream(client.getInputStream());
this.dos = new DataOutputStream(client.getOutputStream());
// 读取用户名称
this.name = receiveName();
// 发送欢迎信息给新用户
send(this.name + "加入了房间");
// 通知其他用户有人上线
broadcastOthers(this.name + "加入了房间", true);
} catch (IOException e) {
release();
}
}
private String receiveName() {
try {
return dis.readUTF();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
// 向当前客户端发送消息
private void send(String data) {
try {
dos.writeUTF(data);
dos.flush();
} catch (IOException e) {
release();
}
}
// 向除自己以外的所有用户发送消息
private void broadcastOthers(String data, boolean isSys) {
for (Chat other : all) {
if (other == this) continue;
if (isSys) {
other.send(data);
} else {
other.send(this.name + "对大家说:" + data);
}
}
}
private void release() {
this.isRuning = false;
Utils.close(dis, dos, client);
all.remove(this);
try {
broadcastOthers(this.name + "离开了房间", true);
} catch (Exception ignored) {}
}
@Override
public void run() {
while(isRuning) {
String data = receiveData();
if (!data.isEmpty()) {
broadcastOthers(data, false);
} else {
send("不能发送空消息");
}
}
}
private String receiveData() {
try {
return dis.readUTF();
} catch (IOException e) {
release();
}
return "";
}
}
三、客户端实现:双线程处理收发分离
为了防止控制台读取输入时阻塞网络数据接收,客户端必须采用多线程架构:一条线程专门负责从键盘读取内容并发送,另一条线程专门负责监听服务端推送的消息并打印。
第1步:客户端主程序连接
入口:新建 Socket 指向 localhost:9999。
操作:提示用户输入姓名,随后启动发送与接收线程。
public class ChatRoomClient {
public static void main(String[] args) throws IOException {
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入姓名:");
String userName = bf.readLine();
// 建立连接
Socket client = new Socket("localhost", 9999);
// 启动发送线程
new Thread(new SendTask(client, userName)).start();
// 启动接收线程
new Thread(new ReceiveTask(client)).start();
}
}
第2步:发送任务封装类
负责循环读取用户输入并通过 DataOutputStream 发送UTF-8字符串。若输入为空,则忽略发送。
class SendTask implements Runnable {
private BufferedReader bf;
private DataOutputStream dos;
private Socket client;
private boolean isRuning = true;
public SendTask(Socket client, String name) {
this.client = client;
this.bf = new BufferedReader(new InputStreamReader(System.in));
try {
this.dos = new DataOutputStream(client.getOutputStream());
// 先发送用户名进行身份标识
dos.writeUTF(name);
} catch (IOException e) {
isRuning = false;
Utils.close(dos, client);
}
}
@Override
public void run() {
while(isRuning) {
String input = "";
try {
input = bf.readLine();
} catch (IOException e) {
break;
}
if (input != null && !input.isEmpty()) {
try {
dos.writeUTF(input);
dos.flush();
} catch (IOException e) {
isRuning = false;
Utils.close(dos, client);
}
}
}
}
}
第3步:接收任务封装类
负责持续监听服务端下发的消息并打印到控制台,直到连接关闭或发生IO异常。
class ReceiveTask implements Runnable {
private DataInputStream dis;
private Socket client;
private boolean isRuning = true;
public ReceiveTask(Socket client) {
this.client = client;
try {
this.dis = new DataInputStream(client.getInputStream());
} catch (IOException e) {
isRuning = false;
Utils.close(dis, client);
}
}
@Override
public void run() {
while(isRuning) {
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
isRuning = false;
Utils.close(dis, client);
break;
}
if (!msg.isEmpty()) {
System.out.println(msg);
}
}
}
}
四、工具类辅助
提供通用的资源关闭工具方法,利用可变参数优雅地处理多个可关闭对象的释放,减少重复代码。
public class Utils {
public static void close(Closeable... targets) {
for (Closeable obj : targets) {
if (obj != null) {
try {
obj.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
五、总结
通过上述代码,我们构建了一个具备基础功能的Java TCP群聊系统。其中,CopyOnWriteArrayList 保证了用户列表在并发读写时的安全性,而多线程模型有效解决了控制台I/O与网络I/O的冲突。虽然此示例较为简化,但它涵盖了网络编程中Socket、Stream及Thread的核心应用模式。
以上就是Java网络编程之群聊功能实现的详细内容,更多关于Java Socket多线程通信的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









