QTcpServer多线程实现

目的:每个客户端连接的tcpSocket分别分配一个专门的线程来处理。

实现时分别继承QTcpServer和QTcpScoket实现出自己需要的类。

继承QTcpServer为每个客户端连接时分配线程,并接受处理tcpScoket的信号和槽、、还有发送信息,储存连接信息等。

继承QTcpScoket为处理通信数据和增加信号的参数,以便和tcpServer更好的配合。

首先是继承并重写QTcpServer的incomingConnection函数去自己实现tcpsocket连接的建立和分配。

其文档的默认描述为:

This virtual function is called by QTcpServer when a new connection is available. The socketDescriptor argument is the native socket descriptor for the accepted connection.

The base implementation creates a QTcpSocket, sets the socket descriptor and then stores the QTcpSocket in an internal list of pending connections. Finally newConnection() is emitted.

Reimplement this function to alter the server's behavior when a connection is available.

If this server is using QNetworkProxy then the socketDescriptor may not be usable with native socket functions, and should only be used with QTcpSocket::setSocketDescriptor().

Note: If you want to handle an incoming connection as a new QTcpSocket object in another thread you have to pass the socketDescriptor to the other thread and create the QTcpSocket object there and use its setSocketDescriptor() method.

译文(谷歌翻译和自己简单的更正):

当QTcpServer有一个新的连接时这个虚函数被调用。该socketDescriptor参数是用于接受连接的本地套接字描述符。

该函数会创建一个QTcpSocket,并设置套接字描述符为socketDescriptor,然后存储QTcpSocket在挂起连接的内部清单。最后newConnection()被发射。

重新实现这个函数来改变服务器的行为,当一个连接可用。

如果该服务器使用QNetworkProxy那么socketDescriptor可能无法与原生socket函数使用,并且只能用QTcpSocket:: setSocketDescriptor()中使用。

注意:如果你想处理在另一个线程一个新的QTcpSocket对象传入连接,您必须将socketDescriptor传递给其他线程,并创建了QTcpSocket对象存在并使用其setSocketDescriptor()方法。

所以我们必须先重写这个函数:

下面先附上继承QTcpServer的自己的类声明,代码注释个人以为挺详细了:

//继承QTCPSERVER以实现多线程TCPscoket的服务器。
class MyTcpServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit MyTcpServer(QObject *parent = 0);
    ~MyTcpServer();
signals:
    void connectClient(const int , const QString & ,const quint16 );//发送新用户连接信息
    void readData(const int,const QString &, quint16, const QByteArray &);//发送获得用户发过来的数据
    void sockDisConnect(const int ,const QString &,const quint16 );//断开连接的用户信息
    void sentData(const QByteArray &,const int);//向scoket发送消息
public slots:
    void setData(const QByteArray & data, const int  handle);//想用户发送消息
    void readDataSlot(const int, const QString &, const quint16,const QByteArray &);//发送获得用户发过来的数据
    void sockDisConnectSlot(int handle, QString ip, quint16 prot);//断开连接的用户信息

protected:
    void incomingConnection(qintptr socketDescriptor);//覆盖已获取多线程
private:
    QMap<int,myTcpSocket *> * tcpClient;
};

接着是我们重写void QTcpServer::incomingConnection(qintptr socketDescriptor)的实现:

void MyTcpServer::incomingConnection(qintptr socketDescriptor)
{
    myTcpSocket * tcpTemp = new myTcpSocket(socketDescriptor);
    QThread * thread = new QThread(tcpTemp);//把线程的父类设为连接的,防止内存泄漏
    //可以信号连接信号的,我要捕捉线程ID就独立出来函数了,使用中还是直接连接信号效率应该有优势
    connect(tcpTemp,&myTcpSocket::readData,this,&MyTcpServer::readDataSlot);//接受到数据
    connect(tcpTemp,&myTcpSocket::sockDisConnect,this,&MyTcpServer::sockDisConnectSlot);//断开连接的处理,从列表移除,并释放断开的Tcpsocket
    connect(this,&MyTcpServer::sentData,tcpTemp,&myTcpSocket::sentData);//发送数据
    connect(tcpTemp,&myTcpSocket::disconnected,thread,&QThread::quit);//断开连接时线程退出
    tcpTemp->moveToThread(thread);//把tcp类移动到新的线程
    thread->start();//线程开始运行

    tcpClient->insert(socketDescriptor,tcpTemp);//插入到连接信息中
    qDebug() <<"incomingConnection THREAD IS:" <<QThread::currentThreadId();
    //发送连接信号
    emit connectClient(tcpTemp->socketDescriptor(),tcpTemp->peerAddress().toString(),tcpTemp->peerPort());

}

