Qt多线程处理网络请求

问题来源

问题来自我初学时看的一个Qt视频,其中博主处理子线程的方法错误,并做了修改后,没有完成下面问题描述中的第三点中的显示客户端发送的信息(在UI上),于是我结合自己所学的内容来解决这个问题。视频在这 【QT快速入门 | 最简单最简洁的QT入门教程 | 嵌入式UI】

问题描述

1.客户端向服务端发起连接时,服务端显示IP地址和端口号(服务器监听指定端口)
2.在客户端与服务端成功建立连接后,客户端新开一个窗口用于发送信息给服务端
3.当客户端点击发送时,要求服务端在界面上显示该信息,并且要求处理过程放在子线程

UI设计

1.客户端的设计

客户端需要有两个界面,分别是连接的主界面,和连接成功后的发送信息界面,如下图所示:

a.主界面

在这里插入图片描述

b.发送信息界面

在这里插入图片描述

2.服务端的设计

在这里插入图片描述

逻辑设计

网络的框架

1.客户端使用QTCpSocket用于建立连接以及信息的发送(没有设计接收)
2.服务端使用QTcpServer用于监听指定端口,当有新的连接时处理该连接,随后使用在子线程中根据socketDescription建立QTCpSocket对象,并用于信息的接收。

线程的通信

1.主线程(myserver)中有新连接时,将incomingConnection函数中的socketDescriptor传递给子线程所在的类,同时用connect将所在类的信号与界面的槽函数连接。
2.实际上子线程所在的类的构造函数还属于主线程,但都在构造函数中将socketDescriptor传递给全局变量,方便子线程所在区域使用。
3.mythread.cpp的run函数和clienthand.cpp里除了构造函数都属于子线程,我们在里面创建QTCpSocket对象,用于接收客户端信息,同时触发信号将信息传给界面的槽函数。

客户端结构和设计

结构如下图所示:
在这里插入图片描述
widget.ui为主界面,chat.ui为发送信息.下面展示客户端的核心部分,详细解释也在代码中体现:

widget.cpp的核心部分
//widget类的构造函数
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //新建一个socket对象用于建立连接以及信息的收发,this表示将widget作为socket的父对象
    socket=new QTcpSocket(this); 
    
	//当连接断开时提示
    connect(socket,&QTcpSocket::disconnected,[this](){
        QMessageBox::warning(this,"warning","disconnected to server");
    });
}

//点击connect时触发的槽函数
void Widget::on_connectBtn_clicked()
{
	//获取服务器地址和端口lineEdit的输入信息
    QString ipAddress=ui->addressLineEdit->text();
    QString port=ui->portLineEdit->text();

	//向服务器发起连接,并指定上述端口和地址(127.0.0.1)
    socket->connectToHost(QHostAddress(ipAddress),port.toUShort());

	//当连接建立时触发,将widget界面隐藏,同时创建Chat对象将socket传入用于通信,显示Chat界面
    connect(socket,&QTcpSocket::connected,[this](){
        QMessageBox::information(this,"information","connected to server");
        this->hide();
        Chat *c=new Chat(socket);
        c->show();
    });
}
chat.cpp的核心部分
#include "chat.h"
#include "ui_chat.h"

Chat::Chat(QTcpSocket *S,QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Chat)
{
    ui->setupUi(this);
    socket=S;//接收来widget传入的socket

}

void Chat::on_sendButton_clicked()
{
    QByteArray ba;
    ba.append(ui->inputLineEdit->text());
    socket->write(ba);//发送信息给服务器
    qDebug()<<"信息已发送";
}


服务端结构和设计

结构如下图所示:
在这里插入图片描述
服务端使用了两种实现子线程处理方法:
1.通过mythread实现QThread,通过里面的run函数来处理子线程中的内容,忽略clienthandler。
2.通过在mytcpserver中使用movetoThread将clienthandler处理事件的过程放入子线程,忽略mythread。

widget.cpp的核心部分

作用:显示客户端连接的信息,包括端口、地址和客户端发送的内容
如何显示:通过信号与槽,信号在子线程中发送

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

//这里创建了myTcpServer实例,并将widget作为其父对象,myTcpServer实现了QTcpServer
    server = new myTcpServer(this); 
    server->listen(QHostAddress::AnyIPv4,PORT);    //    监听指定端口
}

//槽函数,用于接受信号,并将信息显示在界面上,connect放在myTcpServer中
void Widget::clientInfoSlot(QStringList list)
{
    ui->receiveLineEdit->setText(list.at(2));
    ui->ciaLineEdit->setText(list.at(0));
    ui->cportLineEdit->setText(list.at(1));
}

mytcpserver.cpp的核心部分

作用:处理新的连接,获得socketDescriptor用于构建socket对象,以及将子线程的信号与widget的槽函数进行connect。

#include "mytcpserver.h"

#include <QThread>

