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


(服务端结构)
下面一个用于实现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都采用了容器来管理连接的客户端对象,以便进行消息转发和文件传输等操作。
- #include "tcpserver.h"
- #include "clientsocket.h"
- #include "myapp.h"
- #include "databasemagr.h"
-
- #include
-
- /
- /// 服务器类,是TcpMsgServer和TcpFileServer的基类
- TcpServer::TcpServer(QObject *parent) :
- QObject(parent)
- {
- m_tcpServer = new QTcpServer(this);
-
- connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(SltNewConnection()));
- }
-
- TcpServer::~TcpServer()
- {
- if (m_tcpServer->isListening()) m_tcpServer->close();
- }
-
- ///启动监听
- bool TcpServer::StartListen(int port)
- {
- if (m_tcpServer->isListening()) m_tcpServer->close();
- bool bOk = m_tcpServer->listen(QHostAddress::Any, port);
- return bOk;
- }
-
- ///关闭监听
- void TcpServer::CloseListen()
- {
- m_tcpServer->close();
- }
-
-
- /
- /// 消息服务器
- TcpMsgServer::TcpMsgServer(QObject *parent) :
- TcpServer(parent)
- {
- }
-
- TcpMsgServer::~TcpMsgServer()
- {
- qDebug() << "tcp server close";
- foreach (ClientSocket *client, m_clients) {
- m_clients.removeOne(client);
- client->Close();
- }
- }
-
- /// 新客户端连接处理
- void TcpMsgServer::SltNewConnection()
- {
- ClientSocket *client = new ClientSocket(this, m_tcpServer->nextPendingConnection());
- connect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
- connect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
- }
-
- ///通过验证后,才可以加入容器进行管理
- void TcpMsgServer::SltConnected()
- {
- ClientSocket *client = (ClientSocket *)this->sender();
- if (NULL == client) return;
-
- connect(client, SIGNAL(signalMsgToClient(quint8,int,QJsonValue)),
- this, SLOT(SltMsgToClient(quint8,int,QJsonValue)));
- connect(client, SIGNAL(signalDownloadFile(QJsonValue)), this, SIGNAL(signalDownloadFile(QJsonValue)));
- m_clients.push_back(client);
-
- qDebug() << "TcpMsgServer::SltConnected. last m_nId=" + QString::number(m_clients[m_clients.size()-1]->GetUserId());
- }
-
- ///有客户端下线
- void TcpMsgServer::SltDisConnected()
- {
- //找到断连的socket
- ClientSocket *client = (ClientSocket *)this->sender();
- if (NULL == client) return;
-
- //移除对应socket
- for (int i = 0; i < m_clients.size(); i++) {
- if (client == m_clients.at(i))
- {
- m_clients.remove(i);
- return;
- }
- }
-
- disconnect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
- disconnect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
- disconnect(client, SIGNAL(signalMsgToClient(quint8,int,QJsonValue)),
- this, SLOT(SltMsgToClient(quint8,int,QJsonValue)));
- disconnect(client, SIGNAL(signalDownloadFile(QJsonValue)), this, SIGNAL(signalDownloadFile(QJsonValue)));
- }
-
- ///消息转发控制
- void TcpMsgServer::SltMsgToClient(const quint8 &type, const int &id, const QJsonValue &json)
- {
- // 查找要发送过去的id
- for (int i = 0; i < m_clients.size(); i++) {
- if (id == m_clients.at(i)->GetUserId())
- {
- qDebug()<<"TcpMsgServer::SltMsgToClient. send to:"+QString::number(id);
-
- m_clients.at(i)->SltSendMessage(type, json);
- return;
- }
- }
- }
-
- ///传送文件到指定ID的客户端
- void TcpMsgServer::SltTransFileToClient(const int &userId, const QJsonValue &json)
- {
- // 查找要发送过去的id
- for (int i = 0; i < m_clients.size(); i++) {
- if (userId == m_clients.at(i)->GetUserId())
- {
- m_clients.at(i)->SltSendMessage(SendFile, json);
- return;
- }
- }
- }
-
- //
- /// 文件中转服务器,客户端先把待转发的文件保存在服务器
- /// 服务器接受完成后,通知其他客户端来下载
- TcpFileServer::TcpFileServer(QObject *parent) :
- TcpServer(parent)
- {
- }
-
- TcpFileServer::~TcpFileServer()
- {
- qDebug() << "tcp server close";
- foreach (ClientFileSocket *client, m_clients) {
- m_clients.removeOne(client);
- client->Close();
- }
- }
-
- ///客户端与文件服务器新建连接
- void TcpFileServer::SltNewConnection()
- {
- //新建槽函数与socket
- ClientFileSocket *client = new ClientFileSocket(this, m_tcpServer->nextPendingConnection());
- connect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
- connect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
- }
-
- /// socket管理
- void TcpFileServer::SltConnected()
- {
- //连接时将Client放入vector m_clients
- ClientFileSocket *client = (ClientFileSocket *)this->sender();
- if (NULL == client) return;
-
- m_clients.push_back(client);
- }
-
- /// 客户端断连
- void TcpFileServer::SltDisConnected()
- {
- ClientFileSocket *client = (ClientFileSocket *)this->sender();
- if (NULL == client) return;
-
- for (int i = 0; i < m_clients.size(); i++) {
- if (client == m_clients.at(i))
- {
- m_clients.remove(i);
- return;
- }
- }
-
- disconnect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
- disconnect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
- }
-
- /// 客户端请求下载文件
- void TcpFileServer::SltClientDownloadFile(const QJsonValue &json)
- {
- // 根据ID寻找连接的socket
- if (json.isObject()) {
- QJsonObject jsonObj = json.toObject();
- qint32 nId = jsonObj.value("from").toInt();//
- qint32 nWid = jsonObj.value("id").toInt();;//
- QString fileName = jsonObj.value("msg").toString();
- qDebug() << "get file" << jsonObj << m_clients.size();
- for (int i = 0; i < m_clients.size(); i++) {
- if (m_clients.at(i)->CheckUserId(nId, nWid))
- {
- m_clients.at(i)->StartTransferFile(fileName);
- return;
- }
- }
- }
- }
-
当服务端端通过accpt收到一个请求后,创建一个ClientSocket,处理客户端消息。
下面是一个Qt中的客户端socket管理类,用于与服务端进行通信。其中包含两个类,一个是ClientSocket,用于处理普通消息,另一个是ClientFileSocket,用于处理文件传输。
在ClientSocket中,包含了一些信号和槽函数,用于处理连接、数据接收、关闭等操作。同时还有一些私有函数,用于解析不同类型的消息,并且把解析后的数据发送到前台界面进行展示。
在ClientFileSocket中,主要有两个功能:文件接收和文件发送。对于文件接收,分别记录了已经接收到的数据大小、文件名大小、要接收的文件等信息;对于文件发送,记录了文件大小、已经发送的数据大小、剩余数据大小、要发送的文件等信息。同时还有一些私有函数,用于初始化socket、处理接收到的数据、更新发送进度等操作。
总的来说,这个类是一个很重要的网络通信模块,可以实现与服务端的双向交互,包括文字、图片、文件等。
- #ifndef CLIENTSOCKET_H
- #define CLIENTSOCKET_H
-
- #include
- #include
- #include
- #include
-
- /// 服务端socket管理类
- class ClientSocket : public QObject
- {
- Q_OBJECT
- public:
- explicit ClientSocket(QObject *parent = 0, QTcpSocket *tcpSocket = NULL);
- ~ClientSocket();
-
- int GetUserId() const;
- void Close();
- signals:
- void signalConnected();
- void signalDisConnected();
- void signalDownloadFile(const QJsonValue &json);
- void signalMsgToClient(const quint8 &type, const int &id, const QJsonValue &dataVal);
- public slots:
-
- private:
- QTcpSocket *m_tcpSocket;
- int m_nId;
-
- public slots:
- // 消息回发
- void SltSendMessage(const quint8 &type, const QJsonValue &json);
-
- private slots:
- void SltConnected();
- void SltDisconnected();
- void SltReadyRead();
-
- private:
- // 消息解析和抓转发处理
- void ParseLogin(const QJsonValue &dataVal);
- void ParseUserOnline(const QJsonValue &dataVal);
- void ParseLogout(const QJsonValue &dataVal);
- void ParseUpdateUserHead(const QJsonValue &dataVal);
-
- void ParseReister(const QJsonValue &dataVal);
- void ParseAddFriend(const QJsonValue &dataVal);
- void ParseAddGroup(const QJsonValue &dataVal);
- void ParseCreateGroup(const QJsonValue &dataVal);
-
- void ParseGetMyFriend(const QJsonValue &dataVal);
- void ParseGetMyGroups(const QJsonValue &dataVal);
-
- void ParseRefreshFriend(const QJsonValue &dataVal);
- void ParseRefreshGroups(const QJsonValue &dataVal);
-
- void ParseFriendMessages(const QByteArray &reply);
- void ParseGroupMessages(const QByteArray &reply);
- };
- ClientSocket::ClientSocket(QObject *parent, QTcpSocket *tcpSocket) :
- QObject(parent)
- {
- qRegisterMetaType
("QAbstractSocket::SocketError"); -
-
- m_nId = -1;
-
- if (tcpSocket == NULL) m_tcpSocket = new QTcpSocket(this);
- m_tcpSocket = tcpSocket;
-
- connect(m_tcpSocket, SIGNAL(readyRead()), this, SLOT(SltReadyRead()));//处理客户端信息
- connect(m_tcpSocket, SIGNAL(connected()), this, SLOT(SltConnected()));//处理登录成功信号
- connect(m_tcpSocket, SIGNAL(disconnected()), this, SLOT(SltDisconnected()));//处理登出信号
- }
处理客户端消息,根据消息类型进行不同的处理:
- void ClientSocket::SltReadyRead()
- {
- // 读取socket数据
- QByteArray reply = m_tcpSocket->readAll();
- QJsonParseError jsonError;
- // 转化为 JSON 文档
- QJsonDocument doucment = QJsonDocument::fromJson(reply, &jsonError);
- // 解析未发生错误
- if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) {
- // JSON 文档为对象
- if (doucment.isObject()) {
- // 转化为对象
- QJsonObject jsonObj = doucment.object();
- int nType = jsonObj.value("type").toInt();
- QJsonValue dataVal = jsonObj.value("data");
-
- switch (nType) {
- case Register:
- {
- ParseReister(dataVal);
- }
- break;
- case Login:
- {
- ParseLogin(dataVal);
- }
- break;
- case UserOnLine:
- {
- ParseUserOnline(dataVal);
- }
- break;
- case Logout:
- {
- ParseLogout(dataVal);
- Q_EMIT signalDisConnected();
- m_tcpSocket->abort();
- }
- break;
- case UpdateHeadPic:
- {
- ParseUpdateUserHead(dataVal);
- }
- break;
- case AddFriend:
- {
- ParseAddFriend(dataVal);
- }
- break;
- case AddGroup:
- {
- ParseAddGroup(dataVal);
- }
- break;
- case CreateGroup:
- {
- ParseCreateGroup(dataVal);
- }
- break;
- case GetMyFriends:
- {
- ParseGetMyFriend(dataVal);
- }
- break;
- case GetMyGroups:
- {
- ParseGetMyGroups(dataVal);
- }
- break;
-
- case RefreshFriends:
- {
- ParseRefreshFriend(dataVal);
- }
- break;
- case RefreshGroups:
- {
- ParseRefreshGroups(dataVal);
- }
- break;
- case SendMsg:
- case SendFile:
- case SendPicture:
- {
- ParseFriendMessages(reply);
- }
- break;
- case SendGroupMsg:
- {
- ParseGroupMessages(reply);
- }
- break;
- case SendFace:
- {
- ParseGroupMessages(reply);
- }
- break;
- case SendFileOk:
- {
-
- }
- break;
- case GetFile:
- {
- Q_EMIT signalDownloadFile(dataVal);
- }
- break;
- default:
- break;
- }
- }
- }
- }
登录的处理:
- void ClientSocket::ParseLogin(const QJsonValue &dataVal)
- {
- // data 的 value 也是JSON对象
- if (dataVal.isObject()) {
- QJsonObject dataObj = dataVal.toObject();
- QString strName = dataObj.value("name").toString();
- QString strPwd = dataObj.value("passwd").toString();
- QJsonObject jsonObj = DataBaseMagr::Instance()->CheckUserLogin(strName, strPwd);
-
- m_nId = jsonObj.value("id").toInt();
- qDebug() << "login" << jsonObj;
- //验证成功才向server发送信号说明可以将socket加入容器管理
- if (m_nId > 0) Q_EMIT signalConnected();
- // 发送查询结果至客户端
- SltSendMessage(Login, jsonObj);;
- }
- }
余略.....