QT ⑧ 多进程 & 套接字通信

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(客户端)的通信流程可分为以下步骤:

  1. 服务器启动监听
    • 服务器创建 QTcpServer 实例,调用 listen() 绑定指定 IP 和端口,开始监听客户端连接请求。
  2. 客户端发起连接
    • 客户端创建 QTcpSocket 实例,调用 connectToHost() 向服务器的 IP 和端口发起连接。
  3. 建立连接
    • 服务器监听到连接请求,触发 newConnection() 信号,通过 nextPendingConnection() 获取与该客户端对应的 QTcpSocket 对象。
    • 客户端连接成功后,触发 connected() 信号,双方建立 TCP 连接。
  4. 数据交互
    • 双方通过各自的QTcpSocket收发数据:
      • 发送方调用 write() 发送数据,触发接收方的 readyRead() 信号。
      • 接收方在 readyRead() 信号的槽函数中,通过 read()readAll() 读取数据。
  5. 断开连接
    • 任意一方调用 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交互界面

客户端

alt

服务器

alt

通信测试

alt

三、QTcpServer多线程并发

1、需求概述

​ 上述示例中所讲述的仅仅只是单线程通信(一对一),但是这在寻常业务中是不合乎常理的,服务器通常需要接收多个客户端连接请求,所以就涉及到服务器的并发处理,这就要用到多线程来操作了。

2、具体构思

首先,定义ClientTask类(继承QObjectQRunnable),用于处理单个客户端连接:在run方法中,通过传入的套接字描述符创建QTcpSocket并绑定连接,进入循环阻塞等待客户端数据;收到数据后,获取客户端 IP 和端口,封装数据并通过recvData信号发送,同时将数据回传给客户端。

其次,定义TcpServer类(继承QTcpServer):构造函数中设置线程池最大线程数(限制并发量);重写incomingConnection方法,当有客户端连接时,创建ClientTask对象(传入套接字描述符),设置任务自动删除,将ClientTaskrecvData信号转发给自身的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更加简单,在发送和接收数据上有一些区别,用的是writeDatagramreadDatagram等,与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);
}
全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务