myTcpServer::myTcpServer(QObject *parent)
{
	//方便将子线程信号传回widget
    widget=(Widget*)parent;
}

//有客户端连接触发该函数,注释的代码为第二种方法,其余为第一种方法
void myTcpServer::incomingConnection(qintptr socketDescriptor){

    qDebug()<<"incomingConnection开始工作,线程为:"<<QThread::currentThreadId();

//    myThread *thread=new myThread(socketDescriptor,this);
//    connect(thread,&myThread::sendBackToWidget,widget,&Widget::clientInfoSlot);
//    thread->start();
//    将线程发出的信号触发widget的槽函数

	//创建线程
    QThread *subThread=new QThread;
    //将socketDescriptor传入事件处理的类,0表示空指针,不能传入this,为该类指定父对象
    ClientHandler *clienthandle=new ClientHandler(socketDescriptor,0);
    //将子线程中接收到的信息传回widget
    connect(clienthandle,&ClientHandler::sendBackToWidget,widget,&Widget::clientInfoSlot);

	//将该对象移动到子线程并启动
    clienthandle->moveToThread(subThread);
    subThread->start();
    //这里触发clienthandle中的workSlot槽函数(用于创建socket对象和将端口及ip传回widget)
    connect(this,&myTcpServer::signal_to_thread,clienthandle,&ClientHandler::workSlot);
    //发送信号
    emit signal_to_thread();
}

clienthandler.cpp的核心部分
#include "clienthandler.h"

ClientHandler::ClientHandler(qintptr sd,QObject *parent) : QObject(parent)
{
	//接收socketDescription用于创建socket对象
    socketDescriptor=sd;
    qDebug()<<"ClientHandler构造函数工作中,线程为:"<<QThread::currentThreadId();
}

void ClientHandler::workSlot(){
    qDebug()<<"线程中的事件workSlot开始工作,线程为:"<<QThread::currentThreadId();
    socket=new QTcpSocket;
    socket->setSocketDescriptor(socketDescriptor);//根据文件描述符创建socket
	
	//当有新数据时触发receiveInfo用于处理该数据
    connect(socket,&QTcpSocket::readyRead,this,&ClientHandler::receiveInfo);

	//添加ip地址和端口,添加空字符是给list[2]占个位置,并发送信号,将该信息传回widget
    QString ip_address=socket->peerAddress().toString();
    list.append(ip_address);
    list.append(QString::number(socket->peerPort()));
    list.append(" ");
    emit sendBackToWidget(list);
}

//客户端发送信息给服务端时做如下处理
void ClientHandler::receiveInfo(){

    qDebug()<<"事件receiveInfo开始工作,线程为:"<<QThread::currentThreadId();
	//将客户端的信息一次读取(readAll读完会清空缓存,就没东西读了),随后发送给widget
    QByteArray ba= socket->readAll();
    QString message=QString(ba);

    list[2]=message;
    qDebug()<<"信息装填如下:"<<list.at(0)<<" "<<list.at(1)<<" "<<list.at(2);
    emit sendBackToWidget(list);
}

mythread.cpp的核心部分

作用:在子线程中处理socket数据接收

#include "mythread.h"

myThread::myThread(qintptr sd,QObject *parent)
{
    qDebug()<<"myThread开始工作,线程为:"<<QThread::currentThreadId();
    socketDescription=sd;
}

//run函数运行属于子线程
void myThread::run()
{
    qDebug()<<"线程中的事件run开始工作,线程为:"<<QThread::currentThreadId();
    socket=new QTcpSocket;
    socket->setSocketDescriptor(socketDescription); //根据描述符设置socket对象

	//当有新数据可读时触发该槽函数
    connect(socket,&QTcpSocket::readyRead,[this](){
                qDebug()<<"线程中的事件handleData开始工作,线程为:"<<QThread::currentThreadId();
                QByteArray ba= socket->readAll();//接收socket数据
                QString message=QString(ba);
                list.append(message);
                list[2]=message;
                qDebug()<<"new say"<<message;
                emit sendBackToWidget(list);
            });

    QString ip_address=socket->peerAddress().toString();
    list.append(ip_address);
    list.append(QString::number(socket->peerPort()));
    list.append("");
    
	//发送信号
    emit sendBackToWidget(list);
    //启动了线程的事件循环
    QThread::exec();
}

效果展示

1.使用clienthandle使用movetoThread

首先客户端连接服务器,成功则服务端显示客户端的ip和端口,客户端显示第二个界面。
在这里插入图片描述
客户端输入信息点击发送,服务端显示发送内容。
在这里插入图片描述
后台打印线程id中看到clienthandle中构造函数为属于主线程,而其他为子线程
在这里插入图片描述

2.使用mythread实现QThread

如下图,注释掉clienthandle,使用mythread
在这里插入图片描述
可以看出已经在两个不同的线程运行了
在这里插入图片描述
和上述一样。
在这里插入图片描述
有需要代码的话我放下面
链接 提取码:6666

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值