QT的TCP连接多线程实现

感谢这位博主的思路
源码直接点这里
qt的tcp服务器有两个socket一个是监听套接字(QTcpServer),一个是通信套接字(QTcpSocket),因为我只需要TCP服务器端线程的实现就将该部分移植整理了一下,因为我所用到就是在子线程里面进行socket的连接所只针对我移植后的代码进行梳理,当然博主的思路已经完美实现了TCP的多线程
由线程ID可见Socket客户端都是在子线程中进行连接
在这里插入图片描述
在客户端断开两个连接
在这里插入图片描述
服务器端断开后重新进行监听,客户端连接后子线程重新创建
在这里插入图片描述
主线程(服务器端)断开连接
在这里插入图片描述
界面没整很复杂就需要一个监听和停止监听,显示连接断开信息
在这里插入图片描述
就从按键开始说吧,清除按键就是对接收断开信息进行清除

void MainWindow::on_textClear_clicked()
{
    ui->TcpMessage->clear();
}

主要看一下监听/停止按键干了什么

void MainWindow::on_TcpConnect_clicked()
{
    if(this->m_tcpServer == nullptr)
    {
        m_tcpServer = new MyServer(this);
        //启动线程
        m_tcpServer->MyThreadStart();
        //监听
        bool islisten = m_tcpServer->listen(QHostAddress(ip), port);
        if(!islisten)
        {
            QMessageBox::warning(this,"错误",m_tcpServer->errorString());
            m_tcpServer->close();
            m_tcpServer->deleteLater();//释放
            m_tcpServer=nullptr;
            return;
        }
        ui->TcpMessage->append("开始监听"+ip+" "+QString::number(port));//消息框提示信息
        ui->TcpConnect->setText("停止");
    }
    else
    {
        m_tcpServer->close();
        delete m_tcpServer;
        m_tcpServer=nullptr;

        ui->TcpMessage->append("停止监听"+ip+" "+QString::number(port));//消息框提示信息
        ui->TcpConnect->setText("监听");
    }
}

先说一下MyServer是继承与QTcpServer的一个类,重写MyServer里incomingConnection这个函数在监听后,有socket进行连接时会自动进入该函数,而单线程有客户端连接的时候是通过newConnection信号,具体自行查阅

class MyServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit MyServer(QObject *parent = nullptr);
    ~MyServer();
    void MyThreadStart();

    SocketHelper* sockethelper;//socket创建辅助对象
    QList<MyThread*> list_thread;//线程列表
    QList<SocketInformation> list_information;//socket信息列表
    MainWindow *mainwindow;

public slots:
   void AddInf(MySocket* mysocket,int index);//添加信息
   void RemoveInf(MySocket* mysocket);//移除信息

private:
    void incomingConnection(qintptr socketDescriptor);//重写这个函数,有客户端连接会自动调用该函数

};

其中list_thread在我的程序中其实可以不用列表,因为我只有一个子线程,而博主有多个子线程,list_information里面保存了所有socket客户端的连接,包括主线程和子线程
回到监听按键代码

m_tcpServer = new MyServer(this);

在构造MyServer时候指明父对象也就是mainwindow

MyServer::MyServer(QObject *parent) :
    mainwindow(static_cast<MainWindow*>(parent))
{
    //在线程内创建对象,槽函数在这个线程中执行
    this->sockethelper=new SocketHelper(this);
    //注册信号类型
    qRegisterMetaType<qintptr>("qintptr");
    //主线程信号和槽
    connect(sockethelper,&SocketHelper::Create,sockethelper,&SocketHelper::CreateSocket);
    connect(sockethelper,&SocketHelper::AddList,this,&MyServer::AddInf);
    connect(sockethelper,&SocketHelper::RemoveList,this,&MyServer::RemoveInf);
}

在MyServer类的定义中看见有一个mainwindow指针,它指向的是主界面的mainwindow,也就是main中的w,这里面还包含一个类SocketHelper,先看一下声明

