QT ⑧ 多进程 & 套接字通信
一、多进程
Qt是可以实现多进程功能的,不过Qt是通过打开其他程序来实现多进程,只要是可以执行的程序或者脚本都可以打开
1、头文件
#include <QProcess>2、具体使用流程
//启动按钮 void Widget::on_btn_start_clicked() { //构建一个进程 qp = new QProcess(this); //QProcess(this) 子进程,如果不加this 表示独立进程 //绑定进程执行结束信号 finished connect(qp , &QProcess::finished , this , [&](){ qp->deleteLater(); }); //设置子进程管理的程序 qp->setProgram(ui->lineEdit->text()); //启动进程 qp->start(); } //停止按钮 void Widget::on_btn_stop_clicked() { qDebug() << "stop"; qp->terminate(); }
二、Qt中TCP套接字通信
1、套接字概述
- 封装了 TCP/UDP 协议的底层操作,用于网络数据的发送和接收;
- 通过信号槽机制(如
readyRead)简化数据收发、连接状态变化等事件的处理,方便实现客户端与服务器的通信。2、 QTcpSocket类
注意:Tcp套接字一般只用来做通信使用,通常为
主动发送连接请求的一方(如客户端等),如果需要实现服务器等功能需使用后文介绍的QTcpServer类来实现。
- 实现与远程 TCP 服务器的连接(通过
connectToHost());- 基于信号槽机制异步处理通信事件(如
connected信号通知连接建立、readyRead信号通知数据可读);- 提供
write()(发送数据)、read()/readAll()(接收数据)等方法,支持可靠的流数据传输;- 适用于客户端与远程服务器的网络通信场景(如聊天客户端、数据采集、实时通信等)。
3、QTcpSocket常用函数
类型 名称 / 函数 说明 连接操作 connectToHost(host, port)连接到指定主机(host)和端口(port),异步操作 abort()立即关闭连接,忽略未发送数据 waitForConnected(msecs)阻塞等待连接完成(超时毫秒,-1 为无限等),返回是否成功 数据收发 write(data, len)发送数据(data 为字节数组,len 为长度),返回实际发送字节数 read(maxSize)读取最多 maxSize 字节数据,返回读取的字节数组 readAll()读取所有可用数据,返回完整字节数组 bytesAvailable()返回当前可读取的字节数 waitForReadyRead(msecs)阻塞等待数据到达(超时毫秒),返回是否有数据可读 状态查询 state()返回当前连接状态(如:UnconnectedState、ConnectedState 等) isOpen()返回是否已打开(处于可操作状态) isConnected()返回是否已成功连接到服务器 关闭操作 disconnectFromHost()优雅关闭连接(等待未发送数据完成) close()关闭套接字,释放资源 信号 connected()成功连接到服务器时触发 disconnected()与服务器断开连接时触发 readyRead()有数据可读取时触发(需调用 read/readAll 获取) bytesWritten(bytes)数据发送完成时触发,bytes 为实际发送的字节数 errorOccurred(error)发生错误时触发,error 为错误类型(如连接失败、断开等) 4、QTcpServer类(服务器专用)
①先介绍常用函数以及信号(后文会有具体示例)
类型 名称 / 函数 说明 监听操作 listen(address, port)开始监听指定地址(address,如 QHostAddress::Any)和端口(port),返回是否成功 isListening()返回服务器是否正在监听连接 serverPort()返回当前监听的端口号(未监听时返回 0) serverAddress()返回当前监听的 IP 地址 waitForNewConnection(msecs)阻塞等待新连接(超时毫秒,-1 为无限等),返回是否有新连接 连接处理 nextPendingConnection()返回下一个待处理的客户端连接(QTcpSocket*),获取后需手动管理生命周期 incomingConnection(qintptr)虚函数,处理新连接(通常重写以自定义连接逻辑,如分配线程) 状态查询 serverError()返回最后发生的错误类型(QAbstractSocket::SocketError) errorString()返回错误的字符串描述(如 “地址已在使用”) 关闭操作 close()停止监听,关闭服务器,不再接受新连接(已建立的连接不受影响) 信号 newConnection()有新客户端连接请求且接受成功时触发(需调用 nextPendingConnection 获取连接) acceptError(QAbstractSocket::SocketError)接受连接失败时触发,携带错误类型 ②具体通信流程
QTcpServer(服务器)与 QTcpSocket(客户端)的通信流程可分为以下步骤:
- 服务器启动监听
- 服务器创建
QTcpServer实例,调用listen()绑定指定 IP 和端口,开始监听客户端连接请求。- 客户端发起连接
- 客户端创建
QTcpSocket实例,调用connectToHost()向服务器的 IP 和端口发起连接。- 建立连接
- 服务器监听到连接请求,触发
newConnection()信号,通过nextPendingConnection()获取与该客户端对应的QTcpSocket对象。- 客户端连接成功后,触发
connected()信号,双方建立 TCP 连接。- 数据交互
- 双方通过各自的QTcpSocket收发数据:
- 发送方调用
write()发送数据,触发接收方的readyRead()信号。- 接收方在
readyRead()信号的槽函数中,通过read()或readAll()读取数据。- 断开连接
- 任意一方调用
disconnectFromHost()或close()关闭连接,另一方触发disconnected()信号,通信结束。③示例
服务端源码//==============================以下是头文件================================ #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QTcpServer> //服务器类,不提供传输服务 #include <QTcpSocket> //套接字,可以为服务器提供传输服务 QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void on_btn_open_clicked(); void on_btn_close_clicked(); void on_btn_send_clicked(); void newConnection(); void recive(); private: Ui::Widget *ui; QTcpServer *server; QTcpSocket *socket = nullptr; }; #endif // WIDGET_H //=========================以下是类的具体实现代码=========================== #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); //实例化服务器 server = new QTcpServer(this); //当监听队列一有新的请求,立即出发newConnection信号 connect(server , &QTcpServer::newConnection , this , &Widget::newConnection); ui->lineEdit->setText("55555"); //打印监听队列最大长度 //qDebug() << "max request count : " << server->maxPendingConnections(); //设置最大连接请求数 //server->setMaxPendingConnections(100); //qDebug() << "max request count : " << server->maxPendingConnections(); } Widget::~Widget() { delete ui; } void Widget::on_btn_open_clicked() { //listen方法 监听启动服务器,绑定IP+端口 //参数1 IP地址(这里设置的IPV4) 参数2 端口 bool flag = server->listen(QHostAddress::AnyIPv4,ui->lineEdit->text().toInt()); if(flag) { ui->label->setStyleSheet("background-color:green;" "border-radius:20px"); ui->textEdit->append(QString("服务器启动成功 ").append(server->serverAddress().toString())); } else { //错误信息 ui->textEdit->append(server->errorString()); } } void Widget::on_btn_close_clicked() { //关闭服务器 server->close(); if(socket != nullptr) { socket->close(); } ui->label->setStyleSheet("background-color:red;" "border-radius:20px"); ui->textEdit->append("服务器关闭成功"); } void Widget::on_btn_send_clicked() { if(socket != nullptr) { //发送 socket->write(ui->textEdit_2->toPlainText().toUtf8()); } } //新连接信号 void Widget::newConnection() { //从监听队列取出请求建立连接 返回一个QTcpSocket socket = server->nextPendingConnection(); //连接失败 if(!socket->isValid()) { ui->textEdit->append(socket->errorString()); return; } //获取ip地址 QString ip = socket->peerAddress().toString(); //获取端口号 int port = socket->peerPort(); ui->textEdit->append("client : " + ip + " : " + QString::number(port)); connect(socket , &QTcpSocket::readyRead , this , &Widget::recive); } //接收数据 void Widget::recive() { ui->textEdit->append(socket->readAll()); }
客户端源码//======================================头文件================================== #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QTcpSocket> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void on_btn_con_clicked(); void on_btn_discon_clicked(); void on_btn_send_clicked(); private: Ui::Widget *ui; QTcpSocket *socket; //套接字指针 }; #endif // WIDGET_H //===================================具体实现================================== #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); //创建套接字 socket = new QTcpSocket(this); ui->lineEdit->setText("192.168.6.107"); ui->lineEdit_2->setText("55555"); } Widget::~Widget() { delete ui; } void Widget::on_btn_con_clicked() { //连接到主机 参数1:IP地址 参数2:端口 socket->connectToHost(ui->lineEdit->text(),ui->lineEdit_2->text().toInt()); //连接成功触发信号 connected connect(socket,&QTcpSocket::connected , this , [&](){ ui->label->setStyleSheet(QString("background-color:green;border-radius:20px;")); ui->textEdit->append(QString("提示:连接服务器成功!")); }); //断开连接信息 connect(socket , &QTcpSocket::disconnected , this , [&](){ ui->label->setStyleSheet(QString("background-color:red;border-radius:20px;")); ui->textEdit->append(QString("提示:断开连接成功!")); }); //连接出错信号 connect(socket , &QTcpSocket::errorOccurred , this , [&](QAbstractSocket::SocketError err){ //追加错误信息 ui->textEdit->append(QString("错误:" + socket->errorString())); }); //读数据信号(当套接字的读缓存,一旦有新数据,就会触发该信号) connect(socket , &QTcpSocket::readyRead , this , [&](){ //获取读缓存的有效字节数 qDebug() << socket->bytesAvailable(); //读取100字节 //QByteArray data = socket->read(100); //读取所有缓存 ui->textEdit->append(socket->readAll()); }); } void Widget::on_btn_discon_clicked() { //断开连接 socket->disconnectFromHost(); } void Widget::on_btn_send_clicked() { /** * socket->isValid(); * 作用:判断 socket 是否处于 **“有效状态”**。 * “有效” 通常意味着 socket 已成功建立连接 * (对于 TCP)或正确初始化(对于 UDP),且未发生致命错误(如连接被强制关闭、初始化失败等)。 * 返回值:bool(true 表示有效,false 表示无效) */ /** * socket->isOpen(); * 作用:判断 socket 是否处于 **“打开状态”**。 * 对于 TCP:调用 connectToHost() 后,即使还在连接中(ConnectingState),isOpen() 也会返回 true;只有调用 close() 后才返回 false。 * 对于 UDP:调用 bind() 后(绑定端口),isOpen() 返回 true;close() 后返回 false。 * 返回值:bool(true 表示打开,false 表示关闭)。 */ /** * socket->isWritable(); * 作用:判断 socket 当前是否 **“可写入数据”**。 * 返回值:bool(true 表示可写,false 表示不可写)。 */ /** * socket->isReadable(); * 作用:判断 socket 当前是否 **“可读取数据”**。 */ //以字节流传输数据(发送数据) socket->write(ui->textEdit_2->toPlainText().toUtf8()); }
UI交互界面客户端
服务器
通信测试
三、QTcpServer多线程并发
1、需求概述
上述示例中所讲述的仅仅只是单线程通信(一对一),但是这在寻常业务中是不合乎常理的,服务器通常需要接收多个客户端连接请求,所以就涉及到服务器的并发处理,这就要用到多线程来操作了。
2、具体构思
首先,定义
ClientTask类(继承QObject和QRunnable),用于处理单个客户端连接:在run方法中,通过传入的套接字描述符创建QTcpSocket并绑定连接,进入循环阻塞等待客户端数据;收到数据后,获取客户端 IP 和端口,封装数据并通过recvData信号发送,同时将数据回传给客户端。其次,定义
TcpServer类(继承QTcpServer):构造函数中设置线程池最大线程数(限制并发量);重写incomingConnection方法,当有客户端连接时,创建ClientTask对象(传入套接字描述符),设置任务自动删除,将ClientTask的recvData信号转发给自身的recvData信号,最后将任务提交到线程池执行,实现多客户端并发处理。3、具体实现
//=========================线程类================================ #ifndef TCPSERVER_H #define TCPSERVER_H #include <QTcpServer> #include <QTcpSocket> #include <QRunnable> //线程 #include <QThreadPool> //线程池 class ClientTask : public QObject , public QRunnable{ Q_OBJECT public: ClientTask(qintptr descriptor):descriptor(descriptor){} //重写run方法 void run() { //套接字描述符 QTcpSocket socket; //设置套接字描述符 socket.setSocketDescriptor(descriptor); //永久循环 forever { //阻塞等待,读缓存有数据可读 ,超时返回false if(!socket.waitForReadyRead()) { continue; } //读取信息 QByteArray data = socket.readAll(); //获取ip QString ip = socket.peerAddress().toString(); //获取端口 int port = socket.peerPort(); QString text = QString(ip + " : " + QString::number(port) + "----" + data); //触发收到信息信号 emit recvData(text.toUtf8()); socket.write(data); } } signals: //自定义信号 void recvData(QByteArray data); private: qintptr descriptor; }; class TcpServer : public QTcpServer { Q_OBJECT public: explicit TcpServer(QObject *parent) : QTcpServer{parent} { //设置线程池的最大管理线程数 QThreadPool::globalInstance()->setMaxThreadCount(50); } //重写基类方法:一有客户端发起请求,系统自动调用该函数,传递请求套接字的描述信息 void incomingConnection(qintptr socketDescriptor) { task = new ClientTask(socketDescriptor); //构造客户端任务对象 task->setAutoDelete(true); //设置任务允许被自动删除 //信号与信号之间的关联 (task信号 --> TcpServer信号 --> Widget槽函数) connect(task , &ClientTask::recvData , this , &TcpServer::recvData); //将task任务交给线程池进行管理并启动 QThreadPool::globalInstance()->start(task); } signals: //自定义信号 void recvData(QByteArray data); private: ClientTask *task; }; #endif // TCPSERVER_H //====================================Widget类=================================== #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include "tcpserver.h" QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void on_btn_open_clicked(); void on_btn_close_clicked(); void recvData(QByteArray); private: Ui::Widget *ui; TcpServer *server; }; #endif // WIDGET_H #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); //实例化多线程服务器 server = new TcpServer(this); ui->lineEdit->setText("55555"); connect(server , &TcpServer::recvData , this , &Widget::recvData); } Widget::~Widget() { delete ui; } void Widget::on_btn_open_clicked() { //listen方法 监听启动服务器,绑定IP+端口 //参数1 IP地址(这里设置的IPV4) 参数2 端口 bool flag = server->listen(QHostAddress::AnyIPv4,ui->lineEdit->text().toInt()); if(flag) { ui->label->setStyleSheet("background-color:green;" "border-radius:20px"); ui->textEdit->append(QString("服务器启动成功 ").append(server->serverAddress().toString())); } else { //错误信息 ui->textEdit->append(server->errorString()); } } //关闭服务器 void Widget::on_btn_close_clicked() { server->close(); ui->label->setStyleSheet("background-color:red;" "border-radius:20px"); ui->textEdit->append(QString("服务器关闭成功")); } //接收客户端任务信息 槽函数 void Widget::recvData(QByteArray data) { ui->textEdit->append(data); }
四、Qt中Udp套接字通信
由于UDP是无连接通信,所以有很多步骤相较于Tcp更加简单,在发送和接收数据上有一些区别,用的是
writeDatagram和readDatagram等,与TCP通信的区别就在这里体现出来,readDatagram可以获取到发送方的IP以及端口号,通过该方式可以实现信息的回传。常用函数以及信号
类型 名称 / 函数 说明 绑定操作 bind(address, port)绑定到指定 IP(address)和端口(port),用于接收数据(返回是否成功) isBound()返回是否已成功绑定端口 数据发送 writeDatagram(data, addr, port)向指定 IP(addr)和端口(port)发送数据报(data),返回发送字节数 sendDatagram(datagram)发送 QNetworkDatagram对象(包含数据和目标地址),返回发送字节数数据接收 hasPendingDatagrams()返回是否有待读取的数据报 pendingDatagramSize()返回下一个待读取数据报的大小(字节) readDatagram(data, maxSize, *addr, *port)读取数据到 data,同时获取发送方 IP(addr)和端口(port)receiveDatagram()返回 QNetworkDatagram对象(包含数据、发送方地址等)状态查询 state()返回当前状态(如 UnconnectedState) errorString()返回错误信息字符串 组播操作 joinMulticastGroup(addr)加入指定组播地址(addr),接收组播数据 leaveMulticastGroup(addr)离开指定组播地址 信号 readyRead()有数据报到达时触发(需调用接收函数读取) bytesWritten(bytes)数据报发送完成时触发, bytes为实际发送字节数errorOccurred(error)发生错误时触发(如绑定失败、发送失败) stateChanged(state)状态变化时触发(如从绑定到未绑定) 简单UDP接收端示例
#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); //实例化socket socket = new QUdpSocket(this); //绑定接收函数 connect(socket , &QUdpSocket::readyRead , this , &Widget::recv); } Widget::~Widget() { delete ui; } void Widget::on_pushButton_clicked() { //绑定udp Socket bool flag = socket->bind(ui->lineEdit->text().toInt()); if(!flag) { ui->textEdit->append("绑定:" + socket->errorString()); return; } ui->textEdit->append("提示:服务器已启动"); } void Widget::on_pushButton_2_clicked() { //关闭 socket->close(); } //发送数据 void Widget::on_pushButton_3_clicked() { //发送数据 socket->writeDatagram(ui->textEdit->toPlainText().toUtf8() , addr , port); } //接收数据函数 void Widget::recv() { char buf[1024] = {0}; //UDP专用接收函数(非阻塞) buf 缓冲区 32 读取字节数 &addr输入输出参数 ip //成功返回 整型数值 , 失败返回 -1 int ret = socket->readDatagram(buf, 32 , &addr , &port); if (ret < 0) { ui->textEdit->append("接收:" + socket->errorString()); return; } ui->textEdit->append(QString::number(addr.toIPv4Address()) + "-->" + buf); }

京公网安备 11010502036488号