• 支持语音与视频即时通讯项目杂记(一)


        

    第一部分解释服务端的实现。

    (服务端结构)

       下面一个用于实现TCP服务器的代码,包括消息服务器(TcpMsgServer)和文件中转服务器(TcpFileServer)。

    首先,TcpServer是TcpMsgServer和TcpFileServer的基类,它负责创建QTcpServer对象并监听端口。通过StartListen()函数可以启动监听,传入指定的端口号进行监听。CloseListen()函数用于关闭监听。

    TcpMsgServer是消息服务器,继承自TcpServer类。它通过重写SltNewConnection()函数来处理新客户端连接的逻辑。当有新的客户端连接到服务器时,会创建一个ClientSocket对象来管理该客户端连接。在SltConnected()函数中,对连接进行验证后,将客户端对象添加到容器m_clients中,并建立与该客户端的信号与槽连接。在SltDisConnected()函数中,处理客户端下线的情况,从容器中移除对应的客户端对象,并断开相关的信号与槽连接。SltMsgToClient()函数用于消息转发控制,根据收到的消息类型、目标客户端ID和消息内容,找到对应的客户端对象,并调用其SltSendMessage()函数将消息发送给客户端。

    TcpFileServer是文件中转服务器,同样继承自TcpServer类。它也重写了SltNewConnection()函数来处理新的客户端连接。在SltConnected()函数中,将连接上的客户端对象添加到容器m_clients中。SltDisConnected()函数处理客户端断连的情况,从容器中移除对应的客户端对象,并断开相关的信号与槽连接。SltClientDownloadFile()函数处理客户端请求下载文件的情况,根据收到的消息中的来源ID和文件名,在容器m_clients中找到对应的客户端对象,调用其StartTransferFile()函数开始文件传输过程。

    在代码中,TcpMsgServer和TcpFileServer都采用了容器来管理连接的客户端对象,以便进行消息转发和文件传输等操作。

    1. #include "tcpserver.h"
    2. #include "clientsocket.h"
    3. #include "myapp.h"
    4. #include "databasemagr.h"
    5. #include
    6. /
    7. /// 服务器类,是TcpMsgServer和TcpFileServer的基类
    8. TcpServer::TcpServer(QObject *parent) :
    9. QObject(parent)
    10. {
    11. m_tcpServer = new QTcpServer(this);
    12. connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(SltNewConnection()));
    13. }
    14. TcpServer::~TcpServer()
    15. {
    16. if (m_tcpServer->isListening()) m_tcpServer->close();
    17. }
    18. ///启动监听
    19. bool TcpServer::StartListen(int port)
    20. {
    21. if (m_tcpServer->isListening()) m_tcpServer->close();
    22. bool bOk = m_tcpServer->listen(QHostAddress::Any, port);
    23. return bOk;
    24. }
    25. ///关闭监听
    26. void TcpServer::CloseListen()
    27. {
    28. m_tcpServer->close();
    29. }
    30. /
    31. /// 消息服务器
    32. TcpMsgServer::TcpMsgServer(QObject *parent) :
    33. TcpServer(parent)
    34. {
    35. }
    36. TcpMsgServer::~TcpMsgServer()
    37. {
    38. qDebug() << "tcp server close";
    39. foreach (ClientSocket *client, m_clients) {
    40. m_clients.removeOne(client);
    41. client->Close();
    42. }
    43. }
    44. /// 新客户端连接处理
    45. void TcpMsgServer::SltNewConnection()
    46. {
    47. ClientSocket *client = new ClientSocket(this, m_tcpServer->nextPendingConnection());
    48. connect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
    49. connect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
    50. }
    51. ///通过验证后,才可以加入容器进行管理
    52. void TcpMsgServer::SltConnected()
    53. {
    54. ClientSocket *client = (ClientSocket *)this->sender();
    55. if (NULL == client) return;
    56. connect(client, SIGNAL(signalMsgToClient(quint8,int,QJsonValue)),
    57. this, SLOT(SltMsgToClient(quint8,int,QJsonValue)));
    58. connect(client, SIGNAL(signalDownloadFile(QJsonValue)), this, SIGNAL(signalDownloadFile(QJsonValue)));
    59. m_clients.push_back(client);
    60. qDebug() << "TcpMsgServer::SltConnected. last m_nId=" + QString::number(m_clients[m_clients.size()-1]->GetUserId());
    61. }
    62. ///有客户端下线
    63. void TcpMsgServer::SltDisConnected()
    64. {
    65. //找到断连的socket
    66. ClientSocket *client = (ClientSocket *)this->sender();
    67. if (NULL == client) return;
    68. //移除对应socket
    69. for (int i = 0; i < m_clients.size(); i++) {
    70. if (client == m_clients.at(i))
    71. {
    72. m_clients.remove(i);
    73. return;
    74. }
    75. }
    76. disconnect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
    77. disconnect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
    78. disconnect(client, SIGNAL(signalMsgToClient(quint8,int,QJsonValue)),
    79. this, SLOT(SltMsgToClient(quint8,int,QJsonValue)));
    80. disconnect(client, SIGNAL(signalDownloadFile(QJsonValue)), this, SIGNAL(signalDownloadFile(QJsonValue)));
    81. }
    82. ///消息转发控制
    83. void TcpMsgServer::SltMsgToClient(const quint8 &type, const int &id, const QJsonValue &json)
    84. {
    85. // 查找要发送过去的id
    86. for (int i = 0; i < m_clients.size(); i++) {
    87. if (id == m_clients.at(i)->GetUserId())
    88. {
    89. qDebug()<<"TcpMsgServer::SltMsgToClient. send to:"+QString::number(id);
    90. m_clients.at(i)->SltSendMessage(type, json);
    91. return;
    92. }
    93. }
    94. }
    95. ///传送文件到指定ID的客户端
    96. void TcpMsgServer::SltTransFileToClient(const int &userId, const QJsonValue &json)
    97. {
    98. // 查找要发送过去的id
    99. for (int i = 0; i < m_clients.size(); i++) {
    100. if (userId == m_clients.at(i)->GetUserId())
    101. {
    102. m_clients.at(i)->SltSendMessage(SendFile, json);
    103. return;
    104. }
    105. }
    106. }
    107. //
    108. /// 文件中转服务器,客户端先把待转发的文件保存在服务器
    109. /// 服务器接受完成后,通知其他客户端来下载
    110. TcpFileServer::TcpFileServer(QObject *parent) :
    111. TcpServer(parent)
    112. {
    113. }
    114. TcpFileServer::~TcpFileServer()
    115. {
    116. qDebug() << "tcp server close";
    117. foreach (ClientFileSocket *client, m_clients) {
    118. m_clients.removeOne(client);
    119. client->Close();
    120. }
    121. }
    122. ///客户端与文件服务器新建连接
    123. void TcpFileServer::SltNewConnection()
    124. {
    125. //新建槽函数与socket
    126. ClientFileSocket *client = new ClientFileSocket(this, m_tcpServer->nextPendingConnection());
    127. connect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
    128. connect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
    129. }
    130. /// socket管理
    131. void TcpFileServer::SltConnected()
    132. {
    133. //连接时将Client放入vector m_clients
    134. ClientFileSocket *client = (ClientFileSocket *)this->sender();
    135. if (NULL == client) return;
    136. m_clients.push_back(client);
    137. }
    138. /// 客户端断连
    139. void TcpFileServer::SltDisConnected()
    140. {
    141. ClientFileSocket *client = (ClientFileSocket *)this->sender();
    142. if (NULL == client) return;
    143. for (int i = 0; i < m_clients.size(); i++) {
    144. if (client == m_clients.at(i))
    145. {
    146. m_clients.remove(i);
    147. return;
    148. }
    149. }
    150. disconnect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
    151. disconnect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
    152. }
    153. /// 客户端请求下载文件
    154. void TcpFileServer::SltClientDownloadFile(const QJsonValue &json)
    155. {
    156. // 根据ID寻找连接的socket
    157. if (json.isObject()) {
    158. QJsonObject jsonObj = json.toObject();
    159. qint32 nId = jsonObj.value("from").toInt();//
    160. qint32 nWid = jsonObj.value("id").toInt();;//
    161. QString fileName = jsonObj.value("msg").toString();
    162. qDebug() << "get file" << jsonObj << m_clients.size();
    163. for (int i = 0; i < m_clients.size(); i++) {
    164. if (m_clients.at(i)->CheckUserId(nId, nWid))
    165. {
    166. m_clients.at(i)->StartTransferFile(fileName);
    167. return;
    168. }
    169. }
    170. }
    171. }

           当服务端端通过accpt收到一个请求后,创建一个ClientSocket,处理客户端消息。
            下面是一个Qt中的客户端socket管理类,用于与服务端进行通信。其中包含两个类,一个是ClientSocket,用于处理普通消息,另一个是ClientFileSocket,用于处理文件传输。

    在ClientSocket中,包含了一些信号和槽函数,用于处理连接、数据接收、关闭等操作。同时还有一些私有函数,用于解析不同类型的消息,并且把解析后的数据发送到前台界面进行展示。

    在ClientFileSocket中,主要有两个功能:文件接收和文件发送。对于文件接收,分别记录了已经接收到的数据大小、文件名大小、要接收的文件等信息;对于文件发送,记录了文件大小、已经发送的数据大小、剩余数据大小、要发送的文件等信息。同时还有一些私有函数,用于初始化socket、处理接收到的数据、更新发送进度等操作。

    总的来说,这个类是一个很重要的网络通信模块,可以实现与服务端的双向交互,包括文字、图片、文件等。

    1. #ifndef CLIENTSOCKET_H
    2. #define CLIENTSOCKET_H
    3. #include
    4. #include
    5. #include
    6. #include
    7. /// 服务端socket管理类
    8. class ClientSocket : public QObject
    9. {
    10. Q_OBJECT
    11. public:
    12. explicit ClientSocket(QObject *parent = 0, QTcpSocket *tcpSocket = NULL);
    13. ~ClientSocket();
    14. int GetUserId() const;
    15. void Close();
    16. signals:
    17. void signalConnected();
    18. void signalDisConnected();
    19. void signalDownloadFile(const QJsonValue &json);
    20. void signalMsgToClient(const quint8 &type, const int &id, const QJsonValue &dataVal);
    21. public slots:
    22. private:
    23. QTcpSocket *m_tcpSocket;
    24. int m_nId;
    25. public slots:
    26. // 消息回发
    27. void SltSendMessage(const quint8 &type, const QJsonValue &json);
    28. private slots:
    29. void SltConnected();
    30. void SltDisconnected();
    31. void SltReadyRead();
    32. private:
    33. // 消息解析和抓转发处理
    34. void ParseLogin(const QJsonValue &dataVal);
    35. void ParseUserOnline(const QJsonValue &dataVal);
    36. void ParseLogout(const QJsonValue &dataVal);
    37. void ParseUpdateUserHead(const QJsonValue &dataVal);
    38. void ParseReister(const QJsonValue &dataVal);
    39. void ParseAddFriend(const QJsonValue &dataVal);
    40. void ParseAddGroup(const QJsonValue &dataVal);
    41. void ParseCreateGroup(const QJsonValue &dataVal);
    42. void ParseGetMyFriend(const QJsonValue &dataVal);
    43. void ParseGetMyGroups(const QJsonValue &dataVal);
    44. void ParseRefreshFriend(const QJsonValue &dataVal);
    45. void ParseRefreshGroups(const QJsonValue &dataVal);
    46. void ParseFriendMessages(const QByteArray &reply);
    47. void ParseGroupMessages(const QByteArray &reply);
    48. };

    1. ClientSocket::ClientSocket(QObject *parent, QTcpSocket *tcpSocket) :
    2. QObject(parent)
    3. {
    4. qRegisterMetaType("QAbstractSocket::SocketError");
    5. m_nId = -1;
    6. if (tcpSocket == NULL) m_tcpSocket = new QTcpSocket(this);
    7. m_tcpSocket = tcpSocket;
    8. connect(m_tcpSocket, SIGNAL(readyRead()), this, SLOT(SltReadyRead()));//处理客户端信息
    9. connect(m_tcpSocket, SIGNAL(connected()), this, SLOT(SltConnected()));//处理登录成功信号
    10. connect(m_tcpSocket, SIGNAL(disconnected()), this, SLOT(SltDisconnected()));//处理登出信号
    11. }

    处理客户端消息,根据消息类型进行不同的处理:

    1. void ClientSocket::SltReadyRead()
    2. {
    3. // 读取socket数据
    4. QByteArray reply = m_tcpSocket->readAll();
    5. QJsonParseError jsonError;
    6. // 转化为 JSON 文档
    7. QJsonDocument doucment = QJsonDocument::fromJson(reply, &jsonError);
    8. // 解析未发生错误
    9. if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) {
    10. // JSON 文档为对象
    11. if (doucment.isObject()) {
    12. // 转化为对象
    13. QJsonObject jsonObj = doucment.object();
    14. int nType = jsonObj.value("type").toInt();
    15. QJsonValue dataVal = jsonObj.value("data");
    16. switch (nType) {
    17. case Register:
    18. {
    19. ParseReister(dataVal);
    20. }
    21. break;
    22. case Login:
    23. {
    24. ParseLogin(dataVal);
    25. }
    26. break;
    27. case UserOnLine:
    28. {
    29. ParseUserOnline(dataVal);
    30. }
    31. break;
    32. case Logout:
    33. {
    34. ParseLogout(dataVal);
    35. Q_EMIT signalDisConnected();
    36. m_tcpSocket->abort();
    37. }
    38. break;
    39. case UpdateHeadPic:
    40. {
    41. ParseUpdateUserHead(dataVal);
    42. }
    43. break;
    44. case AddFriend:
    45. {
    46. ParseAddFriend(dataVal);
    47. }
    48. break;
    49. case AddGroup:
    50. {
    51. ParseAddGroup(dataVal);
    52. }
    53. break;
    54. case CreateGroup:
    55. {
    56. ParseCreateGroup(dataVal);
    57. }
    58. break;
    59. case GetMyFriends:
    60. {
    61. ParseGetMyFriend(dataVal);
    62. }
    63. break;
    64. case GetMyGroups:
    65. {
    66. ParseGetMyGroups(dataVal);
    67. }
    68. break;
    69. case RefreshFriends:
    70. {
    71. ParseRefreshFriend(dataVal);
    72. }
    73. break;
    74. case RefreshGroups:
    75. {
    76. ParseRefreshGroups(dataVal);
    77. }
    78. break;
    79. case SendMsg:
    80. case SendFile:
    81. case SendPicture:
    82. {
    83. ParseFriendMessages(reply);
    84. }
    85. break;
    86. case SendGroupMsg:
    87. {
    88. ParseGroupMessages(reply);
    89. }
    90. break;
    91. case SendFace:
    92. {
    93. ParseGroupMessages(reply);
    94. }
    95. break;
    96. case SendFileOk:
    97. {
    98. }
    99. break;
    100. case GetFile:
    101. {
    102. Q_EMIT signalDownloadFile(dataVal);
    103. }
    104. break;
    105. default:
    106. break;
    107. }
    108. }
    109. }
    110. }

    登录的处理:

    1. void ClientSocket::ParseLogin(const QJsonValue &dataVal)
    2. {
    3. // data 的 value 也是JSON对象
    4. if (dataVal.isObject()) {
    5. QJsonObject dataObj = dataVal.toObject();
    6. QString strName = dataObj.value("name").toString();
    7. QString strPwd = dataObj.value("passwd").toString();
    8. QJsonObject jsonObj = DataBaseMagr::Instance()->CheckUserLogin(strName, strPwd);
    9. m_nId = jsonObj.value("id").toInt();
    10. qDebug() << "login" << jsonObj;
    11. //验证成功才向server发送信号说明可以将socket加入容器管理
    12. if (m_nId > 0) Q_EMIT signalConnected();
    13. // 发送查询结果至客户端
    14. SltSendMessage(Login, jsonObj);;
    15. }
    16. }

    余略.....

  • 相关阅读:
    无序点云排序
    订单号传递规则
    PMP每日一练 | 考试不迷路-9.14(包含敏捷+多选)
    alsa pcm接口之在unix环境的传输方法
    Elasticsearch:如何在不更新证书的情况下为集群之间建立互信
    矩阵分析与应用+张贤达
    postman的简单使用
    互联网数据的重要性
    3D城市模型可视化:开启智慧都市探索之旅
    基于docker实现JMeter分布式压测
  • 原文地址:https://blog.csdn.net/wh_xia_jun/article/details/131848447