//Socket创建辅助类
class SocketHelper:public QObject
{
    Q_OBJECT
public:
    explicit SocketHelper(QObject *parent);
    MyServer* myserver;
public slots:
    void CreateSocket(qintptr socketDescriptor,int index);//创建socket
signals:
    void Create(qintptr socketDescriptor,int index);//创建
    void AddList(MySocket* tcpsocket,int index);//添加信息
    void RemoveList(MySocket* tcpsocket);//移除信息
};

博主称该类为socket辅助类,其功能就是对socket进行创建,添加,移除,在此类中有一个MyServer指针,再来看看它的构造函数

SocketHelper::SocketHelper(QObject *parent):
    myserver(static_cast<MyServer*>(parent))
{
}

myserver(static_cast<MyServer*>(parent))这句话也就是说声明时myserver就是父对象,父对象是谁回到myserver.cpp中看MyServer构造函数this->sockethelper=new SocketHelper(this);,也就是说传入的是当前构造的MyServer,在声明时候构造的,再回到mianwindow.cpp,m_tcpServer = new MyServer(this);,也就是说SocketHelper的myserver指针指向的是监听按键中new出来的MyServer,而MyServer继承自QTcpServer,也就是监听套接字
继续回到MyServer中,只剩下以下代码

//在线程内创建对象,槽函数在这个线程中执行
    this->sockethelper=new SocketHelper(this);
 //注册信号类型
   qRegisterMetaType<qintptr>("qintptr");
   //主线程信号和槽
   connect(sockethelper,&SocketHelper::Create,sockethelper,&SocketHelper::CreateSocket);
   connect(sockethelper,&SocketHelper::AddList,this,&MyServer::AddInf);
   connect(sockethelper,&SocketHelper::RemoveList,this,&MyServer::RemoveInf);

为什么需要注册qintptr,因为在多线程的socket信号槽传递中是通过qintptr类型(有点牵强,自行百度…),下面就是信号和槽的连接了,包含socket的创建、添加、移除,上面的this就表示以下三个信号都是主线程中进行,后面会有解释
回到监听按键的代码

 m_tcpServer->MyThreadStart();//启动线程
void MyServer::MyThreadStart()//启动线程
{
    list_thread.append(new MyThread(this));
    list_thread[0]->start();
}

qt线程就是写一个类继承自QThread函数,重写run函数,通过start启动run函数,先看看MyThread声明和构造函数

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent);
    ~MyThread() override;

public:
    MyServer* myserver;
    SocketHelper* sockethelper;
    void run() override;
};
MyThread::MyThread(QObject *parent):
    myserver(static_cast<MyServer*>(parent)),
    sockethelper(nullptr)
{
}

也就是说线程里的myserver指针也是指向监听按键中new出来的MyServer,也就是主线程的监听套接字,再来看看run函数

void MyThread::run()
{
    //在线程内创建对象,槽函数在这个线程中执行
    this->sockethelper=new SocketHelper(this->myserver);
    connect(sockethelper,&SocketHelper::Create,sockethelper,&SocketHelper::CreateSocket);
    connect(sockethelper,&SocketHelper::AddList,myserver,&MyServer::AddInf);
    connect(sockethelper,&SocketHelper::RemoveList,myserver,&MyServer::RemoveInf);

    exec();
}

这里面重新定义了一个SocketHelper,但是其父对象还是指向监听按键中new出来的MyServer,但是其槽函数是在子线程中注册的
回到监听按键代码,监听部分就剩以下

bool islisten = m_tcpServer->listen(QHostAddress(ip), port);
 if(!islisten)
   {
       QMessageBox::warning(this,"错误",m_tcpServer->errorString());
       m_tcpServer->close();
       m_tcpServer->deleteLater();//释放
       m_tcpServer=nullptr;
       return;
   }
   ui->TcpMessage->append("开始监听"+ip+" "+QString::number(port));//消息框提示信息
   ui->TcpConnect->setText("停止");

