哈罗,大家好,今天教大家用原生java库实现一个超级简单的qq聊天。
这是一款简易的QQ软件,《我的QQ》软件需要采用采用图形界面编写,并具备如下基本功能:
1、新用户注册
在用户可以使用《我的QQ》软件之前需要注册成为系统的合法用户。在用户注册成功后,可以在登录界面登录。
2、用户登录
用户在可以向其他用户发送消息之前,需要登录进入系统。成功登录的用户,在其界面中显示所有用户的列表,同时,将所有其他用户在该用户不在线时发送给他/她的信息转发给他/她。
3、向其他用户发送信息
登录用户从界面中选择要发送信息的用户,然后输入要发送的信息,点击发送按钮后将输入的信息发送给选定的用户。如果目标用户在线,则将信息实时发送给目标用户,否则,如果目标用户不在线,你的程序将信息保存到数据库中,当目标用户登录后,应该将这些信息自动转发给目标用户。
程序采用Java技术、采用IntelliJ IDEA及MySQL数据库开发环境完成该项目的设计开发。
一共需要两张表:user表和message表。user表存储所有已注册用户信息,message表存储所有发送给未在线用户的信息。
字段名 | 字段数据类型 | 字段属性 | 备注 |
---|---|---|---|
_id | long | Not Null,主键,自增 | |
name | varchar(40) | Not null | 登录账号 |
password | varchar(40) | Not null | 密码 |
photo | blob | Nullable | 头像 |
reserved | varchar(200) | Nullable | 保留 |
字段名 | 字段数据类型 | 字段属性 | 备注 |
---|---|---|---|
_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函数:
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 , 这个是与数据库交互的工具,负责操作数据库的表。代码如下:
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处理的核心类。里面有消息协议处理,代码如下:
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类,代码如下:
public class Main {
public static void main(String[] args) {
// 使用SwingUtilities确保在事件分发线程中创建UI
SwingUtilities.invokeLater(() -> {
LoginFrame loginFrame = new LoginFrame();
loginFrame.setVisible(true);
});
}
}
LoginFrame为登录界面,有很多UI代码,我就不讲了,里面初始化了核心的socket连接,代码如下:
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的核心处理,连接服务器的代码如下:
/**
* 连接到服务器
* @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为开启消息处理线程 , 里面的消息协议处理如下:
/**
* 接收消息线程
*/
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 删除。