用moveToThread来处理移动到新的线程。

其他的非重要的函数就不一一列出,但是别忘记在断开连接的槽中释放连接:

void MyTcpServer::setData(const QByteArray &data, const int handle)
{
    emit sentData(data,handle);
}

void MyTcpServer::sockDisConnectSlot(int handle, QString ip, quint16 prot)
{
    qDebug() <<"MyTcpServer::sockDisConnectSlot thread is:" << QThread::currentThreadId();

    myTcpSocket * tcp = tcpClient->value(handle);
    tcpClient->remove(handle);//连接管理中移除断开连接的socket
    delete tcp;//释放断开连接的资源、、子对象线程也会释放
    emit sockDisConnect(handle,ip,prot);
}

其次就是继承的QTcpSocket的声明,直接上代码把:

//继承QTcpSocket以处理接收到的数据
class myTcpSocket : public QTcpSocket
{
    Q_OBJECT
public:
    explicit myTcpSocket(qintptr socketDescriptor,QObject *parent = 0);

signals:
    void readData(const int,const QString &,const quint16,const QByteArray &);//发送获得用户发过来的数据
    void sockDisConnect(const int ,const QString &,const quint16 );//断开连接的用户信息
public slots:
    void thisReadData();//处理接收到的数据
    void sentData(const QByteArray & ,const int);//发送信号的槽
private:
    qintptr socketID;//保存id,== this->socketDescriptor();但是this->socketDescriptor()客户端断开会被释放,
                        //断开信号用this->socketDescriptor()得不到正确值
};

这个实现其实更简单了、、、就把主要的信号槽连接部分附上把,还有发送数据需要注意下,我是用的Qt,其中信号槽用的新语法,而且配合的C++11的lambda表达式,你如果不清楚C++11,你最好先去看下C++11的文档。

myTcpSocket::myTcpSocket(qintptr socketDescriptor, QObject *parent) :
    QTcpSocket(parent),socketID(socketDescriptor)
{
    this->setSocketDescriptor(socketDescriptor);
    //转换系统信号到我们需要发送的数据、、直接用lambda表达式了,qdebug是输出线程信息
    connect(this,&myTcpSocket::readyRead,this,&myTcpSocket::thisReadData); //连接到收到数据的处理函数
    connect(this,&myTcpSocket::readyRead, //转换收到的信息,发送信号
            [this](){
                qDebug() <<"myTcpSocket::myTcpSocket lambda readData thread is:" << QThread::currentThreadId();                 emit readData(socketID,this->peerAddress().toString(),this->peerPort() ,this->readAll());//发送用户发过来的数据
            });
    connect(this,&myTcpSocket::disconnected, //断开连接的信号转换
            [this](){
                qDebug() <<"myTcpSocket::myTcpSocket lambda sockDisConnect thread is:" << QThread::currentThreadId();                 emit sockDisConnect(socketID,this->peerAddress().toString(),this->peerPort());//发送断开连接的用户信息
            });

    qDebug() << this->socketDescriptor() << " " << this->peerAddress().toString()
                << " " << this->peerPort() << "myTcpSocket::myTcpSocket thread is " <<QThread::currentThreadId();
}

void myTcpSocket::sentData(const QByteArray &data, const int id)
{
    //如果是服务器判断好,直接调用write会出现跨线程的现象,所以服务器用广播,每个连接判断是否是自己要发送的信息。
    if(id == socketID)//判断是否是此socket的信息
    {
        qDebug() << "myTcpSocket::sentData" << QThread::currentThreadId();
        write(data);
    }
}

整篇代码中出现了n个qDebug() << ,这个是我为了查看运行所在的线程所设,实际应用中这些都没用的、、你自己删除把、、自己测试的例子和源码我还是保留了、、毕竟时间长了都忘得,留着那天一看就一目了然的、、

这个每个连接分配一个线程,连接太多很耗资源的、、您可以自己更改下,把多个连接移到一个线程,但是那样,你需要保存线程信息,更要小心线程的分配和释放时、、可以自己做下、、我也欢迎大家来探讨、、

