Java网络编程实现简易群聊系统:服务端与客户端代码解析

小石头
小石头
发布时间:2026-10-15 | 4人浏览过
原创

在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多线程通信的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表