从 IPC 到 QLocalSocket、QSharedMemory:Qt 本机进程间通信完整理解
在 Qt 开发中,程序内部对象之间可以通过信号槽通信;不同机器之间可以通过 TCP/UDP 通信;但如果是同一台电脑上的两个独立程序之间通信 ,就会涉及一个重要概念:IPC,进程间通信。
例如有两个程序:
text
MainUI.exe 显控主界面进程
DataServer.exe 后台数据解析进程
它们是两个独立进程,内存空间互相隔离。MainUI.exe 不能直接访问 DataServer.exe 里的普通变量。两个进程之间要交换数据,就需要 IPC。
Qt 中常见的 IPC 方式有:
text
QLocalSocket / QLocalServer 本机 Socket 通信
QSharedMemory 共享内存
QSystemSemaphore 跨进程同步
QProcess 启动和控制子进程
QFile 简单文件交换
本文重点介绍:
text
IPC 是什么
QLocalSocket 怎么用
QSharedMemory 怎么用
QLocalSocket 和 QSharedMemory 的区别
两个进程如何通过 QLocalSocket 传递 JSON 数据
操作系统底层到底做了什么
一、IPC 是什么
IPC 全称是:
text
Inter-Process Communication
中文叫:
text
进程间通信
它解决的问题是:两个独立进程之间如何交换数据。
在操作系统中,每个进程都有独立的虚拟地址空间:
text
进程 A
├── 变量
├── 堆内存
├── 栈内存
└── 文件描述符 / 句柄
进程 B
├── 变量
├── 堆内存
├── 栈内存
└── 文件描述符 / 句柄
默认情况下:
text
进程 A 不能直接读写进程 B 的内存
进程 B 不能直接读写进程 A 的内存
这是操作系统为了安全和稳定做的隔离。如果一个进程崩溃,不应该直接破坏另一个进程的内存。
但实际工程中经常需要多个进程协作,例如:
text
主界面进程
后台采集进程
日志记录进程
算法计算进程
数据存储进程
这些进程之间要交换命令、状态、JSON、图像帧、目标数据,就需要 IPC。
二、Qt 中常见 IPC 方式对比
| IPC 方式 | Qt 类 | 特点 | 适合场景 |
|---|---|---|---|
| 本地 Socket | QLocalSocket / QLocalServer |
像 TCP 一样读写,但只能本机使用 | JSON、命令、状态、小数据包 |
| 共享内存 | QSharedMemory |
多个进程映射同一块内存,速度快 | 大数组、图像帧、大批量点迹 |
| 信号量 | QSystemSemaphore |
跨进程同步 | 共享内存读写保护 |
| 子进程 | QProcess |
启动、控制外部程序 | 主程序启动算法进程 |
| 文件 | QFile |
简单但效率低 | 配置、日志、低频数据交换 |
| TCP/UDP | QTcpSocket / QUdpSocket |
可跨机器通信 | 网络通信、设备通信 |
其中:
text
QLocalSocket = 本机版 TCP
QSharedMemory = 多进程共享同一块内存
三、QLocalSocket 是什么
QLocalSocket 是 Qt 对本机 IPC 的封装,通常配合 QLocalServer 使用。
结构如下:
text
进程 A:QLocalServer
listen("server_name")
↑
进程 B:QLocalSocket
connectToServer("server_name")
连接成功后,双方就可以像普通 socket 一样读写数据:
cpp
socket->write(data);
socket->readAll();
1. 底层实现
不同系统下,QLocalSocket 的底层实现不同:
text
Windows:Named Pipe,命名管道
Linux / Unix:Unix Domain Socket,本地域套接字
Qt 把这些平台差异封装掉了,所以你在代码里只需要使用:
cpp
QLocalServer
QLocalSocket
四、QLocalSocket 的典型使用场景
场景一:两个本机进程传递 JSON 数据
例如:
text
DataServer.exe:负责接收 UDP 数据、解析目标结果
MainUI.exe:负责界面显示
两者之间通过 QLocalSocket 传递 JSON:
json
{
"interface": "interfaceName",
"param": {
"id": 1,
"tvRange": 1234.27,
"irRange": 1234.29,
"radarRange": 1234.29,
"firingRange": 134.29,
"result": 1
}
}
这种方式适合:
text
数据量不算特别大
要求可靠传输
需要连接状态
希望协议清晰
场景二:防止程序重复启动
很多桌面程序只允许启动一个实例。
第一次启动时:
text
主程序创建 QLocalServer
第二次启动时:
text
发现已有 QLocalServer
通过 QLocalSocket 把启动参数发给第一个程序
然后自己退出
例如第二次启动时传递:
json
{
"cmd": "activateWindow",
"file": "D:/test.csv"
}
第一个程序收到后激活窗口并打开文件。
场景三:主界面进程和后台服务进程通信
例如:
text
主界面进程
├── 发送开始采集命令
├── 发送停止采集命令
└── 接收后台状态
后台服务进程
├── 采集数据
├── 解析协议
└── 返回处理结果
可以设计命令:
json
{
"cmd": "startCollect"
}
json
{
"cmd": "stopCollect"
}
json
{
"cmd": "status",
"running": true,
"targetCount": 12
}
这种方式比用文件轮询更实时,也比直接把所有功能写在一个进程里更稳定。
五、QLocalSocket 使用前的工程配置
qmake 工程:
pro
QT += network
CMake 工程:
cmake
find_package(Qt5 REQUIRED COMPONENTS Core Network)
target_link_libraries(your_target
PRIVATE
Qt5::Core
Qt5::Network
)
头文件:
cpp
#include <QLocalSocket>
#include <QLocalServer>
#include <QJsonObject>
#include <QJsonDocument>
六、为什么 QLocalSocket 发送 JSON 要加长度头
QLocalSocket 是流式通信。
也就是说,你不能认为:
cpp
socket->write(json1);
socket->write(json2);
接收端就一定会收到两次完整 JSON。
实际可能出现:
text
情况一:一次收到 json1
情况二:一次收到 json1 + json2
情况三:第一次收到半个 json1,第二次收到剩下半个
所以不能直接在 readyRead() 中简单写:
cpp
QByteArray data = socket->readAll();
QJsonDocument doc = QJsonDocument::fromJson(data);
更稳妥的做法是自定义协议:
text
[4 字节数据长度][JSON 数据内容]
例如:
text
00000085{"interface":"interfaceName","param":{...}}
前 4 字节表示后面 JSON 的字节长度。接收端先读长度,再按长度读取完整 JSON。
七、QLocalSocket 传递 JSON 示例
下面示例包含两个进程:
text
JsonServer:服务端进程,使用 QLocalServer
JsonClient:客户端进程,使用 QLocalSocket
通信协议:
text
quint32 length + JSON body
1. 公共工具函数:打包 JSON
cpp
#include <QByteArray>
#include <QDataStream>
#include <QJsonDocument>
#include <QJsonObject>
inline QByteArray packJson(const QJsonObject &obj)
{
QByteArray body = QJsonDocument(obj).toJson(QJsonDocument::Compact);
QByteArray packet;
QDataStream out(&packet, QIODevice::WriteOnly);
out.setByteOrder(QDataStream::LittleEndian);
quint32 len = static_cast<quint32>(body.size());
out << len;
packet.append(body);
return packet;
}
数据格式是:
text
4 字节长度 + JSON 内容
2. 公共工具函数:解析接收缓存
cpp
#include <QByteArray>
#include <QDataStream>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
inline QList<QJsonObject> unpackJsonPackets(QByteArray &buffer)
{
QList<QJsonObject> result;
while (true) {
if (buffer.size() < static_cast<int>(sizeof(quint32))) {
break;
}
QDataStream in(buffer.left(sizeof(quint32)));
in.setByteOrder(QDataStream::LittleEndian);
quint32 bodyLen = 0;
in >> bodyLen;
int packetLen = static_cast<int>(sizeof(quint32) + bodyLen);
if (buffer.size() < packetLen) {
break;
}
QByteArray body = buffer.mid(sizeof(quint32), bodyLen);
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(body, &parseError);
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
result.append(doc.object());
} else {
qDebug() << "JSON parse error:" << parseError.errorString();
}
buffer.remove(0, packetLen);
}
return result;
}
这里的关键是:
cpp
buffer.append(socket->readAll());
把每次收到的数据累积到缓存中,然后不断判断是否已经凑够一个完整包。
八、QLocalServer 服务端示例
cpp
#ifndef JSONLOCALSERVER_H
#define JSONLOCALSERVER_H
#include <QObject>
#include <QLocalServer>
#include <QLocalSocket>
#include <QHash>
#include <QByteArray>
#include <QJsonObject>
class JsonLocalServer : public QObject
{
Q_OBJECT
public:
explicit JsonLocalServer(QObject *parent = nullptr);
bool start(const QString &serverName);
void sendJson(QLocalSocket *socket, const QJsonObject &obj);
private slots:
void onNewConnection();
void onReadyRead();
void onDisconnected();
private:
void handleJson(QLocalSocket *socket, const QJsonObject &obj);
private:
QLocalServer *m_server = nullptr;
QHash<QLocalSocket *, QByteArray> m_buffers;
};
#endif
cpp
#include "JsonLocalServer.h"
#include <QDebug>
#include <QJsonDocument>
JsonLocalServer::JsonLocalServer(QObject *parent)
: QObject(parent)
{
m_server = new QLocalServer(this);
connect(m_server, &QLocalServer::newConnection,
this, &JsonLocalServer::onNewConnection);
}
bool JsonLocalServer::start(const QString &serverName)
{
/*
* 程序异常退出后,Linux 下可能残留 socket 文件。
* 启动前 removeServer 可以清理残留。
* 注意:如果已有正常服务端运行,不要随意 remove。
*/
QLocalServer::removeServer(serverName);
if (!m_server->listen(serverName)) {
qDebug() << "listen failed:" << m_server->errorString();
return false;
}
qDebug() << "server started:" << serverName;
return true;
}
void JsonLocalServer::onNewConnection()
{
while (m_server->hasPendingConnections()) {
QLocalSocket *socket = m_server->nextPendingConnection();
qDebug() << "client connected";
m_buffers.insert(socket, QByteArray());
connect(socket, &QLocalSocket::readyRead,
this, &JsonLocalServer::onReadyRead);
connect(socket, &QLocalSocket::disconnected,
this, &JsonLocalServer::onDisconnected);
}
}
void JsonLocalServer::onReadyRead()
{
QLocalSocket *socket = qobject_cast<QLocalSocket *>(sender());
if (!socket) {
return;
}
QByteArray &buffer = m_buffers[socket];
buffer.append(socket->readAll());
QList<QJsonObject> packets = unpackJsonPackets(buffer);
for (const QJsonObject &obj : packets) {
handleJson(socket, obj);
}
}
void JsonLocalServer::handleJson(QLocalSocket *socket, const QJsonObject &obj)
{
qDebug() << "server recv:"
<< QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact));
QString interfaceName = obj.value("interface").toString();
if (interfaceName == "strikeResult") {
QJsonObject reply;
reply["interface"] = "strikeResultAck";
reply["ok"] = true;
reply["message"] = QStringLiteral("服务端已收到打击结果");
sendJson(socket, reply);
}
}
void JsonLocalServer::sendJson(QLocalSocket *socket, const QJsonObject &obj)
{
if (!socket || socket->state() != QLocalSocket::ConnectedState) {
return;
}
QByteArray packet = packJson(obj);
socket->write(packet);
socket->flush();
}
void JsonLocalServer::onDisconnected()
{
QLocalSocket *socket = qobject_cast<QLocalSocket *>(sender());
if (!socket) {
return;
}
qDebug() << "client disconnected";
m_buffers.remove(socket);
socket->deleteLater();
}
服务端启动:
cpp
JsonLocalServer *server = new JsonLocalServer(this);
server->start("strike_result_server");
九、QLocalSocket 客户端示例
cpp
#ifndef JSONLOCALCLIENT_H
#define JSONLOCALCLIENT_H
#include <QObject>
#include <QLocalSocket>
#include <QByteArray>
#include <QJsonObject>
class JsonLocalClient : public QObject
{
Q_OBJECT
public:
explicit JsonLocalClient(QObject *parent = nullptr);
void connectToServer(const QString &serverName);
void sendJson(const QJsonObject &obj);
private slots:
void onConnected();
void onReadyRead();
void onDisconnected();
void onError(QLocalSocket::LocalSocketError error);
private:
void handleJson(const QJsonObject &obj);
private:
QLocalSocket *m_socket = nullptr;
QByteArray m_buffer;
};
#endif
cpp
#include "JsonLocalClient.h"
#include <QDebug>
#include <QJsonDocument>
JsonLocalClient::JsonLocalClient(QObject *parent)
: QObject(parent)
{
m_socket = new QLocalSocket(this);
connect(m_socket, &QLocalSocket::connected,
this, &JsonLocalClient::onConnected);
connect(m_socket, &QLocalSocket::readyRead,
this, &JsonLocalClient::onReadyRead);
connect(m_socket, &QLocalSocket::disconnected,
this, &JsonLocalClient::onDisconnected);
/*
* Qt 5.12 使用 error 信号。
* Qt 5.15 以后常用 errorOccurred。
*/
connect(m_socket,
QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error),
this,
&JsonLocalClient::onError);
}
void JsonLocalClient::connectToServer(const QString &serverName)
{
if (m_socket->state() != QLocalSocket::UnconnectedState) {
m_socket->abort();
}
m_socket->connectToServer(serverName);
}
void JsonLocalClient::onConnected()
{
qDebug() << "connected to local server";
}
void JsonLocalClient::sendJson(const QJsonObject &obj)
{
if (m_socket->state() != QLocalSocket::ConnectedState) {
qDebug() << "socket not connected";
return;
}
QByteArray packet = packJson(obj);
m_socket->write(packet);
m_socket->flush();
}
void JsonLocalClient::onReadyRead()
{
m_buffer.append(m_socket->readAll());
QList<QJsonObject> packets = unpackJsonPackets(m_buffer);
for (const QJsonObject &obj : packets) {
handleJson(obj);
}
}
void JsonLocalClient::handleJson(const QJsonObject &obj)
{
qDebug() << "client recv:"
<< QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact));
}
void JsonLocalClient::onDisconnected()
{
qDebug() << "local socket disconnected";
}
void JsonLocalClient::onError(QLocalSocket::LocalSocketError error)
{
qDebug() << "local socket error:" << error
<< m_socket->errorString();
}
客户端连接并发送打击结果:
cpp
JsonLocalClient *client = new JsonLocalClient(this);
client->connectToServer("strike_result_server");
QJsonObject param;
param["id"] = 1;
param["tvRange"] = 1234.27;
param["irRange"] = 1234.29;
param["radarRange"] = 1234.29;
param["firingRange"] = 123.29;
param["result"] = 1;
QJsonObject root;
root["interface"] = "strikeResult";
root["param"] = param;
client->sendJson(root);
十、QLocalSocket 的代码原理
从 Qt 代码层看,QLocalSocket 的使用流程是:
text
1. 服务端 listen
2. 客户端 connectToServer
3. 操作系统建立本机连接
4. 服务端触发 newConnection
5. 客户端触发 connected
6. 任意一方 write
7. 对端触发 readyRead
8. readAll/read 读取数据
9. 断开连接时触发 disconnected
可以理解为:
text
QLocalSocket 封装了本机进程之间的一条可靠字节流通道
它和 TCP 一样是字节流,但不走真实网卡,不需要 IP 地址和端口。
十一、操作系统底层干了什么:QLocalSocket 篇
1. Windows 下
Windows 下通常使用 Named Pipe,命名管道。
服务端监听时,操作系统会创建一个命名管道对象,例如类似:
text
\\.\pipe\strike_result_server
客户端连接时,根据这个名字找到对应管道。
底层过程大致是:
text
1. 服务端请求内核创建命名管道
2. 内核维护一个管道对象和缓冲区
3. 客户端通过同名管道连接
4. 服务端和客户端分别持有一个句柄
5. 一方 WriteFile 写入内核缓冲区
6. 另一方 ReadFile 从内核缓冲区读取
7. Qt 把这些事件包装成 readyRead、connected、disconnected
用户代码中写:
cpp
socket->write(data);
本质上是把数据从用户态内存复制到内核缓冲区。
对端:
cpp
socket->readAll();
本质上是从内核缓冲区复制到另一个进程的用户态内存。
2. Linux / Unix 下
Linux / Unix 下通常使用 Unix Domain Socket。
它是一种本机 socket,不通过网卡,不经过 IP 协议栈,通常在文件系统中有一个 socket 文件路径。
底层过程大致是:
text
1. 服务端创建 Unix Domain Socket
2. bind 到一个本地路径或抽象名字
3. listen 等待连接
4. 客户端 connect 到该名字
5. 内核建立两端 socket 的连接关系
6. 一方 write,数据进入内核 socket buffer
7. 另一方 read,从 socket buffer 取出数据
8. Qt 事件循环监听文件描述符可读事件,触发 readyRead
所以在 Linux 下,readyRead() 的本质是:
text
内核发现 socket 文件描述符可读
Qt 事件分发器收到通知
Qt 发出 readyRead 信号
十二、QLocalSocket 的优点和问题
优点
text
使用简单,像 TCP
支持连接状态
支持双向通信
可靠传输
适合 JSON、命令、状态信息
跨平台封装好
问题
text
只能本机通信
是字节流,需要自己处理粘包/半包
不适合直接传超大数据
服务端异常退出后可能残留 serverName
工程建议:
text
小数据、命令、JSON:QLocalSocket
大数据、图像、点迹数组:QSharedMemory
大数据 + 通知:QSharedMemory + QLocalSocket
十三、QSharedMemory 是什么
QSharedMemory 是 Qt 提供的共享内存类。
它允许多个进程访问同一块物理内存区域。
普通进程内存隔离是这样:
text
进程 A 内存 进程 B 内存
A变量 B变量
A缓存 B缓存
共享内存是这样:
text
进程 A 共享内存 进程 B
映射 ───────► 同一块内存 ◄─────── 映射
进程 A 写入共享内存后,进程 B 可以从同一块共享内存中读取。
十四、QSharedMemory 适合什么场景
QSharedMemory 适合大数据量场景,例如:
text
图像帧
雷达点迹数组
目标列表缓存
大块二进制数据
频繁刷新数据
例如:
text
采集进程每 20ms 写入一批目标数据
界面进程每 50ms 读取最新一批目标数据
如果每次都通过 socket 复制大量数据,会增加内核缓冲区复制和解析开销。
共享内存的优势是:
text
两个进程映射同一块内存
减少大数据在进程之间来回复制
速度更快
十五、QSharedMemory 使用示例
下面示例演示:
text
进程 A 创建共享内存并写入 JSON
进程 B 连接共享内存并读取 JSON
注意:这只是基础示例。正式工程中,建议结合数据头、版本号、长度、校验、读写状态。
1. 写入进程
cpp
#include <QSharedMemory>
#include <QBuffer>
#include <QDataStream>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
#include <cstring>
void writeSharedJson()
{
QSharedMemory sharedMemory;
sharedMemory.setKey("strike_shared_memory");
const int memorySize = 4096;
if (!sharedMemory.create(memorySize)) {
qDebug() << "create shared memory failed:" << sharedMemory.errorString();
return;
}
QJsonObject param;
param["id"] = 1;
param["tvRange"] = 1144.27;
param["result"] = 1;
QJsonObject root;
root["interface"] = "interfaceName";
root["param"] = param;
QByteArray json = QJsonDocument(root).toJson(QJsonDocument::Compact);
if (json.size() + static_cast<int>(sizeof(quint32)) > memorySize) {
qDebug() << "json too large";
return;
}
sharedMemory.lock();
char *to = static_cast<char *>(sharedMemory.data());
std::memset(to, 0, memorySize);
quint32 len = static_cast<quint32>(json.size());
std::memcpy(to, &len, sizeof(quint32));
std::memcpy(to + sizeof(quint32), json.constData(), json.size());
sharedMemory.unlock();
qDebug() << "write shared memory ok";
}
2. 读取进程
cpp
#include <QSharedMemory>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
#include <cstring>
void readSharedJson()
{
QSharedMemory sharedMemory;
sharedMemory.setKey("strike_shared_memory");
if (!sharedMemory.attach()) {
qDebug() << "attach shared memory failed:" << sharedMemory.errorString();
return;
}
sharedMemory.lock();
const char *from = static_cast<const char *>(sharedMemory.constData());
quint32 len = 0;
std::memcpy(&len, from, sizeof(quint32));
QByteArray json(from + sizeof(quint32), static_cast<int>(len));
sharedMemory.unlock();
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(json, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qDebug() << "json parse failed:" << parseError.errorString();
return;
}
qDebug() << "read json:" << QString::fromUtf8(json);
}
十六、QSharedMemory 的代码原理
QSharedMemory 的典型流程是:
text
写入进程:
1. setKey 设置共享内存名称
2. create 创建共享内存
3. lock 加锁
4. data 获取内存地址
5. memcpy 写入数据
6. unlock 解锁
读取进程:
1. setKey 设置相同名称
2. attach 连接共享内存
3. lock 加锁
4. constData 获取只读地址
5. memcpy 读取数据
6. unlock 解锁
核心接口:
cpp
sharedMemory.create(size);
sharedMemory.attach();
sharedMemory.lock();
sharedMemory.data();
sharedMemory.constData();
sharedMemory.unlock();
sharedMemory.detach();
十七、操作系统底层干了什么:QSharedMemory 篇
共享内存的底层原理可以理解为:
text
1. 操作系统创建一块内核管理的共享内存对象
2. 进程 A 把这块共享内存映射到自己的虚拟地址空间
3. 进程 B 也把同一块共享内存映射到自己的虚拟地址空间
4. 两个进程看到的是不同的虚拟地址
5. 但它们最终映射到同一批物理内存页
示意:
text
进程 A 虚拟地址 0x10000000 ─┐
├── 物理内存页
进程 B 虚拟地址 0x30000000 ─┘
所以:
text
进程 A 写入共享内存
进程 B 读取共享内存
本质上读写的是同一块物理内存
这就是共享内存速度快的原因。
但是它也有问题:
text
共享内存本身只负责共享数据
不负责告诉对方"什么时候有新数据"
不负责数据格式
不负责读写冲突
所以通常需要配合:
text
QSystemSemaphore
QLocalSocket
QTimer
数据头结构
十八、QSharedMemory 的重要问题
1. 必须做同步
如果一个进程正在写,另一个进程同时读,就可能读到半截数据。
例如写入 JSON 时:
text
刚写完长度
还没写完 JSON 内容
读取进程就开始读
读取进程可能拿到错误数据。
所以要加锁:
cpp
sharedMemory.lock();
...
sharedMemory.unlock();
2. 必须定义数据格式
不能随便把复杂对象直接塞进去。
错误示例:
cpp
QString text = "hello";
std::memcpy(sharedMemory.data(), &text, sizeof(QString));
这是错误的。QString 内部包含指针和引用计数,另一个进程不能直接使用这个指针。
正确做法是写入纯字节数据:
text
quint32 length + UTF-8 JSON
或者:
text
固定头结构 + 二进制数组
3. 共享内存不会自动通知对方
进程 A 写入共享内存后,进程 B 不会自动收到信号。
可以用几种方式处理:
text
方式一:进程 B 用 QTimer 定时读取
方式二:进程 A 写完后通过 QLocalSocket 通知进程 B
方式三:使用系统信号量做同步通知
工程上最推荐:
text
QSharedMemory 传大数据
QLocalSocket 传通知和命令
十九、QLocalSocket + QSharedMemory 组合方案
对于高频大数据,推荐架构:
text
采集进程
├── 把大数据写入 QSharedMemory
└── 通过 QLocalSocket 发送通知:
{"cmd":"newData","size":1024,"seq":1001}
界面进程
├── 收到 QLocalSocket 通知
└── 从 QSharedMemory 读取最新数据
这种设计的优点:
text
大数据不走 socket,减少复制
socket 只传小通知,协议清晰
界面进程能知道什么时候读取
性能和实时性都比较好
示意:
text
小 JSON 通知
采集进程 ───────────────► 界面进程
│ │
│ │
└──── 写入共享内存 ◄──────┘
大块目标数据
二十、三个典型工程使用场景总结
场景一:两个本机进程传递 JSON 数据
推荐:
text
QLocalSocket + QLocalServer
适合:
text
命令控制
状态同步
业务结果传输
打击结果数据
示例:
json
{
"interface": "interfaceName",
"param": {
"id": 1,
"result": 1
}
}
特点:
text
协议清晰
调试方便
数据量不大时非常合适
场景二:主程序防重复启动
推荐:
text
QLocalServer 检测已有实例
QLocalSocket 发送第二次启动参数
流程:
text
第一次启动:
listen("app_single_instance")
第二次启动:
connectToServer("app_single_instance")
连接成功说明已有实例
发送参数后退出
适合桌面软件:
text
资源管理系统
显控软件
配置工具
日志查看器
场景三:大数据实时共享
推荐:
text
QSharedMemory + QLocalSocket
适合:
text
图像帧
雷达点迹
大量目标数据
高频状态缓存
分工:
text
QSharedMemory:放大数据
QLocalSocket:发通知
这是性能和工程可维护性都比较好的方案。
二十一、QLocalSocket 和 QSharedMemory 怎么选
| 需求 | 推荐方案 |
|---|---|
| 传 JSON 命令 | QLocalSocket |
| 传状态、日志、小数据包 | QLocalSocket |
| 传大数组、大图像、大量点迹 | QSharedMemory |
| 大数据并且要通知对方 | QSharedMemory + QLocalSocket |
| 跨电脑通信 | QTcpSocket / QUdpSocket |
| 防止重复启动 | QLocalServer + QLocalSocket |
| 简单低频交换 | QFile 也可以 |
简单结论:
text
小数据走 QLocalSocket
大数据走 QSharedMemory
大数据通知走 QLocalSocket
跨机器通信走 TCP/UDP
二十二、常见错误总结
1. 把 QLocalSocket 当成数据包通信
错误想法:
text
write 一次,对方 readyRead 一次
正确理解:
text
QLocalSocket 是字节流
必须处理粘包和半包
推荐:
text
长度头 + JSON
2. 共享内存直接放复杂对象
错误:
cpp
QString text;
memcpy(sharedMemory.data(), &text, sizeof(QString));
正确:
text
把 QString 转成 UTF-8 QByteArray
再写入共享内存
3. 共享内存不加锁
错误:
cpp
memcpy(sharedMemory.data(), data, size);
正确:
cpp
sharedMemory.lock();
memcpy(sharedMemory.data(), data, size);
sharedMemory.unlock();
4. QLocalServer 启动失败不处理
服务端异常退出后,可能残留 serverName。
启动时可以处理:
cpp
QLocalServer::removeServer(serverName);
server->listen(serverName);
但要注意:不要误删正在运行的正常服务端。
5. 把 QLocalSocket 用于跨设备通信
QLocalSocket 只能本机通信。
跨设备通信应该使用:
text
QTcpSocket
QUdpSocket
二十三、总结
IPC 是进程间通信,用来解决多个独立进程之间的数据交换问题。
在 Qt 中:
text
QLocalSocket / QLocalServer 适合本机进程间的小数据、命令、JSON 通信
QSharedMemory 适合本机进程间的大数据共享
QSystemSemaphore / lock 机制用于共享内存同步
从操作系统角度看:
text
QLocalSocket:
Windows 下通常是命名管道
Linux 下通常是 Unix Domain Socket
本质是内核维护的一条本机字节流通道
QSharedMemory:
本质是多个进程把同一块物理内存映射到各自虚拟地址空间
速度快,但需要自己处理同步、数据格式和通知
工程中最推荐的组合方式是:
text
QLocalSocket 负责控制命令、JSON、状态通知
QSharedMemory 负责大块实时数据
例如两个进程传递打击结果 JSON 数据时,使用 QLocalSocket 非常合适;如果后续数据量变大,比如需要传递大量目标点迹或图像帧,可以升级为:
text
QSharedMemory 存数据
QLocalSocket 发通知
这样既能保持协议清晰,又能兼顾性能和实时性。