首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java+Swing实现仿QQ聊天源码+Socket编程实际案例

Java+Swing实现仿QQ聊天源码+Socket编程实际案例

原创
作者头像
家庭Q秋-3993387644
发布2025-06-19 13:00:59
发布2025-06-19 13:00:59
14100
代码可运行
举报
运行总次数:0
代码可运行

哈罗,大家好,今天教大家用原生java库实现一个超级简单的qq聊天。

程序基本需求

这是一款简易的QQ软件,《我的QQ》软件需要采用采用图形界面编写,并具备如下基本功能:

1、新用户注册

在用户可以使用《我的QQ》软件之前需要注册成为系统的合法用户。在用户注册成功后,可以在登录界面登录。

2、用户登录

用户在可以向其他用户发送消息之前,需要登录进入系统。成功登录的用户,在其界面中显示所有用户的列表,同时,将所有其他用户在该用户不在线时发送给他/她的信息转发给他/她。

3、向其他用户发送信息

登录用户从界面中选择要发送信息的用户,然后输入要发送的信息,点击发送按钮后将输入的信息发送给选定的用户。如果目标用户在线,则将信息实时发送给目标用户,否则,如果目标用户不在线,你的程序将信息保存到数据库中,当目标用户登录后,应该将这些信息自动转发给目标用户。

运行环境

程序采用Java技术、采用IntelliJ IDEA及MySQL数据库开发环境完成该项目的设计开发。

演示视频

图片演示

数据库表设计

一共需要两张表:user表和message表。user表存储所有已注册用户信息,message表存储所有发送给未在线用户的信息。

user表

字段名

字段数据类型

字段属性

备注

_id

long

Not Null,主键,自增

name

varchar(40)

Not null

登录账号

password

varchar(40)

Not null

密码

photo

blob

Nullable

头像

reserved

varchar(200)

Nullable

保留

message表

字段名

字段数据类型

字段属性

备注

_id

long

Not Null,主键,自增

sender

long

外键关联user的_id,Not Null

发送者的id

receiver

long

外键关联user的_id,Not Null

接收者的id

message

varchar(200)

Not Null

消息

ddate

date

Not Null

发送日期

源码实现设计

服务端设计

服务端为QQSystem目录, QQServer 为启动函数类,找到main函数:

代码语言:javascript
代码运行次数:0
运行
复制
public static void main(String[] args) {
        System.out.println("启动QQ服务器...");
        DatabaseManager dbManager = null;
        ExecutorService executor = Executors.newCachedThreadPool();

        try {
            System.out.println("初始化数据库连接...");
            dbManager = new DatabaseManager();

            // 启动服务器
            try (ServerSocket serverSocket = new ServerSocket(PORT)) {
                System.out.println("QQ服务器已启动,监听端口: " + PORT);

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

                    // 为每个客户端创建处理线程
                    ClientHandler handler = new ClientHandler(clientSocket, dbManager);
                    executor.execute(handler);
                }
            }
        } catch (IOException | SQLException | ClassNotFoundException e) {
            System.err.println("服务器启动失败: " + e.getMessage());
            e.printStackTrace();
        } finally {
            if (dbManager != null) {
                try {
                    dbManager.close();
                } catch (SQLException e) {
                    System.err.println("关闭数据库连接失败: " + e.getMessage());
                }
            }
            executor.shutdown();
            System.out.println("服务器已关闭");
        }
    }

首先初始化了 DatabaseManager , 这个是与数据库交互的工具,负责操作数据库的表。代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
package com.myqq.server;


