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