diff --git a/IM Porject/IM.mp4 b/IM Porject/IM.mp4 new file mode 100644 index 0000000..0b475c6 Binary files /dev/null and b/IM Porject/IM.mp4 differ diff --git a/IM Porject/IM/IM.pro b/IM Porject/IM/IM.pro new file mode 100644 index 0000000..2605eef --- /dev/null +++ b/IM Porject/IM/IM.pro @@ -0,0 +1,51 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2018-11-30T17:54:04 +# +#------------------------------------------------- + +QT += core gui network sql +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = IM +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +CONFIG += c++11 + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + imclient.cpp \ + formlogin.cpp \ + imdal.cpp + +HEADERS += \ + mainwindow.h \ + imclient.h \ + protocol.h \ + formlogin.h \ + immessage.h \ + imdal.h + +FORMS += \ + mainwindow.ui \ + formlogin.ui + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +RESOURCES += \ + resources.qrc diff --git a/IM Porject/IM/IM.pro.user b/IM Porject/IM/IM.pro.user new file mode 100644 index 0000000..b113cee --- /dev/null +++ b/IM Porject/IM/IM.pro.user @@ -0,0 +1,322 @@ + + + + + + EnvironmentId + {2d03fc4c-541a-4fa5-8f5c-ae92a1998dce} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + -fno-delayed-template-parsing + + true + + + + ProjectExplorer.Project.Target.0 + + Desktop Qt 5.11.2 MinGW 32bit + Desktop Qt 5.11.2 MinGW 32bit + qt.qt5.5112.win32_mingw53_kit + 0 + 0 + 0 + + E:/C++/Qt/build-IM-Desktop_Qt_5_11_2_MinGW_32bit-Debug + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + true + + + E:/C++/Qt/build-IM-Desktop_Qt_5_11_2_MinGW_32bit-Release + + + true + qmake + + QtProjectManager.QMakeBuildStep + false + + false + false + true + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + + E:/C++/Qt/build-IM-Desktop_Qt_5_11_2_MinGW_32bit-Profile + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + true + true + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + 3 + + + 0 + 部署 + + ProjectExplorer.BuildSteps.Deploy + + 1 + Deploy Configuration + + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + IM + + Qt4ProjectManager.Qt4RunConfiguration:E:/C++/Qt/IM/IM.pro + true + + IM.pro + + E:/C++/Qt/build-IM-Desktop_Qt_5_11_2_MinGW_32bit-Debug + 3768 + false + true + false + false + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 18 + + + Version + 18 + + diff --git a/IM Porject/IM/formlogin.cpp b/IM Porject/IM/formlogin.cpp new file mode 100644 index 0000000..dfbc07a --- /dev/null +++ b/IM Porject/IM/formlogin.cpp @@ -0,0 +1,128 @@ +#include "formlogin.h" +#include "ui_formlogin.h" +#include +#include "mainwindow.h" + +FormLogin::FormLogin(QWidget *parent) : + QWidget(parent), + ui(new Ui::FormLogin) +{ + ui->setupUi(this); + connect(IMClient::instance(), &IMClient::serverConnected, this, &FormLogin::serverConnected); + connect(IMClient::instance(), &IMClient::serverClose, this, &FormLogin::serverClose); + connect(IMClient::instance(), &IMClient::loginResult, this, &FormLogin::loginResult); + connect(IMClient::instance(), &IMClient::connectError, this, &FormLogin::connectError); + // 连接服务器 + IMClient::instance()->connectServer(ui->leHostName->text().trimmed()); +} + +FormLogin::~FormLogin() +{ + delete ui; +} + +// 连接成功后将重试按钮什么的隐藏掉 +void FormLogin::serverConnected() +{ + ui->lblServerConnectState->clear(); + ui->btnRetry->setHidden(true); + ui->leHostName->setHidden(true); +} + +// 如果服务器关闭,那么程序跟着关闭 +void FormLogin::serverClose() +{ + QMessageBox::warning(this, "警告", "服务器已关闭,程序即将退出!"); + QCoreApplication::quit(); +} + +// 如果连接发生错误,提示并将重试按钮显示 +void FormLogin::connectError(QString ErrorInfo) +{ + QMessageBox::critical(this, "连接失败", "错误信息:" + ErrorInfo); + ui->lblServerConnectState->setText("服务器连接失败!"); + ui->btnRetry->setHidden(false); + ui->leHostName->setHidden(false); +} + +// 当登录有结果时 +void FormLogin::loginResult(bool isSuccess) +{ + // 如果登录没有成功,将登录按钮设置为可用,并提示用户 + if (!isSuccess) + { + QMessageBox::critical(this, "登录失败", "该用户名或连接已经登录!"); + ui->btnLogin->setText("登 录"); + ui->btnLogin->setEnabled(true); + ui->lineEdit->setEnabled(true); + return; + } + // 登录成功,启动主窗体 + MainWindow *w = new MainWindow; + w->show(); + this->close(); +} + +// 登录按钮按下时 +void FormLogin::on_btnLogin_clicked() +{ + // 获取用户名 + QString username = ui->lineEdit->text().trimmed(); + if (username.isEmpty()) + { + QMessageBox::critical(this, "错误", "请输入用户名!"); + return; + } + // 检测是否存在非法字符 + bool isSpace = false; + for (int i = 0; i < username.length(); ++i) + { + if (username[i].isSpace() + || username[i] == ':' + || username[i] == '"' + || username[i] == '/' + || username[i] == '*' + || username[i] == '|' + || username[i] == '?' + || username[i] == '<' + || username[i] == '>') + { + isSpace = true; + break; + } + } + if (isSpace) + { + QMessageBox::critical(this, "错误", "用户名中有非法字符!"); + return; + } + + if (!IMClient::instance()->isOpen()) + { + QMessageBox::critical(this, "错误", "未连接服务器!"); + return; + } + + // 尝试登录 + IMClient::instance()->login(username); + ui->btnLogin->setText("登录中..."); + ui->btnLogin->setEnabled(false); + ui->lineEdit->setEnabled(false); +} + +// 当重试按钮按下时 +void FormLogin::on_btnRetry_clicked() +{ + // 如果连接已经打开了,就直接隐藏按钮 + if (IMClient::instance()->isOpen()) + { + ui->lblServerConnectState->clear(); + ui->btnRetry->setHidden(true); + ui->leHostName->setHidden(true); + } + else + { + // 否则尝试重新连接服务器 + IMClient::instance()->connectServer(ui->leHostName->text().trimmed()); + } +} diff --git a/IM Porject/IM/formlogin.h b/IM Porject/IM/formlogin.h new file mode 100644 index 0000000..117cc5d --- /dev/null +++ b/IM Porject/IM/formlogin.h @@ -0,0 +1,39 @@ +#ifndef FORMLOGIN_H +#define FORMLOGIN_H + +#include + +namespace Ui { +class FormLogin; +} + +class FormLogin : public QWidget +{ + Q_OBJECT + +public: + explicit FormLogin(QWidget *parent = nullptr); + ~FormLogin(); +// 槽 +private slots: + // 服务器连接成功时触发 + void serverConnected(); + // 服务器关闭时触发 + void serverClose(); + // 连接发生错误时触发 + void connectError(QString ErrorInfo); + + // 登录结果 + // 参数:isSuccess 是否登录成功 + void loginResult(bool isSuccess); + + + void on_btnLogin_clicked(); + + void on_btnRetry_clicked(); + +private: + Ui::FormLogin *ui; +}; + +#endif // FORMLOGIN_H diff --git a/IM Porject/IM/formlogin.ui b/IM Porject/IM/formlogin.ui new file mode 100644 index 0000000..a3522de --- /dev/null +++ b/IM Porject/IM/formlogin.ui @@ -0,0 +1,130 @@ + + + FormLogin + + + + 0 + 0 + 400 + 300 + + + + + 0 + 0 + + + + + 400 + 300 + + + + + 400 + 300 + + + + IM + + + + + 90 + 120 + 41 + 16 + + + + 用户名: + + + + + + 110 + 170 + 181 + 31 + + + + 登 录 + + + + + + 0 + 280 + 131 + 21 + + + + 服务器连接状态:未连接 + + + + + + 140 + 280 + 41 + 20 + + + + 重试 + + + + + + 180 + 40 + 31 + 41 + + + + + 20 + + + + IM + + + + + + 140 + 120 + 151 + 20 + + + + + + + 190 + 280 + 151 + 20 + + + + 127.0.0.1 + + + + + + diff --git a/IM Porject/IM/images/group.png b/IM Porject/IM/images/group.png new file mode 100644 index 0000000..5fec946 Binary files /dev/null and b/IM Porject/IM/images/group.png differ diff --git a/IM Porject/IM/images/userOffline.png b/IM Porject/IM/images/userOffline.png new file mode 100644 index 0000000..85610ae Binary files /dev/null and b/IM Porject/IM/images/userOffline.png differ diff --git a/IM Porject/IM/images/userOnline.png b/IM Porject/IM/images/userOnline.png new file mode 100644 index 0000000..ba2697f Binary files /dev/null and b/IM Porject/IM/images/userOnline.png differ diff --git a/IM Porject/IM/imclient.cpp b/IM Porject/IM/imclient.cpp new file mode 100644 index 0000000..3ac433e --- /dev/null +++ b/IM Porject/IM/imclient.cpp @@ -0,0 +1,223 @@ +#include +#include "imclient.h" +#include "protocol.h" +#include "imdal.h" + +IMClient::IMClient(QObject *parent) + : QObject(parent), + m_socket(new QTcpSocket) +{ + connect(m_socket, &QTcpSocket::connected, this, &IMClient::connected); + connect(m_socket, &QTcpSocket::readyRead, this, &IMClient::readyread); + connect(m_socket, &QTcpSocket::disconnected, this, &IMClient::disconnected); + connect(m_socket, QOverload::of(&QAbstractSocket::error), + [this](QAbstractSocket::SocketError socketError){this->error(socketError);}); +} + +IMClient *IMClient::instance() +{ + static IMClient imClient; + return &imClient; +} + +IMClient::~IMClient() +{ + qDebug() << "~IMClient"; + if (m_socket != nullptr) + { + m_socket->close(); + delete m_socket; + } +} + +// 连接服务器 +void IMClient::connectServer(QString hostName) +{ + m_socket->connectToHost(hostName, 9876, QTcpSocket::ReadWrite); +} + +// 登录 +void IMClient::login(QString name) +{ + this->m_name = name; + this->sendData(QString("%1 %2").arg(ClientFunctionCode::Login).arg(name)); +} + +// 发送私聊消息 +void IMClient::sendPrivateMessage(QString toName, QString content) +{ + // 发送消息到服务器 + this->sendData(QString("%1 %2 %3").arg(ClientFunctionCode::SendPrivateMessage).arg(toName).arg(content)); + // 构造消息对象,用o表示是发出消息 + IMMessage msg("o", content, QDateTime::currentDateTime()); + // 将消息添加到聊天记录中 + this->m_chatRecord[toName].append(msg); + // 将消息插入到数据库中 + IMDAL::instance()->addPrivateMessage(toName, msg); +} + +// 发送群聊消息 +void IMClient::sendGroupMessage(QString content) +{ + // 发送消息到服务器 + this->sendData(QString("%1 %2").arg(ClientFunctionCode::SendGroupMessage).arg(content)); + // 构造消息对象 + IMMessage msg(this->m_name, content, QDateTime::currentDateTime()); + // 将消息添加到聊天记录中 + this->m_gruopChatRecord.append(msg); + // 将消息插入到数据库中 + IMDAL::instance()->addGroupMessage(msg); +} + +const QVector *IMClient::getChatRecord(QString name) +{ + if (!this->m_chatRecord.contains(name)) + this->m_chatRecord[name] = IMDAL::instance()->getPrivateMessage(name); + + return &this->m_chatRecord[name]; +} + +// 连接成功时触发 +void IMClient::connected() +{ + qDebug() << "connected"; + emit serverConnected(); +} + +// 当接收到数据时触发 +void IMClient::readyread() +{ + // 获取接收到数据的对象 + QTcpSocket* sender = static_cast(QObject::sender()); + // 获取数据 + QByteArray data = sender->readAll(); + QTextStream in(data, QIODevice::ReadOnly); + qDebug() << "readyread:" << data; + + // 进行协议分析与任务调度 + int functionID = 0; + in >> functionID; + switch (functionID) { + case ServerFunctionCode::PrivateMessage: + { + // 如果是私聊消息,获取发送者昵称,然后发出获取到私聊消息的信号 + QString fromName; + in >> fromName; + in.seek(in.pos() + 1); + // 构造一个消息对象,发送者用'i'表示是接受到的消息 + IMMessage msg("i", in.readAll(), QDateTime::currentDateTime()); + // 将它添加到聊天记录中 + this->m_chatRecord[fromName].append(msg); + // 并且插入数据库 + IMDAL::instance()->addPrivateMessage(fromName, msg); + // 然后将发送者改回原来的名称 + msg.fromName = fromName; + // 通过信号将其传递出去 + emit receivedPrivateMessage(msg); + }break; + case ServerFunctionCode::GroupMessage: + { + // 如果是群聊消息,获取发送者昵称,然后发出获取到群聊消息的信号 + QString fromName; + in >> fromName; + in.seek(in.pos() + 1); + // 构造一个消息对象 + IMMessage msg(fromName, in.readAll(), QDateTime::currentDateTime()); + // 添加到聊天记录中 + this->m_gruopChatRecord.append(msg); + // 添加到数据库中 + IMDAL::instance()->addGroupMessage(msg); + // 发出信号 + emit receivedGroupMessage(msg); + }break; + case ServerFunctionCode::UserOnline: + { + // 如果是用户上线,获取发送者昵称,然后发出用户上线信号 + QString fromName; + in >> fromName; + if (m_offline.contains(fromName)) + m_offline.removeOne(fromName); + m_online.append(fromName); + emit userOnline(fromName); + }break; + case ServerFunctionCode::UserOffline: + { + // 如果是用户下线,获取发送者昵称,然后发出用户下线信号 + QString fromName; + in >> fromName; + if (m_online.contains(fromName)) + m_online.removeOne(fromName); + m_offline.append(fromName); + emit userOffline(fromName); + }break; + case ServerFunctionCode::LoginResult: + { + // 如果是登录有结果了,先获取登录结果 + int result = 0; + in >> result; + if (result == 0) + { + // 如果登录成功了,获取当前在线人数 + int n = 0; + in >> n; + QVector names; + QString name; + for (int i = 0; i < n; i++) + { + in >> name; + names.push_back(name); + } + this->m_online = names; + + // 初始化数据库 + IMDAL::instance()->initDatabase(this->m_name); + // 获取数据库中有聊天记录的用户名列表 + QVector userList = IMDAL::instance()->getUserList(); + // 将没上线的用户名添加到离线列表中 + for (int i = 0; i < userList.length(); i++) + { + // 如果这个名字在在线列表中不存在同时又不是自己 + if (!this->m_online.contains(userList[i]) && userList[i] != this->m_name) + { + // 就添加到离线列表中 + this->m_offline.append(userList[i]); + } + } + // 初始化群聊消息 + this->m_gruopChatRecord = IMDAL::instance()->getGroupMessage(); + // 最后发送登录成功消息 + emit loginResult(true); + } + else + { + // 否则就是登录失败了,发送登录失败的消息 + emit loginResult(false); + } + }break; + default: + return; + } +} + +// 当连接断开时触发 +void IMClient::disconnected() +{ + qDebug() << "disconnected"; + // 发送服务器关闭信号 + emit serverClose(); +} + +// 当连接发生错误时触发 +void IMClient::error(QAbstractSocket::SocketError socketError) +{ + qDebug() << socketError; + // 发出连接错误信号 + emit connectError(this->m_socket->errorString()); +} + +// 向服务器发送数据方法 +void IMClient::sendData(QString data) +{ + if (this->isOpen()) + this->m_socket->write(data.toUtf8()); +} diff --git a/IM Porject/IM/imclient.h b/IM Porject/IM/imclient.h new file mode 100644 index 0000000..d9fc0bf --- /dev/null +++ b/IM Porject/IM/imclient.h @@ -0,0 +1,204 @@ +#ifndef IMCLIENT_H +#define IMCLIENT_H + +#include +#include +#include +#include +#include "immessage.h" + +/*********************************** + * + * Class IMClient + * IM客户端类 + * + * 用于与服务端交互的封装类 + * + * 公开方法有: + * login 登录 + * sendPrivateMessage 发送私聊消息 + * sendGroupMessage 发送群聊消息 + * + * 发出的信号有: + * receivedPrivateMessage 接收到私聊消息信号 + * receivedGroupMessage 接收到群聊消息信号 + * userOnline 用户上线信号 + * userOffline 用户下线信号 + * serverClose 服务器关闭信号 + * + **********************************/ +/** + * @brief IM客户端类 + */ +class IMClient : public QObject +{ + Q_OBJECT +// 公开的成员函数 +protected: + explicit IMClient(QObject *parent = nullptr); + +public: + /** + * @brief instance 单例对象 + * @return 单例对象指针 + */ + static IMClient *instance(); + + ~IMClient(); + + /** + * @brief connectServer 尝试连接服务器 + * @param hostName 连接名 + */ + void connectServer(QString hostName); + + /** + * @brief login 登录 + * @param name 用户名 + */ + void login(QString name); + + /** + * @brief sendPrivateMessage 发送私聊消息 + * @param toName 发送给谁 + * @param content 消息内容 + */ + void sendPrivateMessage(QString toName, QString content); + + /** + * @brief sendGroupMessage 发送群聊消息 + * @param content 消息内容 + */ + void sendGroupMessage(QString content); + + /** + * @brief isOpen 连接是否打开 + * @return + */ + bool isOpen() + { + return this->m_socket != nullptr + && this->m_socket->state() != QAbstractSocket::UnconnectedState; + } + + +public: + const QVector *getChatRecord(QString name); + const QVector *getGroupChatRecord() { return &m_gruopChatRecord; } + const QVector *getOnlineList() { return &m_online; } + const QVector *getOfflineList() { return &m_offline; } + QString getName() { return m_name; } + +// 信号 +signals: + /** + * @brief loginResult 登录结果 + * @param isSuccess 是否登录成功 + */ + void loginResult(bool isSuccess); + + /** + * @brief receivedPrivateMessage 接收到私聊消息信号 + * @param msg 消息内容 + */ + void receivedPrivateMessage(IMMessage msg); + + /** + * @brief receivedGroupMessage 接收到群聊消息信号 + * @param msg 消息内容 + */ + void receivedGroupMessage(IMMessage msg); + + /** + * @brief userOnline 用户上线信号 + * @param fromName 上线者昵称 + */ + void userOnline(QString fromName); + + /** + * @brief userOffline 用户下线信号 + * @param fromName 下线者昵称 + */ + void userOffline(QString fromName); + + /** + * @brief serverConnected 服务器连接成功信号 + */ + void serverConnected(); + + /** + * @brief serverClose 服务器关闭信号 + */ + void serverClose(); + + /** + * @brief connectError 连接发生错误信号 + * @param ErrorInfo 错误信息文本说明 + */ + void connectError(QString ErrorInfo); + +// 槽 +public slots: + /** + * @brief connected 连接成功时触发 + */ + void connected(); + + /** + * @brief readyread 当接收到数据时触发 + */ + void readyread(); + + /** + * @brief disconnected 当连接断开时触发 + */ + void disconnected(); + + /** + * @brief error 当连接发生错误时触发 + * @param socketError 错误信息 + */ + void error(QAbstractSocket::SocketError socketError); + +// 私有成员函数 +private: + /** + * @brief sendData 发送数据到服务器 + * @param data 数据 + */ + void sendData(QString data); + +// 私有的成员变量 +private: + + /** + * @brief 当前登录的用户昵称 + */ + QString m_name; + + /** + * @brief 客户端的Tcp Socket对象 + */ + QTcpSocket *m_socket; + + /** + * @brief 在线人员昵称列表 + */ + QVector m_online; + /** + * @brief 离线人员昵称列表 + */ + QVector m_offline; + + /** + * @brief 私聊的聊天记录 + */ + QMap > m_chatRecord; + + /** + * @brief 群聊的聊天记录 + */ + QVector m_gruopChatRecord; +}; + +#endif // IMCLIENT_H diff --git a/IM Porject/IM/imdal.cpp b/IM Porject/IM/imdal.cpp new file mode 100644 index 0000000..e7331ce --- /dev/null +++ b/IM Porject/IM/imdal.cpp @@ -0,0 +1,241 @@ +#include +#include +#include +#include +#include +#include +#include "imdal.h" +#include "immessage.h" + + + +IMDAL *IMDAL::instance() +{ + static IMDAL imDAL; + return &imDAL; +} + +void IMDAL::initDatabase(QString name) +{ + //打印Qt支持的数据库驱动 + qDebug()< IMDAL::getPrivateMessage(QString name) +{ + QVector msgList; + int userID = 0; + if (!QSqlDatabase::database().isOpen()) + return msgList; + QSqlQuery query; + query.prepare("SELECT id FROM user WHERE name = ?"); + query.addBindValue(name); + if(!query.exec()) + { + qDebug()< IMDAL::getGroupMessage() +{ + QVector msgList; + if (!QSqlDatabase::database().isOpen()) + return msgList; + QSqlQuery query; + if(!query.exec("SELECT name, content, time FROM message,user WHERE io == 'g' AND user.id = fromID")) + { + qDebug()< IMDAL::getUserList() +{ + qDebug() << "getUserList()"; + QVector names; + if (!QSqlDatabase::database().isOpen()) + return names; + QSqlQuery query; + if (!query.exec("SELECT name FROM user")) + { + qDebug()< +#include +#include "immessage.h" + +// IM数据层 +class IMDAL +{ +public: + /** + * @brief instance 单例对象 + * @return 单例对象指针 + */ + static IMDAL *instance(); + + /** + * @brief initDatabase 初始化数据库 + * @param name 用户名 + */ + void initDatabase(QString name); + + /** + * @brief addPrivateMessage 添加一条私聊消息 + * @param name 用户名 + * @param msg 消息内容 + */ + void addPrivateMessage(QString name, IMMessage msg); + + /** + * @brief addGroupMessage 添加一条群聊消息 + * @param msg 消息内容 + */ + void addGroupMessage(IMMessage msg); + + /** + * @brief getPrivateMessage 获取私聊消息 + * @param name 对方昵称 + * @return 消息内容 + */ + QVector getPrivateMessage(QString name); + + /** + * @brief getGroupMessage 获取群聊消息 + * @return 消息内容 + */ + QVector getGroupMessage(); + + /** + * @brief getUserList 获取用户列表 + * @return 用户列表 + */ + QVector getUserList(); +private: + int getUserID(QString name); +}; + +#endif // IMDAL_H diff --git a/IM Porject/IM/immessage.h b/IM Porject/IM/immessage.h new file mode 100644 index 0000000..0a3cf82 --- /dev/null +++ b/IM Porject/IM/immessage.h @@ -0,0 +1,33 @@ +#ifndef IMMESSAGE_H +#define IMMESSAGE_H + +#include +#include + +struct IMMessage +{ + IMMessage(){} + + IMMessage(QString fromName, QString content, QDateTime time) + : fromName(fromName), + content(content), + time(time) {} + + + /** + * @brief fromName 发送者 + */ + QString fromName; + + /** + * @brief content 内容 + */ + QString content; + + /** + * @brief time 时间 + */ + QDateTime time; +}; + +#endif // IMMESSAGE_H diff --git a/IM Porject/IM/main.cpp b/IM Porject/IM/main.cpp new file mode 100644 index 0000000..2b5e2c0 --- /dev/null +++ b/IM Porject/IM/main.cpp @@ -0,0 +1,14 @@ +#include "formlogin.h" +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + QTextCodec *codec = QTextCodec::codecForName("UTF-8"); + QTextCodec::setCodecForLocale(codec); + // 启动登录窗口 + FormLogin l; + l.show(); + return a.exec(); +} diff --git a/IM Porject/IM/mainwindow.cpp b/IM Porject/IM/mainwindow.cpp new file mode 100644 index 0000000..cd54c8b --- /dev/null +++ b/IM Porject/IM/mainwindow.cpp @@ -0,0 +1,190 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + connect(IMClient::instance(), &IMClient::receivedPrivateMessage, this, &MainWindow::receivedPrivateMessage); + connect(IMClient::instance(), &IMClient::receivedGroupMessage, this, &MainWindow::receivedGroupMessage); + connect(IMClient::instance(), &IMClient::userOnline, this, &MainWindow::userOnline); + connect(IMClient::instance(), &IMClient::userOffline, this, &MainWindow::userOffline); + connect(IMClient::instance(), &IMClient::serverClose, this, &MainWindow::serverClose); + // 初始化好友列表 + ui->listWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + updateUserList(); + + // 设置窗口标题 + this->setWindowTitle("IM:" + IMClient::instance()->getName()); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +// 当发送按钮被点击时 +void MainWindow::on_btnSend_clicked() +{ + // 获取输入框的内容 + QString text = ui->plainTextEdit->toPlainText(); + if (text.isEmpty()) + return; + // 如果要发送的文本大于255,提示超出范围并返回 + if (text.length() > 255) + { + QMessageBox::warning(this, "警告", "您输入的文本太长"); + return; + } + + // 如果当前没有选中任何人,提示并返回 + if (ui->listWidget->currentItem() == nullptr) + { + QMessageBox::information(this, "提示", "请选择对话对象!"); + return; + } + // 验证通过后清空输入框 + ui->plainTextEdit->clear(); + + // 用自己的名字组装消息结构 + IMMessage msg(IMClient::instance()->getName(), text, QDateTime::currentDateTime()); + // 添加到对话框中 + this->addMessage(msg); + // 如果当前选中的是群聊,发送群聊消息,否则发送私聊消息 + if (ui->listWidget->currentItem() == this->pGruopItem) + IMClient::instance()->sendGroupMessage(text); + else + IMClient::instance()->sendPrivateMessage(ui->listWidget->currentItem()->text(), text); +} + +// 接收到私聊消息时 +void MainWindow::receivedPrivateMessage(IMMessage msg) +{ + // 如果当前没有选中发消息这个人,直接返回(优化的话就是新消息提醒) + if (ui->listWidget->currentItem() == nullptr) + return; + if (ui->listWidget->currentItem() == this->pGruopItem) + return; + if (ui->listWidget->currentItem()->text() != msg.fromName) + return; + + // 将消息添加到对话框中 + this->addMessage(msg); +} + +void MainWindow::receivedGroupMessage(IMMessage msg) +{ + if (ui->listWidget->currentItem() == this->pGruopItem) + this->addMessage(msg); +} + +void MainWindow::userOnline(QString fromName) +{ + updateUserList(); + QMessageBox::information(nullptr, "提示", fromName + " 已上线"); +} + +void MainWindow::userOffline(QString fromName) +{ + updateUserList(); + QMessageBox::information(nullptr, "提示", fromName + " 已下线"); +} + +void MainWindow::serverClose() +{ + QMessageBox::warning(this, "警告", "服务器已经关闭,程序即将退出!"); + QApplication::quit(); +} + +// 更新用户列表 +void MainWindow::updateUserList() +{ + ui->listWidget->setCurrentItem(nullptr); + int n = ui->listWidget->count(); + // 清空表,重新加载 + for (int i = 0; i < n; i++) + delete ui->listWidget->takeItem(0); + + // 添加群聊项在表中 + pGruopItem = new QListWidgetItem(QIcon(":icons/images/group.png"), "群聊"); + ui->listWidget->addItem(pGruopItem); + + // 添加在线人员在表中 + const QVector *pUserList = IMClient::instance()->getOnlineList(); + for (int i = 0; i < pUserList->length(); i++) + ui->listWidget->addItem(new QListWidgetItem(QIcon(":icons/images/userOnline.png"), pUserList->at(i))); + + // 添加离线人员到表中 + pUserList = IMClient::instance()->getOfflineList(); + for (int i = 0; i < pUserList->length(); i++) + ui->listWidget->addItem(new QListWidgetItem(QIcon(":icons/images/userOffline.png"), pUserList->at(i))); +} + +void MainWindow::setChatRecord(const QVector *chatRecord, const QString *name) +{ + // 清空聊天框 + ui->textBrowser->clear(); + // 如果是空的直接返回 + if (chatRecord == nullptr) + return; + // 如果name为空表示是群聊消息 + if (name == nullptr) + { + // 群聊消息直接添加即可 + for (int i = 0; i < chatRecord->length(); ++i) + this->addMessage(chatRecord->at(i)); + } + else + { + // 私聊消息需要经过转换,因为私聊消息的fromName使用i和o来代表接收或者发出 + for (int i = 0; i < chatRecord->length(); ++i) + { + IMMessage msg = chatRecord->at(i); + // 如果是i表示接收的消息,用传进来的名字代替,否则是接收的消息,用自己的名字来代替 + msg.fromName = msg.fromName == "i" ? *name : IMClient::instance()->getName(); + this->addMessage(msg); + } + } +} + +void MainWindow::addMessage(const IMMessage &msg) +{ + QString timeStr; + // 选择时间的格式 如果是同一天,那么时间就按 12:00:00 这种格式显示 + if (msg.time.date() > QDateTime::currentDateTime().date().addDays(-1)) + timeStr = msg.time.toString("HH:mm:ss"); + // 否则如果超过一天,但是小于一年,则显示月份日期 12/10 12:00:00 + else if (msg.time.date() > QDateTime::currentDateTime().date().addYears(-1)) + timeStr = msg.time.toString("MM/dd HH:mm:ss"); + // 否则显示年份 + else + timeStr = msg.time.toString("yyyy/MM.dd HH:mm:ss"); + + // 显示文本 + ui->textBrowser->append(QString("%2 %3").arg(msg.fromName == IMClient::instance()->getName() ? "#008040" : "#0000ff").arg(msg.fromName).arg(timeStr)); + ui->textBrowser->insertPlainText(QString("\n %1\n").arg(msg.content)); + ui->textBrowser->moveCursor(QTextCursor::End); +} + +void MainWindow::on_listWidget_itemClicked(QListWidgetItem *item) +{ + // 如果选中项为空,那就清空对话框 + if (item == nullptr) + this->setChatRecord(nullptr); + + // 如果选中了群聊选项,加载群聊的聊天记录 + if (item == this->pGruopItem) + this->setChatRecord(IMClient::instance()->getGroupChatRecord()); + else + { + // 否则就是选中了用户,读取这个用户的聊天记录 + QString name = item->text(); + this->setChatRecord(IMClient::instance()->getChatRecord(name), &name); + } +} diff --git a/IM Porject/IM/mainwindow.h b/IM Porject/IM/mainwindow.h new file mode 100644 index 0000000..ab6b4bf --- /dev/null +++ b/IM Porject/IM/mainwindow.h @@ -0,0 +1,83 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include "imclient.h" +#include "immessage.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void on_btnSend_clicked(); + + void on_listWidget_itemClicked(QListWidgetItem *item); + +public slots: + /** + * @brief receivedPrivateMessage 接收到私聊消息时触发 + * @param msg 消息内容 + */ + void receivedPrivateMessage(IMMessage msg); + + /** + * @brief receivedGroupMessage 接收到群聊消息时触发 + * @param msg 消息内容 + */ + void receivedGroupMessage(IMMessage msg); + + /** + * @brief userOnline 用户上线时触发 + * @param fromName 上线者昵称 + */ + void userOnline(QString fromName); + + /** + * @brief userOffline 用户下线时触发 + * @param fromName 下线者昵称 + */ + void userOffline(QString fromName); + + /** + * @brief serverClose 服务器关闭时触发 + */ + void serverClose(); + +private: + /** + * @brief updateUserList 更新用户列表 + */ + void updateUserList(); + + /** + * @brief setChatRecord 设置当前聊天记录 + * @param chatRecord 聊天记录内容 + */ + void setChatRecord(const QVector *chatRecord, const QString *name = nullptr); + + /** + * @brief addMessage 添加一条消息 + * @param msg 消息内容 + */ + void addMessage(const IMMessage &msg); +private: + Ui::MainWindow *ui; + + /** + * @brief pGruopItem 群聊项指针 + */ + QListWidgetItem *pGruopItem; +}; + +#endif // MAINWINDOW_H diff --git a/IM Porject/IM/mainwindow.ui b/IM Porject/IM/mainwindow.ui new file mode 100644 index 0000000..457499d --- /dev/null +++ b/IM Porject/IM/mainwindow.ui @@ -0,0 +1,149 @@ + + + MainWindow + + + + 0 + 0 + 800 + 500 + + + + + 800 + 500 + + + + + 800 + 500 + + + + MainWindow + + + + + + 240 + 7 + 561 + 371 + + + + IBeamCursor + + + QFrame::NoFrame + + + + + + 240 + 400 + 561 + 71 + + + + IBeamCursor + + + QFrame::NoFrame + + + + + + 720 + 470 + 75 + 23 + + + + Send + + + Return + + + + + + 230 + 390 + 571 + 111 + + + + false + + + background-color: rgb(255, 255, 255) + + + + + + 230 + 0 + 571 + 388 + + + + false + + + background-color: rgb(255, 255, 255) + + + + + + 0 + 0 + 229 + 501 + + + + false + + + background-color: rgb(255, 255, 255) + + + + + 10 + 10 + 218 + 481 + + + + QFrame::NoFrame + + + + widget_3 + widget_2 + widget + textBrowser + plainTextEdit + btnSend + + + + + + diff --git a/IM Porject/IM/protocol.h b/IM Porject/IM/protocol.h new file mode 100644 index 0000000..9e0892f --- /dev/null +++ b/IM Porject/IM/protocol.h @@ -0,0 +1,59 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +/************************************************ + * IM通讯协议规定 + * + * 功能码 [参数] + * + * 客户端功能码规定: 参数: 例子: 说明: + * 1 = 请求登录 用户昵称 1 张三 当客户端登录时必须先发送这条命令,告诉服务端自己的昵称 + * 2 = 发送私聊消息 私聊对象 消息内容 2 李四 你妈喊你回家吃饭 当A向B发送一条私聊消息时,A要向服务器发送这条指令,告诉服务器消息发给谁和发什么消息 + * 3 = 发送群聊消息 消息内容 3 大家好,我是张三 当A向群聊发送一条消息时,直接告诉服务端需要发什么内容即可 + * + * 服务端功能码规定: 参数: 例子: 说明: + * 1 = 发送私聊消息 用户昵称 消息内容 1 张三 你妈喊你回家吃饭 当A向B发送一条私聊消息时,B会收到这条指令,其中昵称是指A(发送者)的昵称 + * 2 = 发送群聊消息 用户昵称 消息内容 2 张三 大家好,我是张三 当A向群聊发送一条消息时,所有人都会收到这条指令,其中昵称是指A(发送者)的昵称 + * 3 = 某人上线 用户昵称 3 张三 当A上线时,所有人都会收到这条指令,其中昵称是A(上线者)的昵称 + * 4 = 某人下线 用户昵称 4 张三 当A下线时,所有人都会手套这条指令,其中昵称是A(下线者)的昵称 + * 10 = 登录结果 是否成功(0:成功,!0:失败) [当前在线人数] [当前在线人员列表] + * 成功时 10 0 4 张三 李四 王五 赵六 当客户端发送登录请求后,如果登录成功则返回当前在线人数与昵称列表,如果失败就返回一个!0值 + * 失败时 10 1 + * + ***********************************************/ + +/** + * @brief 服务端功能码 + */ +enum ServerFunctionCode { + // 私聊消息 + PrivateMessage = 1, + + // 群聊消息 + GroupMessage = 2, + + // 用户上线 + UserOnline = 3, + + // 用户下线 + UserOffline = 4, + + // 登录结果 + LoginResult = 10 +}; + +/** + * @brief 客户端功能码 + */ +enum ClientFunctionCode { + // 登录 + Login = 1, + + // 发送私聊消息 + SendPrivateMessage = 2, + + // 发送群聊消息 + SendGroupMessage = 3 +}; + +#endif // PROTOCOL_H diff --git a/IM Porject/IM/resources.qrc b/IM Porject/IM/resources.qrc new file mode 100644 index 0000000..1ba20d4 --- /dev/null +++ b/IM Porject/IM/resources.qrc @@ -0,0 +1,7 @@ + + + images/group.png + images/userOffline.png + images/userOnline.png + + diff --git a/IM Porject/IMService/IMService.pro b/IM Porject/IMService/IMService.pro new file mode 100644 index 0000000..0970308 --- /dev/null +++ b/IM Porject/IMService/IMService.pro @@ -0,0 +1,29 @@ +QT -= gui +QT += network + +CONFIG += c++11 console +CONFIG -= app_bundle + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which as been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp \ + imservice.cpp + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +HEADERS += \ + imservice.h \ + protocol.h diff --git a/IM Porject/IMService/IMService.pro.user b/IM Porject/IMService/IMService.pro.user new file mode 100644 index 0000000..4b7ff99 --- /dev/null +++ b/IM Porject/IMService/IMService.pro.user @@ -0,0 +1,322 @@ + + + + + + EnvironmentId + {2d03fc4c-541a-4fa5-8f5c-ae92a1998dce} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + -fno-delayed-template-parsing + + true + + + + ProjectExplorer.Project.Target.0 + + Desktop Qt 5.11.2 MinGW 32bit + Desktop Qt 5.11.2 MinGW 32bit + qt.qt5.5112.win32_mingw53_kit + 0 + 0 + 0 + + E:/C++/Qt/build-IMService-Desktop_Qt_5_11_2_MinGW_32bit-Debug + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + true + + + E:/C++/Qt/build-IMService-Desktop_Qt_5_11_2_MinGW_32bit-Release + + + true + qmake + + QtProjectManager.QMakeBuildStep + false + + false + false + true + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + + E:/C++/Qt/build-IMService-Desktop_Qt_5_11_2_MinGW_32bit-Profile + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + true + true + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + 3 + + + 0 + 部署 + + ProjectExplorer.BuildSteps.Deploy + + 1 + Deploy Configuration + + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + IMService + + Qt4ProjectManager.Qt4RunConfiguration:E:/C++/Qt/IMService/IMService.pro + true + + IMService.pro + + E:/C++/Qt/build-IMService-Desktop_Qt_5_11_2_MinGW_32bit-Debug + 3768 + false + true + false + false + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 18 + + + Version + 18 + + diff --git a/IM Porject/IMService/imservice.cpp b/IM Porject/IMService/imservice.cpp new file mode 100644 index 0000000..f033bdf --- /dev/null +++ b/IM Porject/IMService/imservice.cpp @@ -0,0 +1,229 @@ +#include "imservice.h" +#include "protocol.h" +#include + +// 构造函数 +IMService::IMService(QObject *parent) + : QObject(parent), + m_server(new QTcpServer), + m_clientSocket(new QMap), + m_clientNames(new QMap) +{ + connect(this->m_server, &QTcpServer::newConnection, this, &IMService::newConnection); + if (this->m_server->listen(QHostAddress::Any, 9876)) + qDebug() << "Service open SUCCESS!"; + else + qDebug() << this->m_server->errorString(); //错误信息 + +} + +IMService::~IMService() +{ + m_server->close(); + delete m_server; + delete m_clientSocket; + delete m_clientNames; + qDebug() << "Service Close!"; +} + +void IMService::closeService() +{ + // 遍历并关闭所有Socket连接 + for (auto it : *(this->m_clientSocket)) + it->close(); + // 清空表 + this->m_clientSocket->clear(); + this->m_clientNames->clear(); +} + +// 当有新连接进入时 +void IMService::newConnection() +{ + // 获取这个连接的Socket + QTcpSocket *socketTemp = this->m_server->nextPendingConnection(); + + // 当接收到数据时触发readyRead + connect(socketTemp, &QTcpSocket::readyRead, this, &IMService::readyRead); + + // 当连接断开时触发disconnected + connect(socketTemp, &QTcpSocket::disconnected, this, &IMService::disconnected); + + qDebug() << "newConnection!"; +} + +// 当连接断开时触发 +void IMService::disconnected() +{ + qDebug() << "disconnected!"; + + // 获取断开连接的对象 + QTcpSocket* sender = static_cast(QObject::sender()); + // 释放内存 + sender->deleteLater(); + + // 先查一下这个连接是否存在表中,如果没在,说明这个连接没上线,直接return即可 + if (!this->m_clientNames->contains(sender)) + return; + + // 否则说明这是一个在线用户断开连接 + // 记录这个用户的昵称 + QString senderName = this->m_clientNames->value(sender); + + // 将这个用户从表中移除 + this->m_clientSocket->remove(senderName); + this->m_clientNames->remove(sender); + + // 通知其他人该用户离线 + this->userOffline(senderName); +} + +// 当接收到数据时触发 +void IMService::readyRead() +{ + // 获取接收到数据的对象 + QTcpSocket* sender = static_cast(QObject::sender()); + // 获取数据 + QByteArray data = sender->readAll(); + QTextStream in(data, QIODevice::ReadOnly); + qDebug() << "readyread:" << data; + + + // 进行协议分析与任务调度 + int functionID = 0; + in >> functionID; + + // 如果是登录的功能码 + if (functionID == ClientFunctionCode::Login) + { + QString name; + in >> name; + // 执行登录 + this->userLogin(name, sender); + } + // 检测这个连接有没有登录 + else if (this->m_clientNames->contains(sender)) + { + // 如果是私聊消息 + if (functionID == ClientFunctionCode::SendPrivateMessage) + { + // 获取要发送的用户昵称 + QString toName; + in >> toName; + in.seek(in.pos() + 1); + + // 将剩下的内容全部发送 + this->sendPrivateMessage(this->m_clientNames->value(sender), toName, in.readAll()); + } + // 否则如果是群聊消息 + else if (functionID == ClientFunctionCode::SendGroupMessage) + { + in.seek(in.pos() + 1); + this->sendGroupMessage(this->m_clientNames->value(sender), in.readAll()); + } + } +} + +void IMService::sendData(QTcpSocket *socket, QString data) +{ + if (socket != nullptr && socket->isOpen()) + socket->write(data.toUtf8()); +} + +/* +enum ServerFunctionCode { + // 私聊消息 + PrivateMessage = 1, + + // 群聊消息 + GroupMessage = 2, + + // 用户上线 + UserOnline = 3, + + // 用户下线 + UserOffline = 4, + + // 登录结果 + LoginResult = 10 +}; +*/ + + +// 用户登录 +// 参数:name 用户昵称 +void IMService::userLogin(QString name, QTcpSocket *socket) +{ + qDebug() << "userLogin(): user name:" << name << "\tsocket:" << socket; + // 如果这个昵称或者连接已经登录了 + if (this->m_clientSocket->contains(name) || this->m_clientNames->contains(socket)) + { + qDebug() << "Login failed!"; + // 发送登录结果:登录失败 + this->sendData(this->m_clientSocket->value(name), QString("%1 1").arg(ServerFunctionCode::LoginResult)); + } + else + { + qDebug() << "Login success!"; + // 获取当前在线的用户昵称 + QList temp = this->m_clientSocket->keys(); + this->m_clientSocket->insert(name, socket); + this->m_clientNames->insert(socket, name); + // 将所有在线用户的昵称组合 + QString ret = QString("%1 0 %2").arg(ServerFunctionCode::LoginResult).arg(temp.length()); + QTextStream ts(&ret); + for (QString it : temp) + ts << ' ' << it; + qDebug() << "ret:" << ret; + // 发送登录结果:登录成功! 并返回当前在线人员数据 + this->sendData(this->m_clientSocket->value(name), ret); + + // 通知其他人改用户上线 + this->userOnline(name); + } +} + +// 发送私聊消息 +// 参数:fromName 发送者昵称 +// 参数:toName 接收者昵称 +// 参数:content 内容 +void IMService::sendPrivateMessage(QString fromName, QString toName, QString content) +{ + qDebug() << "sendPrivateMessage(): fromName:" << fromName << "\ttoName" << toName << "\tcontent" << content; + // 如果该用户存在才发送 + if (this->m_clientSocket->contains(toName)) + this->sendData(this->m_clientSocket->value(toName), QString("%1 %2 %3").arg(ServerFunctionCode::PrivateMessage).arg(fromName).arg(content)); +} + +// 发送群聊消息 +// 参数:fromName 发送者昵称 +// 参数:content 内容 +void IMService::sendGroupMessage(QString fromName, QString content) +{ + qDebug() << "sendGroupMessage(): fromName:" << fromName << "\tcontent" << content; + QString data = QString("%1 %2 %3").arg(ServerFunctionCode::GroupMessage).arg(fromName).arg(content); + for (auto it = this->m_clientSocket->begin(); it != this->m_clientSocket->end(); it++) + if (it.key() != fromName) + this->sendData(it.value(), data); +} + +// 用户上线 +// 参数:name 用户昵称 +void IMService::userOnline(QString name) +{ + qDebug() << "userOnline(): name:" << name; + QString data = QString("%1 %2").arg(ServerFunctionCode::UserOnline).arg(name); + for (auto it = this->m_clientSocket->begin(); it != this->m_clientSocket->end(); it++) + if (it.key() != name) + this->sendData(it.value(), data); +} + +// 用户离线 +// 参数:name 用户昵称 +void IMService::userOffline(QString name) +{ + qDebug() << "userOffline(): name:" << name; + QString data = QString("%1 %2").arg(ServerFunctionCode::UserOffline).arg(name); + for (auto it = this->m_clientSocket->begin(); it != this->m_clientSocket->end(); it++) + if (it.key() != name) + this->sendData(it.value(), data); +} diff --git a/IM Porject/IMService/imservice.h b/IM Porject/IMService/imservice.h new file mode 100644 index 0000000..5906a7b --- /dev/null +++ b/IM Porject/IMService/imservice.h @@ -0,0 +1,129 @@ +#ifndef IMSERVICE_H +#define IMSERVICE_H + +#include +#include +#include + +/*********************************** + * + * Class IMService + * IM服务端类 + * + * 用于与客户端交互的封装类 + * + * 公开方法有: + * login 登录 + * sendPrivateMessage 发送私聊消息 + * sendGroupMessage 发送群聊消息 + * + * 发出的信号有: + * receivedPrivateMessage 接收到私聊消息信号 + * receivedGroupMessage 接收到群聊消息信号 + * userOnline 用户上线信号 + * userOffline 用户下线信号 + * serverClose 服务器关闭信号 + * + **********************************/ + +class IMService : public QObject +{ + Q_OBJECT + +// 公开成员函数 +public: + explicit IMService(QObject *parent = nullptr); + + ~IMService(); + + /** + * @brief closeService 关闭服务 断开所有Socket连接 + */ + void closeService(); +// 信号 +signals: + + +// 槽 +public slots: + /** + * @brief newConnection 当有新连接进入时 + */ + void newConnection(); + + /** + * @brief disconnected 当连接断开时触发 + */ + void disconnected(); + + /** + * @brief readyRead 当接收到数据时触发 + */ + void readyRead(); + +// 私有成员函数 +private: + // 发送数据到socket对象中 + /** + * @brief sendData 发送数据到socket对象中 + * @param socket 指定socket对象 + * @param data 要发送的数据 + */ + void sendData(QTcpSocket *socket, QString data); + + /** + * @brief userLogin 用户登录 + * @param name 用户昵称 + * @param socket socket对象 + */ + void userLogin(QString name, QTcpSocket *socket); + + /** + * @brief sendPrivateMessage 发送私聊消息 + * @param fromName 发送者昵称 + * @param toName 接收者昵称 + * @param content 内容 + */ + void sendPrivateMessage(QString fromName, QString toName, QString content); + + /** + * @brief sendGroupMessage 发送群聊消息 + * @param fromName 发送者昵称 + * @param content 内容 + */ + void sendGroupMessage(QString fromName, QString content); + + /** + * @brief userOnline 用户上线 + * @param name 用户昵称 + */ + void userOnline(QString name); + + /** + * @brief userOffline 用户离线 + * @param name 用户昵称 + */ + void userOffline(QString name); + +// 私有成员变量 +private: + /** + * @brief m_server 服务端的Tcp Server对象 + */ + QTcpServer *m_server; + + // 客户端的Tcp Socket连接列表 + // QMap是一个键值对容器,在这里key是用户的昵称,value是用户的Socket连接对象 + // 每当一个新用户上线,将会添加到该容器中 + // 当用户下线时则从容器中删除 + /** + * @brief m_clientSocket 客户端的Tcp Socket连接列表 + */ + QMap *m_clientSocket; + /** + * @brief m_clientNames 客户端的昵称列表 + */ + QMap *m_clientNames; +}; + +#endif // IMSERVICE_H diff --git a/IM Porject/IMService/main.cpp b/IM Porject/IMService/main.cpp new file mode 100644 index 0000000..8096d49 --- /dev/null +++ b/IM Porject/IMService/main.cpp @@ -0,0 +1,13 @@ +#include +#include +#include "imservice.h" + + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + QTextCodec *codec = QTextCodec::codecForName("UTF-8"); + QTextCodec::setCodecForLocale(codec); + static IMService imService; + return a.exec(); +} diff --git a/IM Porject/IMService/protocol.h b/IM Porject/IMService/protocol.h new file mode 100644 index 0000000..9e0892f --- /dev/null +++ b/IM Porject/IMService/protocol.h @@ -0,0 +1,59 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +/************************************************ + * IM通讯协议规定 + * + * 功能码 [参数] + * + * 客户端功能码规定: 参数: 例子: 说明: + * 1 = 请求登录 用户昵称 1 张三 当客户端登录时必须先发送这条命令,告诉服务端自己的昵称 + * 2 = 发送私聊消息 私聊对象 消息内容 2 李四 你妈喊你回家吃饭 当A向B发送一条私聊消息时,A要向服务器发送这条指令,告诉服务器消息发给谁和发什么消息 + * 3 = 发送群聊消息 消息内容 3 大家好,我是张三 当A向群聊发送一条消息时,直接告诉服务端需要发什么内容即可 + * + * 服务端功能码规定: 参数: 例子: 说明: + * 1 = 发送私聊消息 用户昵称 消息内容 1 张三 你妈喊你回家吃饭 当A向B发送一条私聊消息时,B会收到这条指令,其中昵称是指A(发送者)的昵称 + * 2 = 发送群聊消息 用户昵称 消息内容 2 张三 大家好,我是张三 当A向群聊发送一条消息时,所有人都会收到这条指令,其中昵称是指A(发送者)的昵称 + * 3 = 某人上线 用户昵称 3 张三 当A上线时,所有人都会收到这条指令,其中昵称是A(上线者)的昵称 + * 4 = 某人下线 用户昵称 4 张三 当A下线时,所有人都会手套这条指令,其中昵称是A(下线者)的昵称 + * 10 = 登录结果 是否成功(0:成功,!0:失败) [当前在线人数] [当前在线人员列表] + * 成功时 10 0 4 张三 李四 王五 赵六 当客户端发送登录请求后,如果登录成功则返回当前在线人数与昵称列表,如果失败就返回一个!0值 + * 失败时 10 1 + * + ***********************************************/ + +/** + * @brief 服务端功能码 + */ +enum ServerFunctionCode { + // 私聊消息 + PrivateMessage = 1, + + // 群聊消息 + GroupMessage = 2, + + // 用户上线 + UserOnline = 3, + + // 用户下线 + UserOffline = 4, + + // 登录结果 + LoginResult = 10 +}; + +/** + * @brief 客户端功能码 + */ +enum ClientFunctionCode { + // 登录 + Login = 1, + + // 发送私聊消息 + SendPrivateMessage = 2, + + // 发送群聊消息 + SendGroupMessage = 3 +}; + +#endif // PROTOCOL_H diff --git a/IM Porject/readme.txt b/IM Porject/readme.txt new file mode 100644 index 0000000..65544d4 --- /dev/null +++ b/IM Porject/readme.txt @@ -0,0 +1,11 @@ +IMͻˣ +FormLogin Ϊ¼ +IMClient ΪIMͻ +IMDAL ΪIMݿ +IMMessage ΪIMϢṹ +MainWindow Ϊ +protocol ΪͨѶЭ + +IM +IMService ΪIM +protocol ΪͨѶЭ