import com.myqq.constent.Constants;
import com.myqq.model.Message;
import com.myqq.model.User;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class DatabaseManager implements AutoCloseable {


    private Connection connection;

    public DatabaseManager() throws SQLException, ClassNotFoundException {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            System.out.println("尝试连接数据库: " + Constants.DB_URL);
            connection = DriverManager.getConnection(Constants.DB_URL, Constants.DB_USER, Constants.DB_PASSWORD);
            System.out.println("数据库连接成功");
        } catch (ClassNotFoundException e) {
            System.err.println("找不到MySQL驱动: " + e.getMessage());
            throw e;
        } catch (SQLException e) {
            System.err.println("数据库连接失败: " + e.getMessage());
            throw e;
        }
    }

    // 用户认证
    public User authenticateUser(String username, String password) throws SQLException {
        String sql = "SELECT * FROM user WHERE name = ? AND password = ?";
        try (PreparedStatement stmt = connection.prepareStatement(sql)) {
            stmt.setString(1, username);
            stmt.setString(2, password);
            ResultSet rs = stmt.executeQuery();

            if (rs.next()) {
                return new User(
                        rs.getLong("_id"),
                        rs.getString("name"),
                        rs.getString("password"),
                        rs.getString("photo")
                );
            }
            return null;
        }
    }

    // 获取所有用户
    public List<User> getAllUsers() throws SQLException {
        List<User> users = new ArrayList<>();
        String sql = "SELECT * FROM user";
        try (Statement stmt = connection.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {
            while (rs.next()) {
                users.add(new User(
                        rs.getLong("_id"),
                        rs.getString("name"),
                        null, // 不返回密码
                        rs.getString("photo")
                ));
            }
        }
        return users;
    }

    // 获取未读消息
    public List<Message> getUnreadMessages(long userId) throws SQLException {
        List<Message> messages = new ArrayList<>();
        String sql = "SELECT * FROM message WHERE receiver = ? AND `read` = 2";
        try (PreparedStatement stmt = connection.prepareStatement(sql)) {
            stmt.setLong(1, userId);
            ResultSet rs = stmt.executeQuery();

            while (rs.next()) {
                messages.add(new Message(
                        rs.getLong("_id"),        // id
                        rs.getLong("sender"),     // sender
                        rs.getLong("receiver"),   // receiver
                        rs.getString("message"),  // content
                        rs.getTimestamp("ddate"), // date
                        rs.getInt("read")         // status
                ));
            }
        }
        return messages;
    }

    // 标记消息为已读
    public void markMessageAsRead(long messageId) throws SQLException {
        String sql = "UPDATE message SET `read` = 1 WHERE _id = ?";
        try (PreparedStatement stmt = connection.prepareStatement(sql)) {
            stmt.setLong(1, messageId);
            stmt.executeUpdate();
        }
    }

    // 保存消息到数据库
    public void saveMessage(Message msg) throws SQLException {
        String sql = "INSERT INTO message (sender, receiver, message, ddate, `read`) VALUES (?, ?, ?, ?, ?)";
        try (PreparedStatement stmt = connection.prepareStatement(sql)) {
            stmt.setLong(1, msg.getSender());
            stmt.setLong(2, msg.getReceiver());
            stmt.setString(3, msg.getContent());
            stmt.setTimestamp(4, msg.getDate());
            stmt.setInt(5, msg.getStatus());
            stmt.executeUpdate();
        }
    }

    // 注册新用户
    public boolean registerUser(User newUser) throws SQLException {
        // 检查用户名是否已存在
        String checkSql = "SELECT COUNT(*) FROM user WHERE name = ?";
        try (PreparedStatement checkStmt = connection.prepareStatement(checkSql)) {
            checkStmt.setString(1, newUser.getName());
            ResultSet rs = checkStmt.executeQuery();
            if (rs.next() && rs.getInt(1) > 0) {
                return false; // 用户名已存在
            }
        }

        // 插入新用户
        String insertSql = "INSERT INTO user (name, password, photo) VALUES (?, ?, ?)";
        try (PreparedStatement insertStmt = connection.prepareStatement(insertSql)) {
            insertStmt.setString(1, newUser.getName());
            insertStmt.setString(2, newUser.getPassword());

            if (newUser.getPhoto() != null && !newUser.getPhoto().isEmpty()) {
                insertStmt.setString(3, newUser.getPhoto());
            } else {
                insertStmt.setNull(3, Types.BLOB);
            }

            int rowsAffected = insertStmt.executeUpdate();
            return rowsAffected > 0;
        }
    }

    // 关闭连接
    @Override
    public void close() throws SQLException {
        if (connection != null) {
            connection.close();
            System.out.println("数据库连接已关闭");
        }
    }
}

ClientHandler 类 为每个客户端socket处理的核心类。里面有消息协议处理,代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
private void handleCommand(String commandStr) throws IOException, SQLException {
        SocketMsg msg = null;
        try {
            msg = JSON.parseObject(commandStr, SocketMsg.class);
        } catch (Exception e) {
            return;
        }
        String command = msg.getType();

        if ("REGISTER".equals(command)) {
            // 注册
            handleRegistration(msg);
        }

        if("LOGIN".equals(command)) {
            //登录
            handleLogin(msg);
        }

        if("USERLIST".equals(command)) {
            //在线列表
            List<User> allUsers = dbManager.getAllUsers();
            Set<Long> longs = QQServer.onlineUsers.keySet();
            //提取在线的人
            for (User user : allUsers) {
                if(longs.contains(Long.parseLong(user.getId()+""))){
                    user.setOnline(true);
                }

                System.out.println("好友列表: " + user.getName());
            }
            SocketMsg socketMsg = new SocketMsg();
            socketMsg.setType("USERLIST");
            socketMsg.setData(allUsers);

            writer.println(JSON.toJSONString(socketMsg));
        }

        if("MSG".equals(command)){
            // 转发消息
            handleMessage(msg);
        }

    }

客户端设计

客户端在 QQSystem2目录下, 找到Main类,代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
public class Main {
    public static void main(String[] args) {
        // 使用SwingUtilities确保在事件分发线程中创建UI
        SwingUtilities.invokeLater(() -> {
            LoginFrame loginFrame = new LoginFrame();
            loginFrame.setVisible(true);
        });
    }
}