最新更新:添加线程管理类(应该算个线程池),单例类。可预先设置线程数或者每个线程处理多少连接。原来的代码主要变动在新建断开连接处更新了、、详细请见代码。

全部的代码测试例子,服务端和客户端,我传到了我的github上了,附上项目地址:https://github.com/dushibaiyu/QtTcpThreadServer

 

欢迎大家转载分享,请转载时注明来源与作者、、谢谢、、、

《QTcpServer多线程实现》有20个想法

      1. Pancakes Everby Paula on August 6, 12in RecipesGuest post written by Paula of Whole InntSeiotsnourdough is my preference when it comes to preparing grains. If we were to compare the efficiency and health

  1. 你好!!我最近在学习qt的tcp编程,多线程的,不知道为什么,连接建立很成功,但是有时候服务端接收不到readyRead信号。您能帮忙看一下吗?代码很短的。其中 dbg()是个宏:qDebug() << __func__server_worker是工作线程,server是服务器主线程。#include "server.h"server_worker::server_worker(qintptr sd){ dbg(); this->sd = sd; this->tsock = new QTcpSocket(this);}void server_worker::init(){ dbg(); if (!this->tsock->setSocketDescriptor(sd)) { qDebug() << tsock->errorString(); return; } connect(this->tsock, &QTcpSocket::readyRead, this, &server_worker::read_data); connect(this->tsock, &QTcpSocket::disconnected, this, &server_worker::dis_conn);}void server_worker::read_data(){ dbg(); qDebug() << this->tsock->readAll();}void server_worker::dis_conn(){ dbg(); emit this->work_finished();}/////////////////////////////////////////////////////////////////////////////void server::start(){ dbg(); connect(&this->tcp_server, &QTcpServer::newConnection, this, &server::acpt_conn); if (!this->tcp_server.listen(QHostAddress::LocalHost, 8888)) { qDebug() << this->tcp_server.errorString(); this->tcp_server.close(); return; }}void server::acpt_conn(){ dbg(); server_worker *worker; QThread *thread; thread = new QThread; worker = new server_worker(this->tcp_server.nextPendingConnection()->socketDescriptor()); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &server_worker::init); connect(worker, &server_worker::work_finished, thread, &QThread::quit); connect(thread, &QThread::finished, worker, &server_worker::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start();}每次连接,server_worker::init()都能执行,但是客户端write数据的时候,服务端的readyRead信号有时接受不到。但是我改成单线程的服务端就没有任何问题。这是完整的客户端和服务端代码:http://pan.baidu.com/s/1kTkbUHh不胜感激!

    1. 你仔细看下文档、、你把你的socket放在这里建立就已经出错了、、、因为Qtcpserver已经为新连接创建好tcpsocket连接了、、你还要另外新建一个连接来同时监听这一个socket、、下面有没有什么一个socket不能跨线程操作的提示?如果你要实现就必须重写incomingConnection这个函数的、、因为Qt在这个函数中默认新建了一个TCPSOCKET来监听这个套接字了,然后存储在一个list当中,才发送的新连接信号、、退而求次的做法,你直接this->tsock = this->tcp_server.nextPendingConnection(),然后建立信号槽,把tsock->movetothread应该也可以、、、

        1. 你好,能把你的多线程代码发给我看下吗?我是多线程编程新手,一些操作中不知道怎么用我new的thread。cheqh_nchu@163.com谢谢了

      1. 这句话:Note: If you want to handle an incoming connection as a new QTcpSocket object in another thread you have to pass the socketDescriptor to the other thread and create the QTcpSocket object there and use its setSocketDescriptor() method.原来也是在重写incomingConnection()里实现啊,原先我理解错了

  2. 那个我看了一下GitHub你那边的代码,我想知道你那里emit了一些信号,但是没有connect(例如在service里面的sockDisConnect,readData, connectClient),这个是为了什么呢?不太明白那样做的目的,希望你能讲解一下。

  3. emit connectClient(tcpTemp->socketDescriptor(),tcpTemp->peerAddress().toString(),tcpTemp->peerPort());
    请问渡先生,这个 connectClient() 信号发给谁了? 代码中,也没看到用connect()连接有对应的槽函数

发表评论

电子邮件地址不会被公开。 必填项已用*标注