QT编写TCP/UDP调试助手之多线程TCP服务器

3 篇文章 3 订阅

效果图:

TCP/UDP调试助手之TCP Server,支持一对一,一对多通信,主动断开客户端;多线程读写数据,线程数可设置,停止监听后及时释放资源。

一、前言

一般的多线程TCP服务器,是连接一个客户端,创建一个子线程,把它放到这个子线程中运行,这样能提高效率,但在大量客户端的时候线程频繁调度也会浪费性能,所以这里提出一种新的多线程方式,可设置最大线程数,一个线程可运行多个Socket,考虑线程负载的方式(线程里运行的Socket数),新的Socket连接总在负载最少的线程中创建和运行,根据硬件合理的设置线程数,可以避免线程频繁调度。

 

二、关键代码

主要讲创建子线程,客户端连接后如何分配给子线程运行。

1.在.pro文件中添加QT += network,要用到的头文件包括:

#include <QTcpSocket>
#include <QTcpServer>
#include <QThread>

2.为了更好的管理socket连接,自定义一个MyServer类,继承自QTcpServer,并且重写incomingConnection(qintptr socketDescriptor)方法。list_thread用来保存子线程指针。

class MyServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit MyServer(QObject *parent = nullptr);
    ~MyServer();
    void SetThread(int num);//设置线程数
    int GetMinLoadThread();//获取当前最少负载的线程ID

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

    MainWindow* mainwindow;
private:
    void incomingConnection(qintptr socketDescriptor);
public slots:
 void AddInf(MySocket* mysocket,int index);//添加信息
 void RemoveInf(MySocket* mysocket);//移除信息

};

3.子线程类MyThread 继承自QThread,定义一个辅助类SocketHelper用来帮助创建socket对象。

#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>

class MyServer;
class MySocket;

//Socket创建辅助类
class SocketHelper:public QObject
{
    Q_OBJECT
public:
    explicit SocketHelper(QObject *parent = nullptr);
    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);//移除信息
};

//子线程类
class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit  MyThread(QObject *parent);
    ~MyThread() override;
public:
    MyServer* myserver;
    SocketHelper* sockethelper;
    int ThreadLoad;//当前线程负载
    void run() override;
};

#endif // MYTHREAD_H

4.点击界面监听按钮,设置线程,线程数可设置最小值为1,也就没有子线程,只有主线程,变成单线程的tcp server。调用listen(ip, port)开启监听。

        m_tcpServer=new MyServer(this);
        //设置线程
        m_tcpServer->SetThread(ui->spinBox_threadNum->value()-1);
        //监听
        bool islisten = m_tcpServer->listen(ip, port);
        if(!islisten)
        {
            QMessageBox::warning(this,"错误",m_tcpServer->errorString());
            m_tcpServer->close();
            m_tcpServer->deleteLater();//释放
            m_tcpServer=nullptr;
            return;
        }

4.1根据输入的线程数创建子线程,并调用start()运行。

//创建子线程数
void MyServer::SetThread(int num)
{
    for(int i=0;i<num;i++)
    {
        list_thread.append(new MyThread(this));//新建线程
        list_thread[i]->ThreadLoad = 0;//线程负载初始0
        list_thread[i]->start();
    }
}

4.2线程run()里创建sockethelper对象,sockethelper的槽函数会在该线程里执行,重点来了,因为后面sockethelper的槽函数CreateSocket里会创建socket对象,socket对象的槽函数也会在该线程执行,这样就把socket读写放到子线程里运行了。

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();
}

5.如果没有重写incomingConnection,每当有客户端连接,可以从nextPendingConnection()返回一个QTcpSocket对象,但现在重写incomingConnection就不需要nextPendingConnection了,有客户连接会触发函数incomingConnection(qintptr socketDescriptor),参数socketDuescriptor是socket描述符,可以根据它初始化socket,把它传给对应线程里的sockethelper对象

//在对应线程里创建新socket连接
void MyServer::incomingConnection(qintptr socketDescriptor)
{
    //获取负载最少的子线程索引
    int index = GetMinLoadThread();
    if( index!= -1)//非UI线程时
    {
        //交给子线程运行
        emit list_thread[index]->sockethelper->Create(socketDescriptor,index);
    }
    else
    {
        //交给UI线程运行
        emit sockethelper->Create(socketDescriptor,index);
    }
}

5.1 遍历list_thread[N]->ThreadLoad负载值,找到最少负载(Socket数)的线程,-1表示UI线程,其他表示在list_thread里的索引。

//获取负载最少的线程索引
//-1:UI线程
int MyServer::GetMinLoadThread()
{
    //只有1个子线程
    if(list_thread.count()==1)
    {
        return 0;
    }
    //多个子线程
    else if(list_thread.count()>1)
    {
        int minload=list_thread[0]->ThreadLoad;
        int index=0;
        for(int i=1;i<list_thread.count();i++)
        {
            if(list_thread[i]->ThreadLoad<minload)
            {
                index = i;
                minload=list_thread[i]->ThreadLoad;
            }
        }
        return index;
    }
    //没有子线程
    return -1;
}

6. sockethelper的CreateSocket槽函数里会创建并用socketDescriptor初始化tcpsocket 对象,并连接一系列信号槽,接下来就可以通信了。

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

    MySocket* tcpsocket = new MySocket(this->myserver);
    tcpsocket->sockethelper = this;
    //初始化socket
    tcpsocket->setSocketDescriptor(socketDescriptor);
    //发送到UI线程记录信息
    emit AddList(tcpsocket,index);//

    if( index!= -1)//非UI线程时
    {
        //负载+1
        myserver->list_thread[index]->ThreadLoad+=1;//负载+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_add_serverMessage);
    //发送消息
    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);

}

7. socket的读写就不贴了,数据显示用信号和槽的方式和UI线程通信。

8. 每次结束监听时及时释放所有资源,避免内存溢出

MyServer::~MyServer()
{
    //释放所有socket
    while(list_information.count()>0)
    {
        emit list_information[0].mysocket->DeleteSocket();
        list_information.removeAt(0);
    }
    //清空combox
    while (this->mainwindow->ui->comboBox->count()>1) {
        this->mainwindow->ui->comboBox->removeItem(1);
    }

    //释放所有线程
    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;//
}

三、下载

tcp/udp调试助手​​​​​​​

  • 24
    点赞
  • 167
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哀歌与世无争

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值