LoginFrame为登录界面,有很多UI代码,我就不讲了,里面初始化了核心的socket连接,代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
private void connectToServer() {
        statusLabel.setText("正在连接服务器...");
        statusLabel.setForeground(Color.BLUE);

        new Thread(() -> {
            try {
                if (qqClient == null) {
                    qqClient = new QQClient("localhost", 8888);
                    qqClient.setLoginListener(this);

                    // 设置连接状态监听器
                    qqClient.setConnectionListener(new QQClient.ConnectionListener() {
                        @Override
                        public void onConnected() {
                            SwingUtilities.invokeLater(() -> {
                                statusLabel.setText("状态: 已连接");
                                statusLabel.setForeground(Color.GREEN);
                            });
                        }

                        @Override
                        public void onDisconnected() {
                            SwingUtilities.invokeLater(() -> {
                                statusLabel.setText("状态: 已断开");
                                statusLabel.setForeground(Color.RED);
                            });
                        }

                        @Override
                        public void onConnectionError(String error) {
                            SwingUtilities.invokeLater(() -> {
                                statusLabel.setText("错误: " + error);
                                statusLabel.setForeground(Color.RED);
                            });
                        }
                    });
                }

                if (!qqClient.isConnected()) {
                    qqClient.connect();
                }
            } catch (IOException e) {
                SwingUtilities.invokeLater(() -> {
                    statusLabel.setText("连接失败: " + e.getMessage());
                    statusLabel.setForeground(Color.RED);
                });
            }
        }).start();
    }

QQClient 为socket的核心处理,连接服务器的代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
/**
     * 连接到服务器
     * @throws IOException 如果连接失败
     */
    public void connect() throws IOException {
        if (connected.get()) {
            disconnect();
        }

        try {
            // 创建新socket并设置连接超时
            socket = new Socket();
            socket.connect(new InetSocketAddress(serverAddress, serverPort), CONNECT_TIMEOUT);

            // 设置读取超时
            socket.setSoTimeout(READ_TIMEOUT);

//            // 创建对象流 - 注意顺序:先创建输出流并刷新头部
            writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
            //接收服务端返回数据流
            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            connected.set(true);

            // 启动接收消息线程
            startReceiverThread();

            // 启动心跳检测线程
            startHeartbeatThread();

            // 通知连接成功
            if (connectionListener != null) {
                connectionListener.onConnected();
            }

            System.out.println("成功连接到服务器: " + serverAddress + ":" + serverPort);
        } catch (IOException e) {
            connected.set(false);
            cleanupResources();
            throw e;
        }
    }

startReceiverThread为开启消息处理线程 , 里面的消息协议处理如下:

代码语言:javascript
代码运行次数:0
运行
复制
/**
     * 接收消息线程
     */
    private void receiveMessages() {
        System.out.println("开始接收消息线程");
        while (connected.get()) {
            try {
                String input = null;
                while ((input = bufferedReader.readLine()) != null) {
                    System.out.println("收到服务器消息: " + input);

                    SocketMsg socketMsg = JSON.parseObject(input, SocketMsg.class);
                    /// 注册消息
                    if (socketMsg.getType().equals("REGISTER")) {
                        // 注册
                        registerListener.onMessageReceived(socketMsg);
                    }

                    // 登录
                    if (socketMsg.getType().equals("LOGIN")) {
                        loginListener.onMessageReceived(socketMsg);
                    }

                    // 在线列表
                    if(socketMsg.getType().equals("USERLIST")) {
                        userListListener.onUserListUpdated(socketMsg);
                    }

                    // 其他用户上线
                    if(socketMsg.getType().equals("USER_ONLIE")) {
                        userOnlineListener.onUserOnlineUpdated(socketMsg);
                    }

                    // 消息转发
                    if(socketMsg.getType().equals("MSG")) {
                        messageListener.onMessageReceived(socketMsg);
                    }

                }
            }catch (SocketTimeoutException e) {
            }catch (Exception e) {
                e.printStackTrace();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e2) {
                }
                try {
                    reconnect();
                } catch (IOException ex) {
                }
            }
        }

源码部署

将服务端QQSystem和客户端QQSystem2直接导入到idea里面, 设置好maven即可。不会的可以自己百度搜索“idea导入普通maven项目流程” 。

运行服务端:

待项目编译好之后,找到QQSystem项目里面的 QQServer类,直接右键运行即可,下面图示表示运行成功:

运行客户端:

待项目编译好之后,找到QQSystem1项目里面的Main ,Main2类,分别直接右键运行即可,下面图示表示运行成功:

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 程序基本需求
  • 运行环境
  • 演示视频
  • 图片演示
  • 数据库表设计
    • user表
    • message表
  • 源码实现设计
  • 服务端设计
  • 客户端设计
  • 源码部署
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档