就是开始监听,当有客户端连接后,会自动进入到incomingConnection函数中,看看连接后incomingConnection函数做了什么

void MyServer::incomingConnection(qintptr socketDescriptor)
{
    if(list_thread.count() != 0)//启动了子线程
    {
        emit list_thread[0]->sockethelper->Create(socketDescriptor,1);//在子线程中创建连接
    }
    else    //只有主线程
    {
        emit sockethelper->Create(socketDescriptor,0);
    }
}

这里面就是通过对线程列表进行判断,如果没有启动子线程,那么就发射emit sockethelper->Create(socketDescriptor,0);,这里对应的槽就是主线程中的创建,记得上面提到在MyServer构造时对应的信号和槽在主线程中而emit list_thread[0]->sockethelper->Create(socketDescriptor,1);对应的信号和槽是在run函数中,看看创建连接信号里面干嘛了

void SocketHelper::CreateSocket(qintptr socketDescriptor ,int index)
{
    qDebug()<<"subThread:"<<QThread::currentThreadId();

    MySocket* tcpsocket = new MySocket(this->myserver);
    tcpsocket->sockethelper = this;
    //初始化socket
    tcpsocket->setSocketDescriptor(socketDescriptor);//设置本 socket 唯一标识符

    //发送到UI记录信息
    emit AddList(tcpsocket,index);

    if(index == 1)//在子线程中
    {
        //关联释放socket,非UI线程需要阻塞
        connect(tcpsocket , &MySocket::DeleteSocket , tcpsocket, &MySocket::deal_disconnect,Qt::ConnectionType::BlockingQueuedConnection);
    }
    else
    {
        connect(tcpsocket , &MySocket::DeleteSocket , tcpsocket, &MySocket::deal_disconnect,Qt::ConnectionType::AutoConnection);
    }

    //关联显示消息
    connect(tcpsocket,&MySocket::AddMessage,myserver->mainwindow,&MainWindow::on_addServerMessage);
    //发送消息
    connect(tcpsocket,&MySocket::WriteMessage,tcpsocket,&MySocket::deal_write);
    //关联接收数据
    connect(tcpsocket , &MySocket::readyRead , tcpsocket , &MySocket::deal_readyRead);
    //关联断开连接时的处理槽
    connect(tcpsocket , &MySocket::disconnected , tcpsocket, &MySocket::deal_disconnect);

    QString ip = tcpsocket->peerAddress().toString();
    quint16 port = tcpsocket->peerPort();
    QString message = QString("[%1:%2] 已连接").arg(ip).arg(port);
    //发送到UI线程显示
    emit tcpsocket->AddMessage(message);
}

socketDescriptor 就是该客户端的唯一标识,MySocket是继承自QTcpSocket,其声明和构造函数如下

class MySocket : public QTcpSocket
{
    Q_OBJECT
public:
    explicit MySocket(QObject *parent = nullptr);
    ~MySocket();

    MyServer* m_tcpServer;
    SocketHelper* sockethelper;
signals:
    void AddMessage(QString data);//发送给UI显示
    void WriteMessage(QByteArray ba);//UI发送过来数据
    void DeleteSocket();//主动关闭socket
public slots:
    void deal_readyRead();//读取数据槽函数
    void deal_disconnect();//断开连接槽函数
    void deal_write(QByteArray ba);//写入数据槽函数
};
MySocket::MySocket(QObject *parent):
    m_tcpServer(static_cast<MyServer*>(parent))
{
}

回到创建连接的函数就可以知道new MySocket(this->myserver);这个父对象也是主线程中监听按键new出来的MyServer
, 继续emit AddList(tcpsocket,index);

