博客地址已更改,文章數量較多不便批量修改,若想訪問源文請到 coologic博客 查閱,網址:www.coologic.cn
如本文記錄地址為 techieliang.com/A/B/C/ 請改為 www.coologic.cn/A/B/C/ 即可查閱
版權聲明:若無來源注明, Techie亮博客文章均為原創。 轉載請以鏈接形式標明本文標題和地址:
本文標題:QTcpServer實現多客戶端連接 本文地址: https://www.techieliang.com/2017/12/760/
1. 介紹
QTcpServer使用請見:QTcpSocket-Qt使用Tcp通訊實現服務端和客戶端
QTcpServer類默認提供的只有無參數的newConnection的信號,這樣雖然知道有人連接了,並且可以通過nextPendingConnection獲取連接的socket,但並不便於管理,尤其是在連接斷開以后無法判斷具體那個斷開了,因為QTcpSocket只提供了無參的disconnected信號。。。
這樣就算在newConnection是存儲一個list或者map,也無法在disconnected是知道具體是那一項斷開連接,給不同的QTcpSocket的信號指向不同的槽。
實際上socket有自己的句柄,並通過下述函數在初步連接時就賦予了對應的socketDescriptor
- virtual void incomingConnection(qintptr socketDescriptor)
當有client連接時,首先是此方法被調用,可自行在此方法內建立QTcpSocket並將socketDescriptor值賦予socket,並在socket斷開時告知此標識符
2. 范例
源碼請見GitHub:QtOtherModuleExamples
tcp_server.h
- #ifndef TCP_SERVER_H
- #define TCP_SERVER_H
- #include <QTcpServer>
- namespace tcp_server_private {
- class TcpServerPrivate;
- }
- class QTcpSocket;
- /**
- * @brief Tcp多客戶端服務器
- */
- class TcpServer : public QTcpServer {
- Q_OBJECT
- public:
- /**
- * @brief 構造函數
- * @param parent 父QObject
- */
- explicit TcpServer(QObject *parent = Q_NULLPTR);
- /**
- * @brief 析構函數
- * 非多線程模式行為:關閉所有連接后析構
- * 多線程模式行為:關閉所有連接及線程池后析構
- */
- ~TcpServer();
- signals:
- /**
- * @brief 客戶端連入
- * @param 連接句柄
- * @param socket指針
- */
- void ClientConnected(qintptr, QTcpSocket*);//發送新用戶連接信息
- /**
- * @brief socket已斷開連接
- * 若需要在socket后析構后進行操作的可連接此信號
- * @param 連接句柄
- */
- void ClientDisconnected(qintptr);
- /**
- * @brief 主動斷開連接信號
- * 若服務端想要主動斷開與客戶端連接將會發出此信號
- * 此信號發出這表明進行斷開操作不表明斷開成功,成功以SocketDisconnected信號為准
- * @param 連接句柄
- */
- void InitiativeDisConnectClient(qintptr);
- protected slots:
- /**
- * @brief 客戶端已斷開槽
- * 此槽與客戶端的已斷開信號連接
- * @param handle
- */
- void ClientDisconnectedSlot(qintptr handle);
- protected:
- /**
- * @brief 重寫-有連接到來
- * 連接到來不一定連接,會根據maxPendingConnections決定是否連接
- * @param handle 連接句柄
- */
- virtual void incomingConnection(qintptr handle);
- private:
- tcp_server_private::TcpServerPrivate *private_;
- };
- #endif // TCP_SERVER_H
tcp_server.cpp
- #include "tcp_server.h"
- #include "tcp_server_private.h"
- //構造函數
- TcpServer::TcpServer(QObject *parent)
- : QTcpServer(parent),
- private_(new tcp_server_private::TcpServerPrivate) {
- }
- //析構函數
- TcpServer::~TcpServer() {
- for(tcp_server_private::TcpSocket *client : private_->clients.values()) {
- client->disconnectFromHost();
- auto handle = client->socketDescriptor();
- client->deleteLater();
- //告知其他調用者 當前socket斷開,避免有需要在socket后執行的方法
- emit ClientDisconnected(handle);
- }
- if(private_)
- delete private_;
- this->close();
- }
- //重寫-有連接到來
- void TcpServer::incomingConnection(qintptr handle) {
- //超出最大練級數量關閉連接
- if (private_->clients.size() > maxPendingConnections()) {
- QTcpSocket tcp;
- tcp.setSocketDescriptor(handle);
- tcp.disconnectFromHost();
- return;
- }
- auto client_socket = new tcp_server_private::TcpSocket(handle);
- Q_ASSERT(client_socket->socketDescriptor() == handle);
- //socket斷開連接的信號與server槽連接
- connect(client_socket,
- &tcp_server_private::TcpSocket::ClientDisconnected,
- this,
- &TcpServer::ClientDisconnectedSlot);
- //主動斷開連接的操作
- connect(this,
- &TcpServer::InitiativeDisConnectClient,
- client_socket,
- &tcp_server_private::TcpSocket::DisconnectSocket);
- //map記錄
- private_->clients.insert(handle, client_socket);
- qDebug()<<handle<<"connected";
- emit ClientConnected(handle, client_socket);
- }
- //客戶端已斷開槽
- void TcpServer::ClientDisconnectedSlot(qintptr handle) {
- //map中移除
- private_->clients.remove(handle);
- qDebug()<<handle<<"disconnected";
- //發出信號
- emit ClientDisconnected(handle);
- }
private
- #ifndef TCP_SERVER_PRIVATE_H
- #define TCP_SERVER_PRIVATE_H
- #include <QTcpSocket>
- namespace tcp_server_private {
- class TcpSocket : public QTcpSocket {
- Q_OBJECT
- public:
- /**
- * @brief 構造函數
- * @param socketDescriptor 連接句柄
- * @param parent 父QObject
- */
- TcpSocket(qintptr handle, QObject *parent = 0);
- signals:
- /*
- * 已斷開連接信號
- */
- void ClientDisconnected(qintptr);
- public slots:
- /**
- * @brief 斷開連接
- * @param handle 連接句柄
- */
- void DisconnectSocket(qintptr handle);
- private:
- qintptr handle_;
- };
- /**
- * @brief Tcp多客戶端服務器私有類
- */
- class TcpServerPrivate {
- public:
- QMap<int, TcpSocket *> clients; ///所有連接的map
- };
- }//tcp_server_private
- #endif // TCP_SERVER_PRIVATE_H
- //cpp
- #include "tcp_server_private.h"
- namespace tcp_server_private {
- //構造函數
- TcpSocket::TcpSocket(qintptr handle, QObject *parent)
- : QTcpSocket(parent), handle_(handle) {
- this->setSocketDescriptor(handle_);
- //斷開連接消息
- connect(this,&TcpSocket::disconnected,
- [&](){
- this->deleteLater();
- emit this->ClientDisconnected(handle_);
- });
- }
- //主動斷開連接槽
- void TcpSocket::DisconnectSocket(qintptr handle) {
- if (handle == handle_)
- disconnectFromHost();
- }
- }
- incomingConnection首先判斷是否超出最大連接數量,超出就斷開新鏈接,最大連接數量在maxPendingConnections方法獲取,而當前已連接client在自定義server的TcpServerPrivate::clients這個QMap記錄,此map記錄了socketDescriptor和socket指針的映射關系
- 若未達最大連接數量,則連接創建新的tcpsocket,並將socketDescriptor賦值到socket對象,最后發出ClientConnected信號,此信號帶有新鏈接的socket的指針,可以用於自定義收發信號槽。
- socket對象的disconnected信號直接connect到了lambda表達式以發出新的信號,新信號ClientDisconnected,並帶有socketDescriptor句柄標識符,從而避免了socket斷開連接后不知道具體哪個斷開
- socket在創建時均吧ClientDisconnected信號與server的ClientDisconnectedSlot槽連接,當有連接斷開時會動態維護map記錄
- server析構時以disconnectFromHost方法主動斷開和所有客戶端連接,若需要等客戶端先斷開可以用waitForDisconnected
上述范例將TcpSocket定義在tcp_server_private命名空間不對外可見,且TcpServer返回值也是QTcpSocket,若需要繼承QTcpSocket做更多自定義需要將TcpSocket移出