Qt 进程间通信

Qt进程间通信的方法:

  1. TCP/IP
  2. Local Server/Socket
  3. 共享内存
  4. D-Bus (Unix库)
  5. QProcess
  6. 会话管理

TCP/IP :

使用套接字的方式,进行通信(之前介绍了,这里就不介绍了)。

Local Server/Socket 

跨平台Qt网络模块提供的类使本地网络编程变得可移植且简单。它提供了QLocalServer和QLocalSocket类,允许在本地设置中进行类似网络的通信。它们的 TCP 对应项可以用作直接替代品,以使跨网络通信工作。(使用方法和TCP相似) 

QLocalServer :

使用方法:

  1. listen()监听指定密钥上的传入连接
  2. 有客户端连接时,会发出newConnection()信号
  3. 使用nextPengingConnection()获取连接的QLocalSocket
  4. close()关闭监听

listen(QString)

监听
close() 关闭监听
errorString() 报告的当前错误的人类可读消息
fullServerName() 返回服务器正在侦听的完整路径
isListrening() 服务器正在侦听传入连接,true,否则为false
maxPendingConnection() 返回挂起的接受连接的最大数目。默认值为 30
nextPengingConnection() 将一个挂起的连接作为连接的 QLocalServer 对象返回
serverName() 则返回服务器名称
socketOption() 返回在套接字上设置的套接字选项
waitForNewConnection(int) 堵塞多少毫秒或直到传入连接可用

QLocalSocket

在Windows上是一个命名管道,在Unix上是一个本地套接字。

QLocalSocket 设计用于事件循环,但也可以在没有事件循环的情况下使用它。在这种情况下,您必须使用 waitForConnected()、waitForReadyRead()、waitForBytesWrite() 和 waitForDisconnected(),它们会阻止操作完成或超时到期。

使用方法:

  1. 使用connectToServer()与服务器简建立连接。
  2. 可以使用readData()读取数据,writeData()写入数据
  3. abort()断开连接
  4. close(),或disconnectFromServer()关闭套接字
connectToServer() 连接服务器
bytesAvailable() 获取数据大小
error() 返回上次发生的错误类型
flush() 此函数尽可能多地从内部写入缓冲区写入套接字,而不会阻塞。如果写入了任何数据返回true
fullServerName() 返回套接字连接到的服务器路径
isVaild() 判断套接字是否可用
readData() 读取数据
writeData() 写入数据
setReadBufferSize() 设置内部缓冲区大小
waitForConnected() 等待连接
waitForReadyRead() 等待读取
waitForBytesWrite() 等待写入
waitForDisconnected() 等待断开

 LoaclServer的搭建:

pro文件中添加:

QT  +network

在ui界面中添加:

QTextEdit 、QPushButton 和QLineEdit

Qt 进程间通信_第1张图片

.h文件:

#ifndef WIDGET_H
#define WIDGET_H

#include 
#include
#include
#include
#include

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

private:
    Ui::Widget *ui;
    QLocalServer *localserver;//服务端
    QLocalSocket*localsocket=nullptr;//套接字

};
#endif // WIDGET_H

.cpp文件:

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("服务器端");
    localserver=new QLocalServer(this);
    localserver->listen("Good People");//Good People作为连接字符
    connect(localserver,&QLocalServer::newConnection,[=]()
    {
        localsocket=localserver->nextPendingConnection();//获取连接的套接字
        connect(localsocket,&QLocalSocket::readyRead,[=]()//如果有可读数据的话
        {
            QByteArray block;
            block=localsocket->readAll();
            QString S=block.data();
            ui->textEdit->append(QString("客户端:%1").arg(S));
        });

    });
}

Widget::~Widget()
{
    delete ui;
}


void Widget::on_pushButton_clicked()//发送数据
{
    if(ui->lineEdit->text().isEmpty())
    {
        QMessageBox::information(this,"提示信息","请输入内容",QMessageBox::Ok);
        return;
    }
    ui->textEdit->setText(QString("服务器端:%1").arg(ui->lineEdit->text()));//设置内容
    if(localsocket->isValid())
    {
        QString S=ui->lineEdit->text();
        localsocket->write(S.toUtf8());//发送消息
    }

}

LocalSocket客户端的搭建:

pro文件中添加:

QT +=network

ui界面中添加:

Qt 进程间通信_第2张图片

.h文件:

#ifndef WIDGET_H
#define WIDGET_H

#include 
#include
#include
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_pushButton_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;
    QLocalSocket * localsocket;//套接字
};
#endif // WIDGET_H

.cpp文件:

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    localsocket=new QLocalSocket(this);
    setWindowTitle("客户端");
    connect(localsocket,&QLocalSocket::readyRead,[=]()
    {
        QByteArray block;
        block=localsocket->readAll();//读取信息
        ui->textEdit->append(QString("服务器:%1").arg(block.data()));//聊天框添加信息
    });

}

Widget::~Widget()
{
    delete ui;
}


void Widget::on_pushButton_clicked()//连接
{
    localsocket->abort();//先断开
    localsocket->connectToServer("Good People");//连接
}

void Widget::on_pushButton_2_clicked()//发送
{
    if(ui->lineEdit->text().isEmpty())
    {
        QMessageBox::information(this,"提示信息","请输入内容。",QMessageBox::Ok);
        return;
    }
    if(localsocket->isValid())
    {
        localsocket->write(ui->lineEdit->text().toUtf8());
        ui->textEdit->append(QString("客户端:%1").arg(ui->lineEdit->text()));
    }


}

运行效果:

Qt 进程间通信_第3张图片

 共享内存:

QSharedMemory

QSharedMemory 提供通过多个线程和进程对共享内存段的访问。它还为单个线程或进程提供了一种锁定内存以进行独占访问的方法。

使用此类时,请注意以下平台差异:

  • Windows:QSharedMemory 不“拥有”共享内存段。当将 QSharedMemory 实例附加到特定共享内存段的所有线程或进程都销毁其 QSharedMemory 实例或退出时,Windows 内核会自动释放共享内存段。
  • Unix:QSharedMemory“拥有”共享内存段。当将 QSharedMemory 实例附加到特定共享内存段的最后一个线程或进程通过销毁其 QSharedMemory 实例与该段分离时,Unix 内核将释放共享内存段。但是,如果在未运行 QSharedMemory 析构函数的情况下最后一个线程或进程崩溃,则共享内存段将在崩溃中幸存下来。
  • HP-UX:每个进程只允许一个附加到共享内存段。这意味着 QSharedMemory 不应在 HP-UX 中同一进程中的多个线程中使用。

注意:由于是共享内存,所以使用共享内存之前使用lock()锁定共享内存,使用完后需要解锁。当 QSharedMemory 的最后一个实例从共享内存段分离时,QSharedMemory 会自动销毁该段,并且不会保留对该段的引用。

常用函数:

setKey(QString) 设置此共享内存对象的平台独立
setNativeKey() 设置此共享内存对象的特定于平台的本机密钥。如果 key 与当前本机键相同,则函数返回而不执行任何操作。
lock() 对共享内存上锁
unlock() 对共享内存解锁
size() 返回附加的共享内存段的大小。如果未连接共享内存段,则返回 0。
isAttached() 如果此进程连接到共享内存段返回true
errorString() 错误信息
detach() 将进程与共享内存段分离。如果这是附加到共享内存段的最后一个进程,则系统将释放共享内存段,即内容被销毁。
data() 返回指向共享内存段内容的指针(如果已连接)
create() 使用传递给构造函数的键创建大小为字节共享内存段
constData() 返回指向共享内存段内容的 const 指针(如果已连接)
attach() 尝试将进程附加到由传递给构造函数的键或对 setKey() 或 setNativeKey() 的调用标识的共享内存段。访问模式默认为读写

QSharedMemory::AccessMode

QSharedMemory::ReadOnly 共享内存段是只读的。不允许写入共享内存段。尝试写入使用 ReadOnly 创建的共享内存段会导致程序中止。
QSharedMemory::ReadWrite 读取和写入共享内存段都是允许的。

基本使用流程:

 发送内容到共享内存

  1. 设置一个标识  setKey();
  2. 使用isAttached()判断进程是否与共享内存相连
  3. 使用detach()断开连接
  4. create(size)创建共享内存
  5. 上锁
  6. 将数据写入共享内存
  7. 解锁