//添加socket信息
void MyServer::AddInf(MySocket* mysocket,int index)
{
    SocketInformation inf;

    QString ip = mysocket->peerAddress().toString();
    quint16 port = mysocket->peerPort();
    QString str_inf = QString("[%1:%2]").arg(ip).arg(port);

    inf.str_inf=str_inf;
    inf.mysocket=mysocket;
    inf.threadIndex=index;
    this->list_information.append(inf);
}

这里将CreateSocket函数中开始new出来的MySocket进行了保存,保存进list_information,回到CreateSocket函数

if(index != 0)//在子线程中
    {
        //关联释放socket,非UI线程需要阻塞
        connect(tcpsocket , &MySocket::DeleteSocket , tcpsocket, &MySocket::deal_disconnect,Qt::ConnectionType::BlockingQueuedConnection);
    }
    else
    {
        connect(tcpsocket , &MySocket::DeleteSocket , tcpsocket, &MySocket::deal_disconnect,Qt::ConnectionType::AutoConnection);
    }

这里是在服务器这边停止监听时主动断开服务器对客户端socket连接发出的信号

void MySocket::deal_disconnect()
{
    MySocket* tcpsocket=static_cast<MySocket*>(sender());
    //断开socket
    tcpsocket->abort();
    //消息提示断开
    QString ip = tcpsocket->peerAddress().toString();
    quint16 port = tcpsocket->peerPort();
    QString message = QString("[%1:%2] 已断开").arg(ip).arg(port);
    //发送到UI线程显示
    emit AddMessage(message);
    //断开所有信号连接
    tcpsocket->disconnect();
    //发送到UI线程移除信息
    emit this->sockethelper->RemoveList(tcpsocket);
   //释放
  tcpsocket->deleteLater();
}

这就是对断开做相应的处理,继续回到创建socket函数中

//关联显示消息
 connect(tcpsocket,&MySocket::AddMessage,myserver->mainwindow,&MainWindow::on_addServerMessage);
 //发送消息
 connect(tcpsocket,&MySocket::WriteMessage,tcpsocket,&MySocket::deal_write);
 //关联接收数据
 connect(tcpsocket , &MySocket::readyRead , tcpsocket , &MySocket::deal_readyRead);
 //关联断开连接时的处理槽
 connect(tcpsocket , &MySocket::disconnected , tcpsocket, &MySocket::deal_disconnect);

 QString ip = tcpsocket->peerAddress().toString();
 quint16 port = tcpsocket->peerPort();
 QString message = QString("[%1:%2] 已连接").arg(ip).arg(port);
 //发送到UI线程显示
 emit tcpsocket->AddMessage(message);

这些信号和槽函数看注释就能理解了,其中博主的一个槽函数名字定义有点问题报以下提示,于是我就把槽函数名字换为on_addServerMessage
在这里插入图片描述
至此监听流程已经完了,回到监听按键代码部分

m_tcpServer->close();
delete m_tcpServer;
m_tcpServer=nullptr;

ui->TcpMessage->append("停止监听"+ip+" "+QString::number(port));//消息框提示信息
ui->TcpConnect->setText("监听");

就是删除线程,然后对socket进行释放,为什么所有的MyServer指针都是指向这里的m_tcpServer,因为在该对象析构时候对所有socket进行删除处理

MyServer::~MyServer()
{
    //释放所有socket
    while(list_information.count()>0)
    {
        emit list_information[0].mysocket->DeleteSocket();
        list_information.removeAt(0);
    }

    //释放所有线程
    while(list_thread.count()>0)
    {
        list_thread[0]->quit();
        list_thread[0]->wait();//等待退出
        list_thread[0]->deleteLater();//释放
        list_thread.removeAt(0);
    }

    //UI线程里的sockethelper
    sockethelper->disconnect();
    delete  this->sockethelper;//
}

不管是主线程socket还是子线程socket都会添加到list_information中,因此主动断开时会对所有连接的socket进行释放,下面是对线程等进行释放。该博主的实现很巧妙,毕竟自己功底还不够,哈哈,以上只是个人理解

  • 12
    点赞
  • 139
    收藏
    觉得还不错? 一键收藏
  • 31
    评论
