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 ΪͨѶЭ