从共享内存中读取数据:

  1. attach()连接到共享内存
  2. 上锁
  3. 读取数据
  4. 解锁

例子:写入和读取数据

    QSharedMemory memory;创建对象
    memory.setKey("1000");//设置标识

    //写入数据
    if(memory.isAttached())//已经连接的话
    {
        memory.detach();//断开连接
    }
    QString Str="SharedMemory";

    QBuffer buffer;
    buffer.open(QBuffer::ReadWrite);
    QDataStream S(&buffer);
    S<>Str1;//读取数据
    memory.unlock();//解锁

    memory.detach();//断开连接
    qDebug()<

使用共享内存实现两个进程的聊天:

ui界面:

Qt 进程间通信_第4张图片

.h文件:

#ifndef WIDGET_H
#define WIDGET_H

#include 
#include
#include
#include
#include
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_pushButton_clicked();

    void on_pushButton_2_clicked();

    void on_pushButton_3_clicked();

private:
    Ui::Widget *ui;
    QSharedMemory sharedmemory;//共享内存
};
#endif // WIDGET_H

.cpp文件:

#include "widget.h"
#include "ui_widget.h"
#include
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    sharedmemory.setKey("GG");//设置标识
}

Widget::~Widget()
{
    delete ui;
}


void Widget::on_pushButton_clicked()//读取数据
{
    if(!sharedmemory.attach())
    {
        QMessageBox::information(this,"提示信息","连接共享内存失败",QMessageBox::Ok);
        return;
    }
    QBuffer buffer;
    QString S1;
    QDataStream in(&buffer);
    sharedmemory.lock();
    buffer.setData((char *)sharedmemory.constData(),sharedmemory.size());//设置数据
    buffer.open(QBuffer::ReadOnly);
    in>>S1;//读取数据
    sharedmemory.unlock();//解锁
    sharedmemory.detach();//断开连接
    ui->textEdit->append(QString("他人:%1").arg(S1));
}

void Widget::on_pushButton_2_clicked()//发送数据
{
    if(sharedmemory.isAttached())//内存和进程连接
    {
       if(sharedmemory.detach())//断开连接
       {
           QMessageBox::information(this,"提示信息","已断开连接");
       }
       else
       {
           QMessageBox::information(this,"提示信息","断开连接失败");
           return;
       }
    }
    if(ui->lineEdit->text().isEmpty())
    {
        QMessageBox::information(this,"提示信息","内容不能为空。",QMessageBox::Ok);
    }
    QBuffer buffer;
    buffer.open(QBuffer::ReadWrite);
    QDataStream out(&buffer);
    QString Str=ui->lineEdit->text();
    out<textEdit->append(QString("本人:%1").arg(ui->lineEdit->text()));//显示信息
}

void Widget::on_pushButton_3_clicked()//断开连接
{
    if(sharedmemory.detach())//断开连接
    {
        QMessageBox::information(this,"提示信息","已断开连接");
    }
}

main函数:

#include "widget.h"

#include 

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w1;
    w1.setWindowTitle("widget1");
    w1.show();//进程1
    Widget w2;
    w2.setWindowTitle("widget2");
    w2.show();//进程2
    return a.exec();
}

这里解释一下为什么要有断开连接的按钮:

经过多次测试发现 :

A端发完数据,B端接收数据后,A端需要主动断开连接后B端才能正常发送数据。

(懂的大佬请在评论区解释一下)

效果:

widget2 发送信息给widget1

Qt 进程间通信_第5张图片

 记住发送端需要主动断开连接:

widget1 发送信息给 widget2

Qt 进程间通信_第6张图片

 QProcess

参考文章:关于Qt的QProcess进程间双向通信_qprocess进程间通信_weixin_46424582的博客-CSDN博客

QProcess进程间通信:

Qt 进程间通信_第7张图片

 主线程构建思路:

  • 发送数据到子进程:使用write()函数
  • 读取子进程发送的数据数据:监听readReadyStandardOutput()信号

创建一个项目:

ui界面中添加以下控件: textEdit   pushButton  lineEdit

Qt 进程间通信_第8张图片

.h文件:

#ifndef WIDGET_H
#define WIDGET_H

#include 
#include
#include
#include
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_pushButton_clicked();

    void on_pushButton_2_clicked();