### 回答1: Qt 中使用多线程连接 TCP 服务器可以通过以下步骤实现: 1. 创建 QTcpSocket 类的对象,并连接到指定的 IP 地址和端口。 2. 创建 QThread 类的对象,并在该线程中运行 QTcpSocket 的对象。 3. 在 QTcpSocket 的对象中处理与服务器的连接,接收和发送数据。 4. 通过信号和槽机制在主线程中处理数据,以实现多线程通信。 希望这个回答对您有所帮助! ### 回答2: Qt Tcp多线程连接服务器通常是为了实现并发处理多个客户端请求的需求。在Qt中,可以使用Qt网络模块提供的QTcpServer和QTcpSocket类实现TCP服务器和客户端的功能,并结合多线程技术来处理多个客户端的连接和请求。 首先,创建一个QTcpServer对象,并使用其listen函数指定服务器的端口号。然后,可以使用QObject的moveToThread函数将QTcpServer对象移动到一个新的线程中,以便实现多线程处理。 在新的线程中,使用QTcpServer的连接信号与槽机制,将新连接的客户端套接字传递给一个自定义的处理类。此处理类继承自QObject,并包含一个QTcpSocket对象作为成员变量,用于与客户端进行通信。 在处理类中,使用QTcpSocket的readyRead信号与槽机制,接收并处理客户端发送的数据。可以使用QObject的moveToThread函数将QTcpSocket对象移动到同一个新的线程中,以便实现多线程处理。 当客户端断开连接时,可以使用QTcpSocket的disconnected信号与槽机制,进行相应的处理。 需要注意的是,在多线程处理中,需要考虑线程间的同步和互斥机制,以避免竞态条件和资源冲突的问题。可以使用Qt提供的互斥量、条件变量等工具类来实现线程间的同步和互斥操作。 总结来说,Qt Tcp多线程连接服务器的步骤包括创建服务器对象、指定端口号、移动服务器对象到新线程,创建处理类对象、将处理类对象移动到同一线程,处理客户端连接请求、接收和处理数据、处理断开连接事件,并注意线程间的同步和互斥机制。 ### 回答3: Qt是一个流行的跨平台C++开发框架,提供了丰富的功能和工具来开发应用程序。其中,Qt提供了一个Tcp模块,用于实现基于Tcp协议的网络通信。而多线程可以使程序能够同时处理多个任务,提高程序的性能和响应速度。 在使用Qt进行Tcp多线程连接服务器时,可以按照以下步骤进行操作: 1. 引入相应的头文件:在需要使用Tcp模块的代码中,首先需要引入相应的头文件,以提供相应的函数和类。 2. 创建Tcp服务器:使用QtQTcpServer类创建一个Tcp服务器对象,然后调用其listen函数指定服务器监听的地址和端口。 3. 实现服务器的连接槽函数:创建一个槽函数来处理客户端的连接请求,在其中可以使用QTcpServer的nextPendingConnection函数获取新的客户端套接字,并进行相关处理。 4. 创建Tcp客户端:使用QtQTcpSocket类创建一个Tcp客户端对象,然后调用其connectToHost函数指定连接的服务器地址和端口。 5. 实现客户端的连接槽函数:创建一个槽函数来处理客户端的连接状态,可以根据连接结果进行相应的处理。 6. 处理服务器收发数据:对于服务器端和客户端,可以使用QTcpSocket类的write函数发送数据,使用readyRead信号和read函数接收数据。 7. 使用多线程:可以使用QtQThread类创建一个线程对象,并将需要多线程处理的任务放入该对象的run函数中,以实现程序的并发处理。 通过以上步骤,可以使用Qt进行Tcp多线程连接服务器的相关开发。在具体实现过程中,还可以根据具体需求进行相应的扩展和优化,以满足应用程序的要求。
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值