Qt实现IPC进程间通信-共享内存方式
- 1 Qt实现IPC进程间通信-共享内存方式
- 2. 跨进程通信设计:Qt 进程间通讯类全面解析
- 2.1. 引言(Introduction)
- 2.2. 基于共享内存的进程间通信类(Shared Memory-based Interprocess Communication Classes)
- 2.3.基于消息队列的进程间通信类(Message Queue-based Interprocess Communication Classes)
- 2.4.基于本地套接字的进程间通信类(Local Socket-based Interprocess Communication Classes)
- 2.5. 基于进程启动的进程间通信类(Process Launch-based Interprocess Communication Classes)
- 2.6. 基于 D-Bus 的进程间通信类(D-Bus-based Interprocess Communication Classes)
- 2.7. 基于远程对象的进程间通信类(Remote Object-based Interprocess Communication Classes)
- 2.8. 综合对比各种方式的优缺点和适用场景以及性能
- 2.9. 从操作系统和 Linux 系统调用的角度分析 Qt 进程间通信类
- Qt线程/进程间通讯
1 Qt实现IPC进程间通信-共享内存方式
原文链接:https://blog.csdn.net/weixin_40355471/article/details/113176745
QT封装了QSharedMemory类实现共享内存,可以共享文本、图片等各种数据格式,支持windows和linux跨平台使用;
示例代码新建两个工程,分别启动两个应用程序,实现文本数据的共享。
工程A
widget_pa.h
#ifndef WIDGET_PA_H
#define WIDGET_PA_H
#include <QWidget>
#include <QSharedMemory>
namespace Ui {
class Widget_PA;
}
class Widget_PA : public QWidget
{
Q_OBJECT
public:
explicit Widget_PA(QWidget *parent = 0);
~Widget_PA();
void sendStrToMemory(QString str);
private slots:
void on_pushButton_clicked();
private:
Ui::Widget_PA *ui;
QSharedMemory m_sharedMemory;
};
#endif // WIDGET_PA_H
widget_pa.cpp
#include "widget_pa.h"
#include "ui_widget_pa.h"
#include <QBuffer>
#include <QDebug>
Widget_PA::Widget_PA(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget_PA)
{
ui->setupUi(this);
}
Widget_PA::~Widget_PA()
{
delete ui;
}
void Widget_PA::sendStrToMemory(QString str)
{
//1、设置共享内存的标志名
m_sharedMemory.setKey("processAstr");
if(m_sharedMemory.isAttached())
{
//2、将该进程与共享内存分离
if(!m_sharedMemory.detach())
{
qDebug() << "Unable to detach from shared memory.";
}
}
// 将数据加载到共享内存中
QBuffer buffer;
buffer.open(QBuffer::ReadWrite);
QDataStream out(&buffer);
out << str;
int size = buffer.size();
//3、创建共享内存
if(!m_sharedMemory.create(size))
{
qDebug() << m_sharedMemory.errorString()<<"\n Unable to create shared memory segment.";
return;
}
//4、将共享内存上锁
m_sharedMemory.lock();
char *to = (char*)m_sharedMemory.data();
const char *from = buffer.data().data();
//5、将进程中要共享的数据拷贝到共享内存中
memcpy(to, from, qMin(m_sharedMemory.size(), size));
//6、将共享内存解锁
m_sharedMemory.unlock();
}
void Widget_PA::on_pushButton_clicked()
{
sendStrToMemory(ui->textEdit->toPlainText());
}
工程B
widget_pb.h
#ifndef WIDGET_PB_H
#define WIDGET_PB_H
#include <QWidget>
#include <QSharedMemory>
namespace Ui {
class Widget_PB;
}
class Widget_PB : public QWidget
{
Q_OBJECT
public:
explicit Widget_PB(QWidget *parent = 0);
~Widget_PB();
QString loadStrFromMemory();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget_PB *ui;
QSharedMemory m_sharedMemory;
};
#endif // WIDGET_PB_H
widget_pb.cpp
#include "widget_pb.h"
#include "ui_widget_pb.h"
#include <QBuffer>
#include <QDebug>
Widget_PB::Widget_PB(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget_PB)
{
ui->setupUi(this);
}
Widget_PB::~Widget_PB()
{
delete ui;
}
QString Widget_PB::loadStrFromMemory()
{
//1、设置共享内存的标志名
m_sharedMemory.setKey("processAstr");
//2、将共享内存与该进程绑定
if (!m_sharedMemory.attach())
{
qDebug() << "Unable to attach to shared memory segment.";
return "Unable to attach to shared memory segment.";
}
// 从共享内存中读取数据
QBuffer buffer;
QDataStream in(&buffer);
QString str;
//3、将共享内存上锁
m_sharedMemory.lock();
//4、从共享内存中取数据
buffer.setData((char*)m_sharedMemory.constData(), m_sharedMemory.size());
buffer.open(QBuffer::ReadOnly);
in >> str;
//5、使用完后将共享内存解锁
m_sharedMemory.unlock();
//6、将共享内存与该进程分离
m_sharedMemory.detach();
return str;
}
void Widget_PB::on_pushButton_clicked()
{
ui->textEdit->setText(loadStrFromMemory());
}
2. 跨进程通信设计:Qt 进程间通讯类全面解析
原文链接:https://blog.csdn.net/qq_21438461/article/details/130211974
2.1. 引言(Introduction)
进程间通信的重要性(The Importance of Interprocess Communication)
进程间通信(Interprocess Communication,简称 IPC)是现代软件开发中不可或缺的一部分。在许多应用场景中,多个进程需要共享数据、协同工作,或在不同进程间传递消息。IPC 技术允许这些进程安全、高效地通信,从而实现复杂的功能和任务。
Qt 提供了一系列进程间通信类,以满足各种不同场景和需求。通过掌握这些类,开发者可以在 Qt 应用中实现强大的进程间通信功能。
本文涉及的主题简介(Introduction to the Topics Covered in This Article)
本文将全面介绍 Qt 进程间通信类,包括基于共享内存、消息队列、本地套接字、进程启动、D-Bus 和远程对象的通信类。对于每个类别,我们将介绍相关类的功能和用法,并提供示例代码。最后,我们将总结如何根据应用需求选择合适的进程间通信方法。
2.2. 基于共享内存的进程间通信类(Shared Memory-based Interprocess Communication Classes)
共享内存是一种在不同进程间共享数据的高效方式。通过使用共享内存,多个进程可以访问相同的内存块,从而在进程间直接交换信息。Qt 提供了以下类来实现基于共享内存的进程间通信:
QSharedMemory:用于共享内存(QSharedMemory: For Shared Memory)
QSharedMemory 类提供了一个跨进程共享的内存段。它允许不同进程将数据存储在共享内存中,以便其他进程可以访问这些数据。QSharedMemory 使得进程间通信变得快速和简单,但它需要开发者自行处理同步和并发问题。
使用方法:
创建一个 QSharedMemory 实例,指定一个键(key)以唯一标识共享内存段。
使用 create() 方法创建一个共享内存段,或使用 attach() 方法连接到现有的共享内存段。
使用 lock() 方法锁定共享内存,以防止其他进程同时访问。
使用 data() 方法获取共享内存的指针,并对其进行读/写操作。
使用 unlock() 方法解锁共享内存。
使用 detach() 方法从共享内存段断开连接。
示例代码:
#include <QSharedMemory>
#include <QBuffer>
#include <QDataStream>
// 创建一个共享内存实例,使用唯一的键 "my_shared_memory"
QSharedMemory sharedMemory("my_shared_memory");
// 创建一个大小为 1024 字节的共享内存段
if (!sharedMemory.create(1024)) {
// 处理错误
}
// 锁定共享内存
if (!sharedMemory.lock()) {
// 处理错误
}
// 写入数据到共享内存
QBuffer buffer;
buffer.setBuffer(static_cast<QByteArray *>(sharedMemory.data()));
buffer.open(QIODevice::WriteOnly);
QDataStream out(&buffer);
out << "Hello, world!";
// 解锁共享内存
sharedMemory.unlock();
2.2.1. QSystemSemaphore:用于系统级别的信号量(QSystemSemaphore: For System-Level Semaphores)
QSystemSemaphore 类提供了一个系统级别的信号量,用于在不同进程间同步共享资源的访问。信号量是一种计数器,用于控制对有限数量的资源的访问。在共享内存的场景中,QSystemSemaphore 可以帮助防止多个进程同时访问同一内存区域,从而避免数据竞争和一致性问题。
使用方法:
创建一个 QSystemSemaphore 实例,指定一个键(key)以唯一标识信号量。
使用 acquire() 方法请求信号量资源。
当资源可用时,执行对共享资源(如共享内存)的操作。
完成资源操作后,使用 release() 方法释放信号量资源。
示例代码:
#include <QSystemSemaphore>
#include <QSharedMemory>
#include <QBuffer>
#include <QDataStream>
// 创建一个系统信号量,使用唯一的键 "my_semaphore"
QSystemSemaphore semaphore("my_semaphore", 1);
// 请求信号量资源
if (!semaphore.acquire()) {
// 处理错误
}
// 创建一个共享内存实例,使用唯一的键 "my_shared_memory"
QSharedMemory sharedMemory("my_shared_memory");
// 连接到现有的共享内存段
if (!sharedMemory.attach()) {
// 处理错误
}
// 锁定共享内存
if (!sharedMemory.lock()) {
// 处理错误
}
// 读取数据从共享内存
QBuffer buffer;
buffer.setBuffer(static_cast<QByteArray *>(sharedMemory.data()));
buffer.open(QIODevice::ReadOnly);
QDataStream in(&buffer);
QString message;
in >> message;
// 解锁共享内存
sharedMemory.unlock();
// 释放信号量资源
semaphore.release();
这个例子展示了如何使用 QSystemSemaphore 来同步多个进程对共享内存的访问。在这个示例中,一个进程在共享内存中写入数据,而另一个进程从共享内存中读取数据。通过使用 QSystemSemaphore,我们可以确保在读写操作进行时,不会发生数据竞争。
2.3.基于消息队列的进程间通信类(Message Queue-based Interprocess Communication Classes)
QMessageQueue:用于在不同进程之间发送和接收消息(QMessageQueue: For Sending and Receiving Messages between Different Processes)
注意:Qt 并没有提供一个名为 QMessageQueue 的类,但是您可以通过第三方库或自定义实现来使用消息队列。以下内容将介绍一个简单的使用示例。
假设我们已经有一个名为 QMessageQueue 的第三方库或自定义实现,我们可以使用它来进行消息队列通信。以下是如何在不同进程之间发送和接收消息的示例:
发送消息示例:
#include "QMessageQueue"
int main(int argc, char *argv[])
{
QMessageQueue msgQueue("my_message_queue");
if (!msgQueue.open(QIODevice::WriteOnly)) {
// 处理错误
}
QByteArray message("Hello, world!");
msgQueue.write(message);
msgQueue.close();
接收消息示例:
#include "QMessageQueue"
int main(int argc, char *argv[])
{
QMessageQueue msgQueue("my_message_queue");
if (!msgQueue.open(QIODevice::ReadOnly)) {
// 处理错误
}
QByteArray message;
msgQueue.read(message);
qDebug() << "Received message:" << message;
msgQueue.close();
}
这个例子展示了如何使用消息队列进行进程间通信。发送消息的进程将一个字符串写入消息队列,而接收消息的进程从消息队列中读取数据。我们使用了一个名为 “my_message_queue” 的唯一键来确保两个进程共享相同的消息队列。
2.4.基于本地套接字的进程间通信类(Local Socket-based Interprocess Communication Classes)
QLocalServer:用于创建本地套接字服务器端(QLocalServer: For Creating Local Socket Server End)
QLocalSocket:用于连接和通信本地套接字服务器端(QLocalSocket: For Connecting and Communicating with Local Socket Server End)
QLocalServer 示例代码
服务器端代码:
#include <QCoreApplication>
#include <QLocalServer>
#include <QLocalSocket>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QLocalServer server;
QObject::connect(&server, &QLocalServer::newConnection, [&server] {
QLocalSocket *clientConnection = server.nextPendingConnection();
QObject::connect(clientConnection, &QLocalSocket::disconnected,
clientConnection, &QLocalSocket::deleteLater);
clientConnection->write("Hello, client!");
clientConnection->flush();
clientConnection->disconnectFromServer();
});
if (!server.listen("my_local_socket")) {
// 处理错误
}
return a.exec();
客户端代码:
#include <QCoreApplication>
#include <QLocalSocket>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QLocalSocket socket;
socket.connectToServer("my_local_socket");
if (socket.waitForConnected()) {
socket.waitForReadyRead();
qDebug() << "Received message:" << socket.readAll();
socket.disconnectFromServer();
} else {
// 处理错误
}
return a.exec();
}
以上示例展示了如何使用 QLocalServer 和 QLocalSocket 在进程间进行通信。服务器端创建一个 QLocalServer 实例并监听一个名为 “my_local_socket” 的本地套接字。当客户端连接时,服务器端发送一条消息给客户端。客户端使用 QLocalSocket 连接到服务器并接收消息。
2.5. 基于进程启动的进程间通信类(Process Launch-based Interprocess Communication Classes)
QProcess:用于启动外部进程并与之进行通信(QProcess: For Launching External Processes and Communicating with Them)
QProcess 示例代码
#include <QCoreApplication>
#include <QProcess>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QProcess process;
QObject::connect(&process, &QProcess::readyReadStandardOutput, [&process] {
qDebug() << "Output:" << process.readAllStandardOutput();
});
QObject::connect(&process, &QProcess::readyReadStandardError, [&process] {
qDebug() << "Error:" << process.readAllStandardError();
});
process.start("ping", QStringList() << "-c" << "4" << "www.google.com");
process.waitForFinished(-1);
return a.exec();
上述示例展示了如何使用 QProcess 启动一个外部进程(例如 ping 命令)并与之进行通信。当进程产生输出或错误信息时,信号槽连接将触发相应的槽函数,从而读取输出或错误信息。
2.6. 基于 D-Bus 的进程间通信类(D-Bus-based Interprocess Communication Classes)
QDBusConnection:用于与 D-Bus 通信总线建立连接(QDBusConnection: For Establishing Connection with D-Bus Communication Bus)
QDBusMessage:用于在 D-Bus 上发送和接收消息(QDBusMessage: For Sending and Receiving Messages on D-Bus)
QDBusConnection 示例代码
#include <QCoreApplication>
#include <QDBusConnection>
#include <QDBusMessage>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QDBusConnection connection = QDBusConnection::sessionBus();
if (!connection.isConnected()) {
qCritical() << "Failed to connect to D-Bus session bus";
return 1;
}
QDBusMessage message = QDBusMessage::createMethodCall("org.example.MyService",
"/org/example/MyService",
"org.example.MyService",
"MyMethod");
message << 42;
QDBusMessage reply = connection.call(message);
if (reply.type() == QDBusMessage::ReplyMessage) {
qDebug() << "Reply received:" << reply.arguments();
} else {
qCritical() << "Failed to call MyMethod on MyService:" << reply.errorMessage();
}
return a.exec();
上述示例展示了如何使用 QDBusConnection 类连接到 D-Bus 会话总线,然后使用 QDBusMessage 类创建并发送一个方法调用消息。在收到回复后,我们可以检查回复类型和参数。
2.7. 基于远程对象的进程间通信类(Remote Object-based Interprocess Communication Classes)
QRemoteObjectNode:用于访问远程对象(QRemoteObjectNode: For Accessing Remote Objects)
QRemoteObjectHost:用于托管远程对象(QRemoteObjectHost: For Hosting Remote Objects)
QRemoteObjectNode 和 QRemoteObjectHost 示例代码
服务端
#include <QCoreApplication>
#include <QRemoteObjectHost>
#include "MyRemoteObject.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QRemoteObjectHost host(QUrl("local:remote_object"));
MyRemoteObject remoteObject;
host.enableRemoting(&remoteObject);
return a.exec();
}
客户端
#include <QCoreApplication>
#include <QRemoteObjectNode>
#include "MyRemoteObjectReplica.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QRemoteObjectNode node;
node.connectToNode(QUrl("local:remote_object"));
QSharedPointer<MyRemoteObjectReplica> replica = node.acquire<MyRemoteObjectReplica>();
if (!replica->waitForSource()) {
qCritical() << "Failed to connect to remote object";
return 1;
}
QObject::connect(replica.data(), &MyRemoteObjectReplica::mySignal,
[](int value) {
qDebug() << "Received signal with value:" << value;
});
replica->myMethod();
return a.exec();
}
上述示例展示了如何使用 QRemoteObjectHost 托管一个远程对象,并使用 QRemoteObjectNode 访问这个对象。在服务端,我们创建了一个 QRemoteObjectHost 实例并使远程对象可用。在客户端,我们使用 QRemoteObjectNode 连接到服务端,并获取远程对象的副本。然后我们连接信号并调用远程方法。
这些示例代码仅用于演示 Qt 进程间通信类的用法。在实际应用中,您可能需要根据项目需求调整这些代码。
2.8. 综合对比各种方式的优缺点和适用场景以及性能
在本节中,我们将对比前面介绍的各种进程间通信方式的优缺点、适用场景以及性能。
基于共享内存的进程间通信
优点:
数据传输速度快,性能高;
避免了数据复制,节省了内存空间。
缺点:
需要手动管理同步和互斥机制,增加了编程复杂性;
容易引发死锁或竞态条件。
适用场景:
需要快速传输大量数据的场景;
适用于有共享内存需求的应用程序。
基于消息队列的进程间通信
优点:
数据传输可靠;
可以通过消息队列系统实现异步通信。
缺点:
传输速度相对较慢;
对于大量数据的传输,需要进行数据拆分和合并。
适用场景:
需要实现异步通信的场景;
适用于对数据传输可靠性要求较高的应用程序。
基于本地套接字的进程间通信
优点:
数据传输速度快;
支持双向通信;
可以实现多个进程之间的通信。
缺点:
对于跨平台的应用程序,需要处理平台差异;
需要处理连接和断开连接的状态。
适用场景:
需要实现双向通信的场景;
适用于需要实现多个进程之间通信的应用程序。
基于进程启动的进程间通信
优点:
可以与外部进程进行通信;
方便地获取外部进程的输出。
缺点:
需要处理进程启动和退出的状态;
对于双向通信,需要额外处理。
适用场景:
需要与外部进程进行通信的场景;
适用于需要获取外部进程输出的应用程序。
基于 D-Bus 的进程间通信
优点:
跨平台;
支持对象导向的通信模式;
支持广播和多播通信。
缺点:
数据传输速度相对较慢;
需要处理与 D-Bus 系统的连接和断开。
适用场景:
需要实现跨平台通信的场景;
适用于需要广播和多播通信的应用程序。
基于远程对象的进程间通信
优点:
支持对象导向的通信模式;
可以轻松地访问和托管远程对象;
支持自动代码生成。
缺点:
依赖于网络连接;
需要处理连接和断开连接的状态。
适用场景:
需要实现远程对象访问的场景;
适用于分布式系统和服务的应用程序。
性能对比
在性能方面,
基于共享内存的进程间通信方式具有最高的性能,因为它避免了数据复制,并可以直接访问共享内存。
基于本地套接字的进程间通信方式具有较高的性能,因为它支持双向通信并且传输速度快。
基于 D-Bus 和基于消息队列的进程间通信方式在性能方面相对较慢,但它们可以实现异步通信,并具有更高的数据传输可靠性。
在选择进程间通信方式时,应根据应用程序的需求和场景来权衡性能、可靠性、易用性和跨平台能力等因素。
2.9. 从操作系统和 Linux 系统调用的角度分析 Qt 进程间通信类
1.共享内存(Shared Memory)与 Linux 系统调用
共享内存是一种在不同进程间共享数据的技术,通过将一块内存映射到多个进程的地址空间来实现。在 Linux 系统中,共享内存是通过 shmget(),shmat() 和 shmdt() 系统调用实现的。Qt 的 QSharedMemory 类封装了这些底层系统调用,使得共享内存的操作更加简单和直观。以下是一些与共享内存相关的 Linux 系统调用:
- shmget():创建一个共享内存段或获取一个已存在的共享内存段的标识符。
- shmat():将共享内存段附加到调用进程的地址空间。
- shmdt():将共享内存段从调用进程的地址空间分离。
- shmctl():执行各种共享内存控制操作,例如设置共享内存段的权限、删除共享内存段等。
使用 Qt 的 QSharedMemory 类时,开发者无需关心底层的 Linux 系统调用,可以直接使用 QSharedMemory 类提供的方法来创建、访问、控制共享内存。这大大简化了共享内存的使用,提高了开发效率。
2.消息队列(Message Queue)与 Linux 系统调用
消息队列是一种允许不同进程通过发送和接收消息进行通信的机制。在 Linux 系统中,消息队列是通过 msgget(),msgsnd() 和 msgrcv() 系统调用实现的。以下是一些与消息队列相关的 Linux 系统调用:
-
msgget():创建一个新的消息队列或获取一个已存在的消息队列的标识符。
-
msgsnd():向消息队列发送一条消息。
-
msgrcv():从消息队列接收一条消息。
-
msgctl():执行各种消息队列控制操作,例如设置消息队列的权限、删除消息队列等。
虽然 Qt 没有直接提供消息队列的封装,但开发者可以使用第三方库或者直接使用 Linux 系统调用来实现消息队列通信。
3.本地套接字(Local Socket)与 Linux 系统调用
本地套接字是一种在同一台主机上的不同进程间进行通信的机制。在 Linux 系统中,本地套接字是通过 socket(),bind(),listen(),accept(),connect(),send() 和 recv() 等系统调用实现的。以下是一些与本地套接字相关的 Linux 系统调用:
- socket():创建一个新的本地套接字。
- bind():将本地套接字绑定到一个文件系统路径。
- listen():将本地套接字设置为监听状态,准备接受连接请求。
- accept():接受来自客户端的连接请求。
- connect():连接到服务器端的本地套接字。
- send() 和 recv():通过本地套接字发送和接收数据。
Qt 的 QLocalServer 和 QLocalSocket类封装了这些底层系统调用,使得本地套接字通信更加简单和直观。开发者可以通过使用这两个类轻松实现本地套接字通信,提高开发效率。
4.进程启动(Process Launch)与 Linux 系统调用
在 Linux 系统中,进程启动是通过 fork() 和 exec() 系列系统调用实现的。Qt 的 QProcess 类封装了这些底层系统调用,使得启动外部进程并与之通信更加方便。
fork() 系统调用创建一个新进程,新进程是当前进程的一个副本,称为子进程,两个进程在相同的内存空间中执行。在 fork() 调用之后,子进程会继承父进程的所有内存空间,包括打开的文件和 socket。在子进程中,exec() 系列系统调用可以用于启动新的程序或者脚本,并用新程序或脚本替换当前进程的内存空间。在 Qt 中,QProcess 类封装了 fork() 和 exec() 系列系统调用,使得启动外部进程并与之通信更加方便。
常用的 exec() 系列函数有:
execl():用指定的参数列表执行一个程序。
execle():用指定的环境变量和参数列表执行一个程序。
execlp():用当前进程的环境变量和指定的参数列表执行一个程序。
execv():用指定的参数列表执行一个程序。
execve():用指定的环境变量和参数列表执行一个程序。
execvp():用当前进程的环境变量和指定的参数列表执行一个程序。
QProcess 类提供了以下方法来执行外部程序:
start():启动一个外部程序,并返回一个布尔值表示启动是否成功。
waitForStarted():等待外部程序启动。
write():向外部程序的标准输入写入数据。
readAllStandardOutput():读取外部程序的标准输出。
readAllStandardError():读取外部程序的标准错误输出。
terminate():终止外部程序的运行。
5. 基于 D-Bus 的进程间通信类与 Linux 系统调用
D-Bus 是一个用于 Linux 桌面环境的消息传递系统,主要用于进程间通信。Qt 的 D-Bus 支持库基于 libdbus 库构建,libdbus 库在底层使用 Linux 的 Unix 域套接字和其他系统调用进行通信。以下是一些与 D-Bus 相关的 Linux 系统调用:
- socket():创建一个 Unix 域套接字。
- bind():将 Unix 域套接字绑定到一个文件系统路径。
- connect():连接到 D-Bus 守护进程的 Unix 域套接字。
- sendmsg() 和 recvmsg():通过 Unix 域套接字发送和接收 D-Bus 消息。
- poll():等待 Unix 域套接字上的事件。
6. 基于远程对象的进程间通信类与 Linux 系统调用
Qt 的远程对象框架用于跨设备、跨平台进行进程间通信。在底层,远程对象使用 Qt 网络模块(如 QTcpSocket 和 QUdpSocket)进行通信。以下是一些与远程对象相关的 Linux 系统调用:
- socket():创建一个 TCP 或 UDP 套接字。
- bind():将套接字绑定到一个 IP 地址和端口。
- connect():连接到远程对象托管进程的套接字。
- send() 和 recv():通过 TCP 或 UDP 套接字发送和接收远程对象消息。
- poll():等待套接字上的事件。
这些系统调用和底层通信机制使得 Qt 进程间通信类能够在 Linux 和其他操作系统上高效、可靠地进行通信。
7. Qt 进程间通信类的可移植性和跨平台性
Qt 作为一个跨平台的应用程序开发框架,为不同平台提供了统一的 API。在进程间通信方面,Qt 同样保持了良好的可移植性和跨平台性。在 Windows、macOS 和 Linux 系统上,Qt 进程间通信类都可以无缝地使用。在底层实现时,Qt 根据操作系统选择合适的系统调用或底层通信机制。例如,在 Windows 平台上,Qt 的共享内存类 QSharedMemory 使用了 Windows 的内存映射文件功能。
通过将底层细节封装在统一的 API 中,Qt 进程间通信类使得开发人员可以专注于应用程序逻辑,而无需担心不同操作系统之间的差异。
8. Qt 进程间通信类与系统资源
使用进程间通信类时,应注意系统资源的使用。例如,使用 QSharedMemory 和 QSystemSemaphore 时,需要在不使用时释放这些资源。否则,这些资源可能会导致操作系统资源耗尽。为了确保资源的正确使用和释放,Qt 进程间通信类提供了相应的析构函数和清理方法。
此外,Qt 进程间通信类还提供了对系统资源使用的限制。例如,QSharedMemory 允许你限制共享内存的大小,以避免消耗过多的内存资源。
综上所述,Qt 进程间通信类在操作系统和 Linux 系统调用层面提供了高效、可靠的通信机制。通过使用 Qt 进程间通信类,开发人员可以轻松实现跨平台的进程间通信,同时确保系统资源的合理使用和释放。
9. Qt 进程间通信类与操作系统权限管理
在操作系统中,不同进程可能具有不同的权限。当使用 Qt 进程间通信类时,需要确保涉及到的进程具有相应的访问权限。例如,当使用 QSharedMemory 时,需要确保两个进程都有对共享内存区域的读写权限。同样,当使用 QLocalServer 和 QLocalSocket 时,需要确保进程具有在相应的本地套接字上进行通信的权限。
Qt 提供了一些机制来简化权限管理。例如,QSharedMemory 允许你为共享内存区域设置访问权限。此外,Qt 提供了一些辅助类,如 QFile 和 QDir,用于检查和设置文件和目录的权限,这对于管理本地套接字文件和 D-Bus 通信文件非常有用。
10. Qt 进程间通信类与多线程环境
在多线程环境中使用 Qt 进程间通信类需要特别注意。许多进程间通信类,如 QSharedMemory、QLocalServer 和 QLocalSocket,不是线程安全的。这意味着在多线程环境中使用这些类时,需要对访问进行同步,以避免数据竞争和不一致。Qt 提供了一系列线程同步工具,如 QMutex 和 QSemaphore,来帮助你在多线程环境中安全地使用进程间通信类。
另一方面,QProcess 和 QDBusConnection 等类是线程安全的,可以在多线程环境中安全地使用。但是,你仍然需要注意线程间的数据同步和通信,以确保应用程序的正确运行。
总之,从操作系统和 Linux 系统调用的角度来看,Qt 进程间通信类提供了一套高效、可靠且易于使用的通信机制。通过合理地管理权限、资源和线程同步,开发人员可以在不同平台上轻松实现进程间通信,从而提高应用程序的性能和用户体验。
2.10. Qt 进程间通信类可能出现的异常和解决方案
1. 共享内存(Shared Memory)
异常:共享内存分配失败
解决方案:检查系统资源是否足够并释放不再使用的共享内存;考虑使用其他进程间通信方法。
2. 消息队列(Message Queue)
异常:消息队列满
解决方案:增加消息队列的大小或减少消息发送速率;考虑使用其他进程间通信方法。
3. 本地套接字(Local Socket)
异常:连接失败
解决方案:检查服务器端是否已启动并监听正确的地址和端口;检查客户端是否使用正确的地址和端口进行连接。
异常:通信中断
解决方案:为套接字添加错误处理和重连机制;确保网络稳定。
4. 进程启动(Process Launch)
异常:无法启动外部进程
解决方案:检查外部程序路径和参数是否正确;检查系统资源是否足够。
5. D-Bus
异常:D-Bus 通信失败
解决方案:检查 D-Bus 服务是否已启动;检查应用程序是否具有访问 D-Bus 的权限;检查 D-Bus 通信地址和端口是否正确。
6. 远程对象(Remote Objects)
异常:无法访问远程对象
解决方案:检查远程对象是否已正确托管;检查远程对象地址和端口是否正确;检查网络连接是否稳定。
2.11. Qt 进程间通信类综合答疑
1. 为什么选择 Qt 进程间通信类?
Qt 提供了一套简洁、高效的 API,可以轻松实现多种进程间通信方法。
Qt 支持跨平台的进程间通信,方便在不同操作系统下进行开发和部署。
Qt 的进程间通信类具有较高的可扩展性,可以根据项目需求选择合适的通信方法。
2. 如何选择合适的进程间通信方法?
选择合适的进程间通信方法需要根据项目的具体需求和场景来决定:
共享内存:适用于大量数据传输且要求高效的场景。
消息队列:适用于离散的消息传输,具有较好的实时性。
本地套接字:适用于同一台设备上的进程间通信,具有较好的传输性能。
进程启动:适用于调用外部程序并与其进行通信的场景。
D-Bus:适用于桌面环境下的进程间通信,具有较好的集成性。
远程对象:适用于跨设备、跨平台的进程间通信,具有较好的可扩展性。
选择合适的进程间通信方式取决于多种因素,包括数据传输的速度、安全性、跨平台兼容性等。以下是一些选择合适通信方式的建议:
对于高速、大量数据传输的场景,可以考虑使用共享内存或本地套接字。共享内存提供了最快的数据传输速度,但需要额外的同步机制。本地套接字在速度和易用性之间取得了平衡。
对于需要实时交换离散消息的场景,可以考虑使用消息队列。消息队列提供了一种简单、可靠的消息传递机制,适用于实时协作场景。
对于需要在同一设备上的不同应用程序之间进行通信的场景,可以考虑使用本地套接字。本地套接字提供了一种高效、易用的通信方式。
对于需要跨设备、跨平台进行通信的场景,可以考虑使用 D-Bus 或远程对象。D-Bus 适用于 Linux 桌面环境,而远程对象提供了一种跨设备、跨平台的通信方式。
当需要与其他非 Qt 应用进行通信时,可以考虑使用 QProcess 或 D-Bus。这种方式具有较好的通用性,但可能需要处理与其他应用的兼容问题。
当需要实现复杂的远程对象调用时,可以考虑使用 QRemoteObjectNode 和 QRemoteObjectHost。这种方式具有较高的抽象级别,但需要处理网络延迟和丢包问题。
3. 如何处理 Qt 进程间通信过程中的异常?
为通信类添加错误处理机制,例如信号槽连接、异常捕获等。
根据异常类型,采取相应的解决措施,例如检查网络连接、释放资源等。
参考官方文档和示例代码,确保代码的正确性和稳定性。
4. 如何优化 Qt 进程间通信性能?
选择合适的通信方式:根据实际需求和场景,选择性能最优的进程间通信方式。
缩小数据传输量:尽量减少传输的数据量,通过压缩、分片等手段降低数据传输的开销。
优化数据序列化和反序列化:选择高效的数据序列化和反序列化方式,如使用 Qt 提供的 QDataStream 类或选择第三方库。
异步通信:尽量采用异步通信方式,以避免阻塞 UI 线程,提高用户体验。
优化网络参数:针对基于网络的通信方式,可以调整网络参数,如缓冲区大小,超时时间、重试次数等,以提高通信的稳定性和性能。
5. 如何确保 Qt 进程间通信的安全性?
使用加密通信,例如 SSL/TLS,以保护数据的隐私和完整性。
使用访问控制机制,例如访问权限、认证等,来限制通信的范围和对象。
对传输数据进行校验和验证,以防止篡改和伪造。
及时更新和维护 Qt 库和操作系统,确保通信过程中的安全性。
在进行进程间通信时,安全性是一个重要的考虑因素。以下是一些建议,以确保通信过程中的安全性:
对于基于共享内存的通信,使用系统级信号量确保对共享内存区域的访问是互斥的,防止数据竞争和不一致。
对于基于消息队列的通信,确保消息队列的访问权限仅限于需要访问的进程。此外,可以对消息进行加密,以防止潜在的窃听和篡改。
对于基于本地套接字的通信,确保套接字文件的访问权限仅限于需要访问的进程。同时,可以使用 SSL/TLS 进行加密通信,确保通信内容的安全性。
对于基于 D-Bus 的通信,可以使用 D-Bus 的安全策略和访问控制功能限制哪些进程可以访问特定的服务和对象。此外,可以对 D-Bus 消息进行加密,以提高安全性。
对于基于远程对象的通信,可以使用 SSL/TLS 加密通信,确保通信内容的安全性。同时,确保访问远程对象的进程具有合适的身份验证和授权机制。
6. 如何避免共享内存数据竞争?
在使用 QSharedMemory 时,可能会遇到多个进程同时访问共享内存的问题。为避免数据竞争,可以使用 QSystemSemaphore 对共享内存进行互斥访问。通过在进程之间同步信号量,可以确保同一时间只有一个进程能够访问共享内存,从而避免数据竞争。
7. 如何确保 QLocalServer 和 QLocalSocket 之间的通信顺序?
为确保 QLocalServer 和 QLocalSocket 之间的通信顺序,可以使用 Qt 提供的信号与槽机制。当服务器端有新的连接请求时,可以发射一个信号通知客户端。客户端可以连接到这个信号,并在槽函数中处理连接请求。通过这种方式,可以确保通信顺序和处理顺序。
8. 当使用 QProcess 时,如何处理子进程的输出?
在使用 QProcess 启动子进程时,可以通过连接 QProcess 的 readyReadStandardOutput() 和 readyReadStandardError() 信号来获取子进程的输出。在对应的槽函数中,可以使用 QProcess 的 readAllStandardOutput() 和 readAllStandardError() 函数获取子进程的输出数据。通过这种方式,可以实时处理子进程的输出,并在需要时将其显示给用户。
9. 如何处理 D-Bus 通信中的超时和错误?
在使用 D-Bus 进行进程间通信时,可能会遇到超时和错误。为处理这些问题,可以使用 QDBusConnection 的 asyncCall() 函数发送异步调用,并连接到 QDBusPendingCallWatcher 的 finished() 信号。在槽函数中,可以使用 QDBusPendingReply 类检查调用是否成功,并处理可能的错误。通过这种方式,可以实现健壮的 D-Bus 通信,并提高用户体验。
10. 在使用 QRemoteObjectNode 和 QRemoteObjectHost 时,如何处理网络延迟和丢包?
在使用 QRemoteObjectNode 和 QRemoteObjectHost 进行远程对象通信时,可能会遇到网络延迟和丢包的问题。为解决这些问题,可以在通信过程中添加超时和重试机制。例如,在发送请求时,可以设置一个超时时间。如果在超时时间内没有收到回应,可以尝试重新发送请求。此外,可以使用 Qt 提供的 QNetworkSession 和 QNetworkConfiguration 类监控网络状态,并在网络状况较差时采取相应措施,如降低通信速率或暂停通信。
通过这些方案,可以更好地解决在使用 Qt 进程间通信类时可能遇到的问题,从而提高应用程序的稳定性和用户体验。
2.12. 案例分析:不同进程间通信方法在实际项目中的应用
1. 共享内存:图像处理应用
一个实时图像处理应用可能需要在不同进程间传输大量图像数据。在这种情况下,共享内存提供了一种高效的数据传输方式。进程可以将图像数据写入共享内存区域,并通过信号量同步读写操作,确保数据的一致性。
2. 消息队列:工厂自动化系统
在一个工厂自动化系统中,各种设备和控制器需要实时交换信息以协同工作。消息队列可以用于实现这种实时通信需求,设备之间可以通过消息队列发送和接收离散的信息,以实现紧密的协作。
3. 本地套接字:桌面应用间通信
在桌面环境下,不同的应用程序可能需要相互通信以完成某些任务。例如,一个文本编辑器可能需要与文件管理器通信以打开或保存文件。在这种情况下,本地套接字提供了一种高效的通信方式,允许应用程序之间在同一台设备上进行通信。
4. 进程启动:批处理任务调度
在执行批处理任务时,主进程可能需要启动多个外部程序并与之通信以完成任务。使用 QProcess 类可以方便地实现这一需求,主进程可以启动外部程序,并通过进程间通信与之交换信息。
5. D-Bus:桌面环境组件间通信
在 Linux 桌面环境中,各种组件(如面板、应用程序、系统服务等)需要实时交换信息以提供统一的用户体验。D-Bus 提供了一种高效的通信机制,允许这些组件在系统范围内相互通信。
6. 远程对象:分布式计算应用
在一个分布式计算应用中,不同设备上的进程需要协同工作以完成计算任务。远程对象提供了一种跨设备、跨平台的通信方式,允许进程访问和操作其他设备上的对象,实现分布式计算的需求。
2.13.Qt 各版本之间的变化
在 Qt5 和 Qt6 之间的变化中,进程间通信(IPC)类的变化相对较小。
尽管在 Qt5 和 Qt6 之间的变化中,大部分 IPC 类仍然可用,但在使用这些类时,可能需要注意 Qt6 中引入的其他变化,例如更严格的类型检查、新的 API 设计和一些废弃的方法。
此外,Qt6 增强了对 C++17 的支持,并引入了更多的性能改进和新特性。因此,在使用 Qt6 进行进程间通信时,可以充分利用这些新特性来提高 IPC 的效率和可靠性。
主要变化主要涉及废弃的方法和一些类型的调整。下面是一些值得注意的变化:
QSharedMemory
在 Qt6 中,没有引入显著的 API 变化。
QSystemSemaphore
在 Qt6 中,没有引入显著的 API 变化。
QLocalServer 和 QLocalSocket
在 Qt6 中,QLocalServer::serverError() 返回类型从 QLocalServer::ServerError 更改为 QAbstractSocket::SocketError。
在 Qt6 中,QLocalSocket::socketError() 返回类型从 QLocalSocket::LocalSocketError 更改为 QAbstractSocket::SocketError。
QProcess
在 Qt6 中,QProcess::error() 返回类型从 QProcess::ProcessError 更改为 QProcess::Error。
在 Qt6 中,QProcess::startDetached() 的某些重载被标记为废弃。
QDBusConnection、QDBusInterface 和 QDBusMessage
在 Qt6 中,没有引入显著的 API 变化。
QRemoteObjectNode 和 QRemoteObjectHost
在 Qt6 中,没有引入显著的 API 变化。
尽管这些变化可能会影响一些项目的迁移过程,但在大多数情况下,Qt5 到 Qt6 的 IPC 类迁移过程相对较为平滑。然而,在迁移过程中,仍需要注意其他相关的 API 变化,例如字符串处理、事件循环和网络操作等。同时,确保充分利用 Qt6 新引入的特性和性能改进,以优化 IPC 方案。
2.14. 从心理学角度的总结
在开发过程中,我们应始终关注用户体验,而用户体验在很大程度上取决于我们应用程序的响应速度和稳定性。进程间通信作为应用程序之间传递信息的关键环节,其性能直接影响到用户体验的质量。
心理学研究表明,用户在与计算机交互时,对于应用程序的响应速度和稳定性有着很高的期望。在面对繁重的计算任务或数据传输时,用户往往希望系统能够迅速、稳定地完成。在这种情况下,选择适当的进程间通信方式以及优化通信性能就显得尤为重要。
此外,良好的用户体验还取决于应用程序的易用性。在设计和实现进程间通信时,我们应确保应用程序的界面和交互逻辑简洁、直观,以便用户能够轻松地理解和使用。这也意味着在实现进程间通信时,我们需要关注通信协议的设计,以降低用户的认知负担。
总之,从心理学的角度来看,开发者在实现进程间通信时应关注通信性能、稳定性和易用性,以提供优秀的用户体验。通过本文对 Qt 进程间通信类的介绍和探讨,希望能帮助开发者在实际项目中更好地应用这些技术,为用户带来更优秀的产品。
Qt线程/进程间通讯
原文链接:https://blog.csdn.net/weixin_42868530/article/details/125642557
Qt线程/进程间数据通讯
本文主要用于阐述说明Qt线程/进程间数据通讯相关内容
Qt线程间数据通讯
方法一 全局变量
优点:使用方便;
缺点:全局变量长时间占用内存,影响程序空间使用率,且全局变量修改影响整个程序,程序的安全性无法保证;
注意:使用时需根据实际状况,加入锁,防止多线程同时使用同一个变量导致异常;
方法二 信号槽
只有QObject类及其派生的类才能使用信号和槽的机制,在线程间使用信号槽进行通信时,槽函数必须使用元数据类型的参数;如果使用自定义的数据类型,需要在connect之前将其注册(qRegisterMetaType)为元数据类型。
下面例子为使用自定义的数据类型作为信号槽传递的参数,使用方法如下:
// mystruct.h
typedef struct MYSTRUCT{
unsigned char data1[4];
int data2;
double data3;
...
}MYSTRUCT;
------------------------------------------------------------------
//mainwindow.h
... //表示无关代码shenglve
#include "testthread.h"
#include "mystruct.h"
#include <QMetaType>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
void init();
public slots:
void slot_receive(const MYSTRUCT mystruct)
private:
TestThread *m_testThread;
...
}
------------------------------------------------------------------
//mainwindow.cpp
...
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
init();
}
void Mainwindow::init()
{
qRegisterMetaType<MYSTRUCT>("mystruct");
//MYSTRUCT 为自定义数据类型
//mystruct 为定义的名称
m_testThread = new TestThread();
connect(m_testThread, &TestThread::signal_send, this, &Mainwindow::slot_receive);
m_testThread->start();
}
void Mainwindow::slot_receive(const MYSTRUCT mystruct)
{
}
...
------------------------------------------------------------------
//testthread.h
#include "mystruct.h"
...
class TestThread : public QThread
{
Q_OBJECT
public:
TestThread();
virtual void run();
signals:
void signal_send(const MYSTRUCT mystruct);
private:
MYSTRUCT m_struct;
...
}
------------------------------------------------------------------
//testthread.cpp
...
TestThread::TestThread()
{
m_struct.data1[0] = 1;
m_struct.data1[1] = 1;
m_struct.data1[2] = 1;
m_struct.data1[3] = 1;
m_struct.data2 = 2;
m_struct.data3 = 3.0;
}
void run()
{
while(1)
{
emit signal_send(m_struct);
sleep(1000);
}
}
上述例程中线程使用 run() 方式运行,还可以使用 movetothread()(各路大佬推荐使用)方式运行;
线程间用信号槽传递参数的话,要加const,因为const文字常量存在常量区中,生命周期和程序一样长,可以避免slot调用的时候参数的运行期已过造成引用无效;
connect函数的第五个参数:
(1) Qt::AutoConnection
如果发射信号的线程和执行槽函数的线程是同一个线程,此时等同于Qt::DirectConnection;如果不在同一个线程,就等同于Qt::QueuedConnection,是connect函数的默认参数;
(2) Qt::DirectConnection
发射信号和执行槽函数由同一个线程(信号发射的线程)完成,信号发射后槽函数立马执行,执行完毕后才继续执行“emit信号”后面的代码,即“emit信号”是阻塞的;
(3) Qt::QueuedConnection
发射信号的线程和执行槽函数的线程不是在同一个线程,此时发射信号的线程不阻塞,马上返回,当执行槽函数的线程被CPU调度时,就会执行槽函数;
(4) Qt::BlockingQueuedConnection
和Qt::QueuedConnection基本一样,只是发射信号的线程会被阻塞,直到槽函数被执行完毕,如果使用这个属性,需确保发射信号的线程与执行槽函数的线程不同,否则将发生死锁;
(5) Qt::UniqueConnection
唯一关联,该类型可以和上面的类型通过“|”符号配合使用,同一个信号与同一个槽只能调用connect一次,不能多次调用;
Qt进程间数据通讯
方法一 共享内存
原理:两个个进程共用同一片物理内存
实现方式如下:
进程A(数据写入)
//processA.h
#include <QSharedMemory>
class ProcessA
{
...
public:
ProcessA();
~ProcessA();
void writeButtonClicked();
private:
QSharedMemory *m_shareMemory;
...
}
...
---------------------------------------------------------
//processA.cpp
...
ProcessA::ProcessA()
{
...
m_shareMemory = new QSharedMemory(); //实例化QSharedMemory类
m_shareMemory->setKey("TestKey"); //通过setKey()设置标签名称;
//判断当前实例化对象m_shareMemory是否已经与进程连接,如果已经连接,使用函数detach()将与进程分离。
if(m_shareMemory->isAttached())
{
m_shareMemory->detach();
}
//使用函数create()创建共享内存段,判断是否创建成功,若失败,打印错误信息并返回
if(!m_shareMemory->create(50))
{
qDebug() << m_shareMemory->errorString();
return ;
}
...
}
ProcessA::~ProcessA()
{
...
//注意用完以后退出程序时在析构函数中释放
m_shareMemory->detach();
delete m_shareMemory;
m_shareMemory = nullptr;
...
}
void ProcessA::writeButtonClicked()
{
char testStr[50] = "This is my share memory!";
m_shareMemory->lock(); //使用共享内存前需调用lock()将共享内存上锁
char *destination = reinterpret_cast<char *>(m_shareMemory->data());
const char *source = testStr;
memcpy(destination, source, 50); //将数据写入共享内存
m_shareMemory->unlock(); //调用unlock()函数解锁
}
进程B(读取写入)
//processB.h
#include <QSharedMemory>
class ProcessB
{
...
public:
ProcessB();
~ProcessB();
void readButtonClicked();
private:
QSharedMemory *m_shareMemory;
...
}
...
---------------------------------------------------------
//processB.cpp
...
ProcessB::ProcessB()
{
...
m_shareMemory = new QSharedMemory(); //实例化QSharedMemory类
m_shareMemory->setKey("TestKey"); //通过函数setKey()设置标签名称;
//连接共享内存与进程
if(!m_shareMemory->attach())
{
qDebug() << "attach m_shareMemory error!";
return ;
}
...
}
ProcessB::~ProcessB()
{
...
m_shareMemory->detach();
delete m_shareMemory;
m_shareMemory = nullptr;
...
}
void ProcessB::readButtonClicked()
{
char testStr[50];
m_shareMemory->lock(); //使用共享内存前需调用lock()将共享内存上锁
const char *source = (char*)m_shareMemory->constData();
char *destination = testStr;
memcpy(destination, source, 50); //从共享内存里读取数据
m_shareMemory->unlock(); //调用unlock()函数解锁
}
方法二 socket通信
在两个进程之间创建socket,自己定义一套通信协议,通过socket,实现两个进程间的数据传输,在此不做赘述,可以参考socket通信相关内容。