private:
    Ui::Widget *ui;
    QProcess *process;
};
#endif // WIDGET_H

.cpp文件:

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("主进程");
    process=new QProcess;
    connect(process,&QProcess::readyReadStandardOutput,[=]()//读取信息
    {
        QString S=process->readAllStandardOutput().data();
        ui->textEdit->append(QString("子线程:%1").arg(S));
    });

}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()//打开子进程
{
    //子线程的开启
    process->start("D:/Qt_data/build-QProcess_text1-Desktop_Qt_5_9_9_MinGW_32bit-Debug/debug/QProcess_text1.exe");

}

void Widget::on_pushButton_2_clicked()//发送数据
{

    if(ui->lineEdit->text().isEmpty())
    {
        QMessageBox::information(this,"提示信息","输入框不能为空。",QMessageBox::Ok);
        return;
    }
    if(process->Running)
    {
        QString S=ui->lineEdit->text();//获取lineEdit中的内容
        process->write(S.toStdString().c_str());//写入数据
        ui->textEdit->append("父线程:"+S);//在聊天框显示内容
    }
    return;

}

子线程的构建思路:

  • 发送数据到父线程:使用文件打开 stdout,然后把数据写入stdout中
  • 获取父线程发送过来的数据:
    • linux中:使用QSocketNotifier 监听 stdin文件,当改文件有变化是,读取信息
    • Windows中:需要开启一个线程来管理stdin的文件变化,这个需要使用Windows API函数

这里介绍Windows中的子线程构建:

创建一个项目:

在pro文件中添加:

QT +=concurrent

ui界面中添加:

Qt 进程间通信_第9张图片

 .h文件:

#ifndef WIDGET_H
#define WIDGET_H

#include 
#include
#include
#include
#include
#include
#include
#include
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_pushButton_clicked();
    void readStdin();//读取数据

private:
    Ui::Widget *ui;
    QFile file;
signals:
    void sig_log(QString S);//显示数据
    void sig_receivedCommand(QString S);//显示数据
};
#endif // WIDGET_H

.cpp文件:

#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
#include
#include
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("子进程");

    QFuture fu=QtConcurrent::run(this,&Widget::readStdin);//开启一个线程
    connect(this,&Widget::sig_receivedCommand,this,[&](QString s)
    {
        ui->textEdit->append("父线程:"+s);
    });
    connect(this,&Widget::sig_log,this,[=](QString s)
    {
        ui->textEdit->append("父线程:"+s);
    });

}

Widget::~Widget()
{
    delete ui;
}


void Widget::readStdin()//读取数据
{
    bool ok=true;
    char chbuf[1024];//缓存
    DWORD dwRead;//32位无符号整数
    HANDLE hStdinDup;//HANDLE 句柄类型

    const HANDLE hStdin=GetStdHandle(STD_INPUT_HANDLE);//GetStdHandle获取标准输入的句柄
    if(hStdin==INVALID_HANDLE_VALUE)//为无效句柄的话
        return;
    DuplicateHandle(GetCurrentProcess(),hStdin,
                    GetCurrentProcess(),&hStdinDup,0,false,DUPLICATE_SAME_ACCESS);//创建一个新句柄
    CloseHandle(hStdin);//关闭旧句柄
    while(ok)
    {
        ok=ReadFile(hStdinDup,chbuf,sizeof(chbuf),&dwRead,NULL);//读取hstdinDup句柄文件中的数据
        emit sig_log(QLatin1String("ok is:")+QString::number(ok));
        if(ok &&dwRead!=0)
        {
            emit sig_receivedCommand(QString::fromUtf8(chbuf,dwRead));//读取数据
        }
    }

}

void Widget::on_pushButton_clicked()//发送数据
{
    if(ui->lineEdit->text().isEmpty())
    {
        QMessageBox::information(this,"提示信息","输入框不能为空。",QMessageBox::Ok);
        return;
    }
    QFile fileout;
    if(fileout.open(stdout,QIODevice::WriteOnly))
    {
        QString S=ui->lineEdit->text();
        fileout.write(S.toStdString().c_str());
        ui->textEdit->append("子线程:"+S);
        fileout.close();//关闭文件
    }

}

运行效果:

Qt 进程间通信_第10张图片

你可能感兴趣的:(Qt网络和线程,qt)