【POCO 实战】第 7 篇:进程、共享内存、网络——POCO 嵌入式 IPC 完整方案

一句话核心结论:嵌入式 IPC 的 3 大难题——父子进程协作、跨进程数据零拷贝、跨主机网络通信——POCO 用 3 大模块(Process / Foundation / Net)给了同一套 C++ API 跨 Linux / QNX / Windows / VxWorks 的统一答案。一套代码,6 大平台——这是 POCO 在车规、机器人、工控领域能成为事实标准的最重要原因。


系列导航

#文章状态
1第 1 篇:POCO 是什么?凭啥值 10K star?✅ 已发布
2第 2 篇:搭建 POCO 开发环境(Linux/Windows/macOS/QNX)✅ 已发布
3第 3 篇:基础类型与智能指针(Poco::Any / AutoPtr / SharedPtr)✅ 已发布
4第 4 篇:字符串与格式化(UTF-8/UTF-16/正则/数字解析)✅ 已发布
5第 5 篇:文件系统与目录遍历(Path/File/Directory/Walk)✅ 已发布
6第 6 篇:日志、配置、事件、加密、压缩——日常工具 5 件套✅ 已发布
7本文:进程、共享内存、网络——POCO 嵌入式 IPC 完整方案✅ 已发布
8第 8 篇:HTTP 客户端/服务端:基于 Net::HTTPServer 实现车机 OTA🔜 计划中
9第 9 篇:多线程与线程池:ThreadPool / Task / ActiveMethod🔜 计划中
10第 10 篇:Net_Server 框架:TCPServer / UDPServer 实战🔜 计划中

前言:为什么这一篇是 POCO 系列最长的一篇?

嵌入式系统里,”进程间通信“这 5 个字能难倒一片老司机。

我自己在 QNX 平台做过车机系统,给大家说一个真实场景:

车载 IVI(In-Vehicle Infotainment,车载信息娱乐系统)里同时跑着 3 个进程

  • audio_app:音频解码、混音、功放
  • nav_app:导航引擎、地图渲染
  • vehicle_daemon:CAN 总线网关、车身状态

需求:

  1. nav_app 启动后要等 audio_app 起来才能发声
  2. audio_appvehicle_daemon零延迟 共享车速信号(64 字节,每秒 100 次)
  3. 远程手机 App 要通过 4G 模块给 nav_app 发 POI 兴趣点

不用框架,你得自己撸 fork/exec、自己撸 mmap、自己撸 socket、自己撸 select/poll/epoll,还要保证一套代码在 QNX / Linux / Android / Windows CE 上都跑得起来

POCO 的回答:3 大模块 → 3 个类家族 → 同一套 C++ API。

通信场景速度进程关系POCO 类
父子进程中(需走 stdin/stdout)亲缘Poco::Process + Poco::Pipe
跨进程数据零拷贝极快(纳秒级)同主机Poco::SharedMemory + Poco::NamedEvent + Poco::NamedMutex
跨主机网络受网络带宽限制跨主机Poco::Net::Socket + Poco::Net::ServerSocket + Poco::Net::SocketAddress + Poco::Net::DNS

读完这一篇(~2500 行 + 8 张 Mermaid + 12 张表 + 40+ 段代码),你将:

  1. 能用同一套 API 在 Linux / QNX / Windows / Android 上写 进程、共享内存、Socket 通信
  2. 看懂 POCO 源码中 Process_UNIX.cpp / Process_WIN32.cpp 的实现差异
  3. 直接把第 11 节的 “综合实战” 复制到你的项目里跑
  4. 知道 QNX / Android 这些特殊平台下 POCO 的坑和替代方案

一、Process 进程管理:用 C++ 跨平台 fork/exec

1.1 为什么不能用 std::system

std::system("ls -l") 看起来简单,但只能拿退出码,拿不到 stdout,在车机、医疗器械这种需要审计进程输出的场景完全不能用。

Poco::Process 的目标:用同一套 C++ API 替代 fork/exec/pipe/waitpid——一套代码,6 大平台

graph LR
    A["📝 C++ 调用<br/>Process::launch()"]:::input
    B["🌍 平台判断<br/>#ifdef POCO_OS"]:::logic
    C["🐧 Linux/QNX<br/>fork + execvp"]:::linux
    D["🍎 macOS<br/>fork + posix_spawn"]:::linux
    E["🪟 Windows<br/>CreateProcessW"]:::win
    F["📱 Android<br/>posix_spawn"]:::linux
    G["🚀 子进程<br/>PID/Handle"]:::output

    A --> B
    B --> C
    B --> D
    B --> E
    B --> F
    C --> G
    D --> G
    E --> G
    F --> G

    style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style B fill:#FFF9C4,stroke:#F9A825,color:#333
    style C fill:#B5EAD7,stroke:#80CBC4,color:#333
    style D fill:#B5EAD7,stroke:#80CBC4,color:#333
    style E fill:#FFB3C6,stroke:#F48FB1,color:#333
    style F fill:#B5EAD7,stroke:#80CBC4,color:#333
    style G fill:#E8D5F5,stroke:#CE93D8,color:#333

关键点:POCO 通过 Platform_WIN32.h / Platform_POSIX.h平台差异编译期消解,调用方完全无感。

1.2 Poco::Process 核心 API

API作用平台实现
Process::launch(cmd, args)同步启动子进程POSIX: fork+execvp,Win: CreateProcessW
Process::launch(cmd, args, options)启动并设置 stdin/stdout 重定向通过 Pipe
Process::launch(cmd, args, stdoutPipe)启动并捕获 stdoutPipeImpl
pid()子进程 PIDgetpid() / GetProcessId()
wait()阻塞等待子进程结束waitpid() / WaitForSingleObject
tryWait()非阻塞等待waitpid(WNOHANG)
kill()强制终止kill(SIGKILL) / TerminateProcess
ProcessHandle子进程句柄RAII,自动回收
Process::isRunning(pid)检查进程是否存活kill(pid, 0) / OpenProcess

1.3 三种创建子进程方式对比

维度std::systemposix_spawnPoco::Process::launch
跨平台✅ 几乎所有❌ 仅 POSIX✅ Linux/macOS/Win/QNX/VxWorks
捕获 stdout❌ 不支持⚠️ 需手动 pipe()Pipe 接管
stdin 输入⚠️ 需手动Pipe
拿退出码
拿到 PID
非阻塞 wait⚠️ 需 waitpidtryWait()
错误信息⚠️ 需 errnoPoco::Exception
代码行数1 行30+ 行5 行
嵌入式可用⚠️ 需 shell

结论:POCO 是唯一同时满足「跨平台 + 捕获 stdout + 错误信息友好 + 嵌入式可用」 的方案。

1.4 最简单的例子:启动子进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// demo1_simple_launch.cpp
// 编译:g++ -o demo1 demo1_simple_launch.cpp -lPocoFoundation
#include <Poco/Process.h>
#include <Poco/Pipe.h>
#include <Poco/PipeStream.h>
#include <Poco/Exception.h>
#include <iostream>

int main() {
try {
// 1. 准备命令和参数
std::string cmd = "ls";
std::vector<std::string> args = { "-l", "-h" };

// 2. 准备 stdout 捕获管道
Poco::Pipe outPipe;

// 3. 准备启动选项
Poco::Process::Args procArgs(args.begin(), args.end());
Poco::Process::Env env; // 继承父进程环境变量

// 4. 启动子进程,stdout 重定向到 outPipe
Poco::ProcessHandle ph = Poco::Process::launch(
cmd,
procArgs,
nullptr, // stdin
&outPipe, // stdout → 管道
nullptr, // stderr
env
);

// 5. 把管道读端包装成 istream
Poco::PipeInputStream istr(outPipe);

// 6. 边读边打印
std::string line;
while (std::getline(istr, line)) {
std::cout << " " << line << std::endl;
}

// 7. 等待子进程结束
int rc = ph.wait();
std::cout << "子进程退出码: " << rc << std::endl;

} catch (const Poco::Exception& ex) {
std::cerr << "POCO 异常: " << ex.displayText() << std::endl;
return 1;
}
return 0;
}

注意Pipe 必须在 launch() 之前 构造。Process::launch同步阻塞到子进程 fork 后立即返回,不是等子进程结束——wait() 才是阻塞收尸。

1.5 ProcessHandle 是什么?

ProcessHandle 是 POCO 的 RAII 句柄:构造时绑定 PID,析构时不会自动 wait——只有关闭底层文件描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// demo2_handle.cpp
#include <Poco/Process.h>
#include <Poco/Pipe.h>
#include <iostream>

int main() {
Poco::Pipe outPipe;
Poco::ProcessHandle ph = Poco::Process::launch(
"sleep", Poco::Process::Args{"5"},
nullptr, &outPipe, nullptr
);

std::cout << "子进程 PID: " << ph.id() << std::endl;

// tryWait 非阻塞检查
if (!ph.tryWait()) {
std::cout << "子进程还在跑,去干点别的..." << std::endl;
// 5 秒后回来
Poco::Thread::sleep(2000);
}

// wait 阻塞等到子进程结束
int rc = ph.wait();
std::cout << "子进程退出: rc=" << rc << std::endl;
return 0;
}

1.6 kill() 强制终止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// demo3_kill.cpp
#include <Poco/Process.h>
#include <Poco/Pipe.h>
#include <Poco/Exception.h>
#include <iostream>

int main() {
Poco::Pipe outPipe;
Poco::ProcessHandle ph = Poco::Process::launch(
"ping", Poco::Process::Args{"127.0.0.1", "-c", "100"},
nullptr, &outPipe, nullptr
);

// 跑 1 秒后杀掉
Poco::Thread::sleep(1000);
try {
ph.kill(); // SIGKILL / TerminateProcess
std::cout << "已 kill 子进程" << std::endl;
} catch (const Poco::Exception& ex) {
std::cerr << "kill 失败: " << ex.displayText() << std::endl;
}

int rc = ph.wait();
std::cout << "退出码: " << rc << std::endl;
// Unix: rc = 128 + 9 (SIGKILL)
// Windows: rc = 1
return 0;
}

1.7 Process::isRunning 检查任意进程是否存活

1
2
3
4
5
6
7
8
9
10
11
12
13
// demo4_isrunning.cpp
#include <Poco/Process.h>
#include <iostream>

int main() {
Poco::Process::PID pid = 1234; // 假设要查的 PID
if (Poco::Process::isRunning(pid)) {
std::cout << "PID " << pid << " 还在跑" << std::endl;
} else {
std::cout << "PID " << pid << " 已死" << std::endl;
}
return 0;
}

实现原理:POSIX 下 kill(pid, 0)——不发信号,仅检查权限和存在性。Windows 下 OpenProcess + GetExitCodeProcess

1.8 父进程与子进程:用环境变量传配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// demo5_env.cpp
// 父进程
#include <Poco/Process.h>
#include <Poco/Pipe.h>
#include <iostream>

int main() {
Poco::Pipe outPipe;
Poco::Process::Env env;
env["APP_ROLE"] = "child";
env["CONFIG_FILE"] = "/etc/ivient/audio.conf";
env["LOG_LEVEL"] = "DEBUG";

Poco::ProcessHandle ph = Poco::Process::launch(
"./child_app", Poco::Process::Args{},
nullptr, &outPipe, nullptr, env
);

Poco::PipeInputStream istr(outPipe);
std::string line;
while (std::getline(istr, line)) std::cout << line << "\n";
return ph.wait();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// child_app.cpp
// 子进程读取环境变量
#include <Poco/Environment.h>
#include <Poco/Process.h>
#include <iostream>

int main() {
std::string role = Poco::Environment::get("APP_ROLE", "standalone");
std::string cfg = Poco::Environment::get("CONFIG_FILE", "/etc/default.conf");
std::string lvl = Poco::Environment::get("LOG_LEVEL", "INFO");

std::cout << "I am " << role << ", cfg=" << cfg
<< ", log=" << lvl << std::endl;
return 0;
}

1.9 stdin 反向通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// demo6_stdin.cpp
// 父进程往子进程 stdin 写数据
#include <Poco/Process.h>
#include <Poco/Pipe.h>
#include <Poco/PipeStream.h>
#include <iostream>

int main() {
Poco::Pipe inPipe; // 父写 → 子读
Poco::Pipe outPipe; // 父读 ← 子写

Poco::ProcessHandle ph = Poco::Process::launch(
"cat", Poco::Process::Args{},
&inPipe, &outPipe, nullptr
);

Poco::PipeOutputStream ostr(inPipe);
Poco::PipeInputStream istr(outPipe);

// 父进程写 3 行
ostr << "hello\n" << "poco\n" << "world\n";
ostr.flush();
inPipe.close(); // 关闭写端,让 cat 知道 EOF

// 父进程读 3 行
std::string line;
while (std::getline(istr, line)) {
std::cout << "echo: " << line << std::endl;
}
return ph.wait();
}

1.10 平台差异:POCO 帮你抹平了哪些坑?

差异Linux/QNXWindowsPOCO 如何抹平
创建子进程fork + execvpCreateProcessWProcess::launch 统一 API
进程标识pid_tHANDLE / DWORDProcess::PID(强类型)
等待子进程waitpidWaitForSingleObjectProcessHandle::wait
信号终止kill(pid, SIGKILL)TerminateProcessProcessHandle::kill
进程信息/proc/<pid>/statGetProcessMemoryInfoProcess::ProcessInfo
退出码WEXITSTATUSGetExitCodeProcessProcessHandle::wait 返回 int
环境变量extern char** environGetEnvironmentVariablePoco::Environment
当前进程路径/proc/self/exeGetModuleFileNamePoco::Path::self()
当前进程 PIDgetpid()GetCurrentProcessIdPoco::Process::id()

1.11 Process::ProcessInfo:拿子进程的 CPU/内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// demo7_processinfo.cpp
#include <Poco/Process.h>
#include <Poco/Pipe.h>
#include <iostream>

int main() {
Poco::Pipe outPipe;
Poco::ProcessHandle ph = Poco::Process::launch(
"openssl", Poco::Process::Args{"speed", "aes-256-cbc"},
nullptr, &outPipe, nullptr
);

Poco::PipeInputStream istr(outPipe);
std::string line;
while (std::getline(istr, line)) std::cout << " " << line << "\n";

int rc = ph.wait();

// 拿子进程退出后的资源信息
Poco::Process::ProcessInfo info = ph.info();
std::cout << "退出码: " << info.exitCode() << std::endl;
// Linux 上还可拿到 rusage(POSIX only)
return 0;
}

1.12 与 std::filesystem 的协作:扫描外部工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// demo8_scan_tools.cpp
// 嵌入式场景:启动前扫描 /usr/bin 里的工具
#include <Poco/Process.h>
#include <Poco/DirectoryIterator.h>
#include <Poco/Path.h>
#include <Poco/Exception.h>
#include <iostream>

bool toolExists(const std::string& name) {
for (auto it = Poco::DirectoryIterator("/usr/bin");
it != Poco::DirectoryIterator(); ++it) {
if (it.name() == name) return true;
}
return false;
}

void runIfExists(const std::string& tool,
const std::vector<std::string>& args) {
if (!toolExists(tool)) {
std::cout << " 跳过: " << tool << " 不存在" << std::endl;
return;
}
try {
Poco::Pipe p;
Poco::ProcessHandle ph = Poco::Process::launch(
tool, Poco::Process::Args(args.begin(), args.end()),
nullptr, &p, nullptr
);
Poco::PipeInputStream istr(p);
std::string line;
while (std::getline(istr, line)) std::cout << line << "\n";
ph.wait();
} catch (const Poco::Exception& ex) {
std::cerr << tool << " 失败: " << ex.displayText() << std::endl;
}
}

int main() {
runIfExists("uptime", {});
runIfExists("df", {"-h"});
runIfExists("ip", {"addr"});
return 0;
}

二、Pipe 管道:父子进程通信的”瑞士军刀”

2.1 什么是 Pipe?

Pipe(管道) 是 Unix 哲学的核心:用文件描述符把两个进程串起来

graph LR
    A["📝 父进程"]:::parent
    P1["📥 Pipe<br/>写端"]:::pipe
    P2["📤 Pipe<br/>读端"]:::pipe
    B["🚀 子进程<br/>(cat)"]:::child

    A -- "ostr << data" --> P1
    P1 -. "内核缓冲区" .-> P2
    P2 -- "stdin" --> B

    style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style P1 fill:#FFDAB9,stroke:#FFAB76,color:#333
    style P2 fill:#FFDAB9,stroke:#FFAB76,color:#333
    style B fill:#B5EAD7,stroke:#80CBC4,color:#333

两个关键事实

  1. Pipe 是 单向 的——要双向通信,开 2 个 Pipe
  2. Pipe 是 匿名的——只能用于有亲缘关系的进程(父子、兄弟);跨进程要靠 NamedPipe(POCO 没直接提供,要用平台 API 或 Boost)

2.2 Poco::Pipe vs Poco::PipeStream vs Poco::NamedPipe

作用平台
Poco::Pipe裸管道(fd / HANDLE 封装)全部
Poco::PipeStreamPipe 包装成 std::iostream全部
Poco::PipeInputStream仅读端全部
Poco::PipeOutputStream仅写端全部
Poco::NamedPipe命名管道,跨进程仅 Windows / Linux 5.6+

2.3 Pipe 完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// demo9_pipe_full.cpp
#include <Poco/Process.h>
#include <Poco/Pipe.h>
#include <Poco/PipeStream.h>
#include <Poco/Thread.h>
#include <iostream>
#include <string>

int main() {
// 1. 创建两个 Pipe:一个 stdin,一个 stdout
Poco::Pipe inPipe; // 父写 → 子读 (stdin)
Poco::Pipe outPipe; // 父读 ← 子写 (stdout)

// 2. 启动子进程(awk 是个文本处理工具,能从 stdin 读)
Poco::ProcessHandle ph = Poco::Process::launch(
"awk", Poco::Process::Args{"{ print toupper($0) }"},
&inPipe, &outPipe, nullptr
);

// 3. 父进程开两个线程:1 个写、1 个读
std::thread writer([&] {
Poco::PipeOutputStream ostr(inPipe);
ostr << "hello, poco!\n";
ostr << "pipe is easy.\n";
ostr.flush();
inPipe.close(); // 关键:关闭写端让子进程 EOF
});

std::thread reader([&] {
Poco::PipeInputStream istr(outPipe);
std::string line;
while (std::getline(istr, line)) {
std::cout << " 子进程说: " << line << std::endl;
}
});

writer.join();
reader.join();
ph.wait();
return 0;
}

2.4 Pipe 的 4 个核心方法

方法作用平台实现
Pipe()构造一个空 pipepipe(fd) / CreatePipe
read(void* buf, int len)从读端读read() / ReadFile
write(const void* buf, int len)从写端写write() / WriteFile
close()关闭两端close(fd) / CloseHandle

2.5 跨平台坑:Windows 下的 Pipe

维度LinuxWindows踩坑
句柄类型int (fd)HANDLEPipe 内部统一为 Poco::PipeHandle
是否可继承默认可继承默认不可继承子进程要能拿到,必须用 Poco::Process::launch(POCO 帮你设好)
EOF 检测read() == 0ReadFile() == FALSE && GetLastError()==ERROR_BROKEN_PIPEPOCO 帮你处理
阻塞默认阻塞默认阻塞一致
非阻塞O_NONBLOCKioctlsocket / SetNamedPipeHandleState都不推荐;POCO 用 PollSet 替代

2.6 PipeStream 异步 IO 替代品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// demo10_pipe_async.cpp
// 用 Pipe + PollSet 实现非阻塞读取
#include <Poco/Process.h>
#include <Poco/Pipe.h>
#include <Poco/Net/PollSet.h> // Foundation 提供
#include <iostream>

int main() {
Poco::Pipe outPipe;
Poco::ProcessHandle ph = Poco::Process::launch(
"yes", Poco::Process::Args{}, // 一直输出 y
nullptr, &outPipe, nullptr
);

// PollSet 注册读端,超时 100ms
char buf[1024];
int total = 0;
for (int i = 0; i < 5; ++i) {
if (outPipe.readBytesAvailable() > 0) {
int n = outPipe.read(buf, sizeof(buf));
total += n;
}
Poco::Thread::sleep(200);
}
ph.kill();
ph.wait();
std::cout << "5 秒读了 " << total << " 字节" << std::endl;
return 0;
}

设计哲学:POCO 推荐把 Pipe 设成非阻塞——它建议用 PollSet 或干脆用多线程。readBytesAvailable() 是非阻塞 peek。

2.7 用 Pipe 实现 RPC 风格的子进程调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// demo11_rpc.cpp
// 父进程调用 "工控计算器" 子进程
#include <Poco/Process.h>
#include <Poco/Pipe.h>
#include <Poco/PipeStream.h>
#include <sstream>
#include <string>

struct CalcRequest {
std::string op;
double a, b;
std::string serialize() const {
std::ostringstream os;
os << op << " " << a << " " << b << "\n";
return os.str();
}
};

double calc(const CalcRequest& req) {
static Poco::Pipe inPipe, outPipe;
static Poco::ProcessHandle ph = Poco::Process::launch(
"python3", Poco::Process::Args{"-c",
"import sys\n"
"while True:\n"
" line = sys.stdin.readline()\n"
" if not line: break\n"
" op, a, b = line.split()\n"
" a, b = float(a), float(b)\n"
" r = {'+': a+b, '-': a-b, '*': a*b, '/': a/b}[op]\n"
" print(r)\n"
" sys.stdout.flush()\n"},
&inPipe, &outPipe, nullptr
);

Poco::PipeOutputStream ostr(inPipe);
Poco::PipeInputStream istr(outPipe);
ostr << req.serialize();
ostr.flush();
std::string line;
std::getline(istr, line);
return std::stod(line);
}

int main() {
std::cout << calc({"+", 1, 2}) << std::endl;
std::cout << calc({"*", 3, 4}) << std::endl;
std::cout << calc({"/", 10, 3}) << std::endl;
return 0;
}

真实生产:用 JSON + nlohmann/json 替代字符串协议,QNX 上用 QNET 替代 TCP 跑同一个服务。


三、SharedMemory 共享内存:跨进程零拷贝的”高速公路”

3.1 为什么需要共享内存?

Pipe / Socket / 信号 的通病:要经过内核协议栈拷贝。一次 memcpy 64 字节的车速信号,看着不起眼,但每秒 100 次 = 100 次 syscall = 1ms 延迟——实时系统受不了

共享内存 的本质:把同一块物理内存映射到多个进程的虚拟地址空间

graph TB
    subgraph "进程 A 地址空间"
        VA_A["0x7F00_0000_0000<br/>SharedMemory 起始地址"]
    end
    subgraph "进程 B 地址空间"
        VA_B["0x7F00_0000_0000<br/>SharedMemory 起始地址"]
    end
    subgraph "进程 C 地址空间"
        VA_C["0x7F00_0000_0000<br/>SharedMemory 起始地址"]
    end
    subgraph "物理内存 / mmap"
        PHY["📦 4 KB 物理页<br/>[车速,转速,水温,...]"]
    end

    VA_A -.->|"mmap 映射"| PHY
    VA_B -.->|"mmap 映射"| PHY
    VA_C -.->|"mmap 映射"| PHY

    style VA_A fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style VA_B fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style VA_C fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style PHY fill:#FFB3C6,stroke:#F48FB1,color:#333

3 个关键事实

  1. 共享内存不能跨主机——只能同主机同 OS 实例
  2. 共享内存没有同步——必须配 NamedEvent / NamedMutex / 原子变量
  3. 共享内存最快——纳秒级延迟,比 Pipe 快 1000 倍

3.2 Poco::SharedMemory vs Poco::NamedSharedMemory vs Poco::MemoryPool

作用域跨进程典型用途
Poco::SharedMemory父子进程❌ 亲缘fork 后父子共享
Poco::NamedSharedMemory任意进程✅ 命名QNX 上不同进程通信
Poco::MemoryPool单进程替代 malloc

3.3 SharedMemory 完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// demo12_shm.cpp
#include <Poco/SharedMemory.h>
#include <Poco/Process.h>
#include <Poco/Pipe.h>
#include <Poco/Environment.h>
#include <Poco/Exception.h>
#include <iostream>
#include <cstring>
#include <unistd.h>

struct VehicleData {
int magic; // 0xCAFEBABE 校验
float speed_kmh; // 车速
float rpm; // 转速
float coolant_c; // 水温
long timestamp_ms; // 时间戳
};

int main(int argc, char** argv) {
const char* SHM_NAME = "/vehicle_shm";
const std::size_t SHM_SIZE = sizeof(VehicleData);

if (argc < 2) {
// ========== 父进程:创建并写入 ==========
try {
// 1. 创建共享内存(如果已存在则 map)
Poco::SharedMemory shm(SHM_NAME, SHM_SIZE,
Poco::SharedMemory::AM_WRITE);

// 2. 拿到指针,写数据
VehicleData* data = reinterpret_cast<VehicleData*>(shm.begin());
std::memset(data, 0, SHM_SIZE);
data->magic = 0xCAFEBABE;

for (int i = 0; i < 10; ++i) {
data->speed_kmh = 60.0f + i;
data->rpm = 2000.0f + i * 100;
data->coolant_c = 85.0f + i * 0.5f;
data->timestamp_ms = Poco::Timestamp().epochMicroseconds() / 1000;
std::cout << "父进程写入: speed=" << data->speed_kmh
<< " km/h" << std::endl;
Poco::Thread::sleep(200);
}

// 3. 启动子进程
Poco::Process::Env env;
env["VEHICLE_SHM"] = SHM_NAME;
Poco::Process::launch("./demo12_shm", Poco::Process::Args{"child"}, env).wait();

} catch (const Poco::Exception& ex) {
std::cerr << "父进程错误: " << ex.displayText() << std::endl;
return 1;
}
} else {
// ========== 子进程:读取 ==========
Poco::Thread::sleep(100); // 等父进程先写
try {
Poco::SharedMemory shm(SHM_NAME, SHM_SIZE,
Poco::SharedMemory::AM_READ);
const VehicleData* data = reinterpret_cast<const VehicleData*>(shm.begin());
std::cout << "子进程读取: speed=" << data->speed_kmh
<< " rpm=" << data->rpm
<< " magic=0x" << std::hex << data->magic << std::dec
<< std::endl;
} catch (const Poco::Exception& ex) {
std::cerr << "子进程错误: " << ex.displayText() << std::endl;
return 1;
}
}
return 0;
}

3.4 NamedSharedMemory:跨任意进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// demo13_named_shm.cpp
// 进程 A:写入
#include <Poco/NamedSharedMemory.h>
#include <Poco/Event.h>
#include <iostream>
#include <cstring>
#include <unistd.h>

struct Message {
int version;
char text[256];
};

int main() {
const std::string NAME = "/my_named_shm";
const std::size_t SIZE = sizeof(Message);

// 1. 创建命名共享内存
Poco::NamedSharedMemory nsm(NAME, SIZE,
Poco::SharedMemory::AM_WRITE);

// 2. 写入
Message* msg = reinterpret_cast<Message*>(nsm.begin());
msg->version = 1;
std::strncpy(msg->text, "Hello from A", sizeof(msg->text));

std::cout << "A 已写入: " << msg->text << std::endl;
std::cout << "A 等待 B 读..." << std::endl;
sleep(3);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// demo13b_named_shm.cpp
// 进程 B:读取
#include <Poco/NamedSharedMemory.h>
#include <iostream>
#include <unistd.h>

struct Message {
int version;
char text[256];
};

int main() {
const std::string NAME = "/my_named_shm";
const std::size_t SIZE = sizeof(Message);

sleep(1); // 等 A 先创建
Poco::NamedSharedMemory nsm(NAME, SIZE,
Poco::SharedMemory::AM_READ);
const Message* msg = reinterpret_cast<const Message*>(nsm.begin());
std::cout << "B 读到: v" << msg->version << " " << msg->text << std::endl;
return 0;
}

3.5 平台实现差异

平台SharedMemoryNamedSharedMemory
Linux / QNX / Androidmmap(MAP_ANONYMOUS) + fork 继承shm_open + mmap
macOS同 Linuxshm_open + mmap(注意 /var/run 权限)
WindowsCreateFileMapping(INVALID_HANDLE_VALUE) + MapViewOfFile同上 + 命名
VxWorksmmap不支持(无文件系统 shm)—— 用 Message Queue 替代

3.6 性能对比

通信方式延迟 (单次 64B)吞吐 (1KB 持续)系统调用
Pipe~5 μs~50 MB/sread/write
TCP localhost~10 μs~30 MB/ssend/recv
Unix Domain Socket~3 μs~80 MB/ssend/recv
共享内存~50 ns~5 GB/s无(直接读写)
内存映射文件~1 μs~1 GB/s缺页中断

QNX 实测:车机上 100Hz 的车速信号,用 Pipe 要占 1% CPU,用共享内存几乎 0%。


四、NamedEvent / NamedMutex 命名同步:跨进程互斥与事件

4.1 为什么需要命名同步原语?

共享内存解决了”数据怎么共享”,但没解决”谁先谁后”——你必须配同步原语。

POCO 提供 3 种跨进程同步:

原语用途跨进程
EventPoco::Event线程间唤醒
NamedEventPoco::NamedEvent跨进程唤醒
MutexPoco::Mutex线程互斥
NamedMutexPoco::NamedMutex跨进程互斥
SemaphorePoco::Semaphore线程计数信号量
NamedSemaphorePoco::NamedSemaphore跨进程计数信号量

4.2 NamedEvent:跨进程”发车灯”

graph LR
    A["📝 进程 A<br/>(数据生产者)"]:::producer
    SHM["📦 共享内存<br/>(VehicleData)"]:::memory
    B["📱 进程 B<br/>(数据消费者)"]:::consumer
    EVT["🚥 NamedEvent<br/>(data_ready)"]:::event

    A -- "1. 写共享内存" --> SHM
    A -- "2. set() 发信号" --> EVT
    EVT -- "3. wait() 唤醒" --> B
    B -- "4. 读共享内存" --> SHM

    style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style B fill:#B5EAD7,stroke:#80CBC4,color:#333
    style SHM fill:#FFB3C6,stroke:#F48FB1,color:#333
    style EVT fill:#FFF9C4,stroke:#F9A825,color:#333
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// demo14_named_event_writer.cpp
// 数据生产者
#include <Poco/NamedEvent.h>
#include <Poco/NamedSharedMemory.h>
#include <Poco/Thread.h>
#include <iostream>
#include <atomic>
#include <csignal>

std::atomic<bool> g_stop{false};
void onSignal(int) { g_stop = true; }

int main() {
std::signal(SIGINT, onSignal);

Poco::NamedSharedMemory shm("/vehicle_shm", 4096,
Poco::SharedMemory::AM_WRITE);
Poco::NamedEvent evt("/data_ready");

volatile int* counter = reinterpret_cast<volatile int*>(shm.begin());
*counter = 0;

while (!g_stop) {
(*counter)++;
evt.set(); // 发信号给读者
Poco::Thread::sleep(10); // 10ms
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// demo14b_named_event_reader.cpp
// 数据消费者
#include <Poco/NamedEvent.h>
#include <Poco/NamedSharedMemory.h>
#include <iostream>
#include <csignal>

std::atomic<bool> g_stop{false};
void onSignal(int) { g_stop = true; }

int main() {
std::signal(SIGINT, onSignal);

Poco::NamedSharedMemory shm("/vehicle_shm", 4096,
Poco::SharedMemory::AM_READ);
Poco::NamedEvent evt("/data_ready");

const volatile int* counter = reinterpret_cast<const volatile int*>(shm.begin());

while (!g_stop) {
evt.wait(); // 阻塞等生产者发信号
std::cout << "消费者读到: counter=" << *counter << std::endl;
}
return 0;
}

4.3 NamedMutex:跨进程互斥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// demo15_named_mutex.cpp
// 多进程互斥写共享内存
#include <Poco/NamedMutex.h>
#include <Poco/NamedSharedMemory.h>
#include <iostream>
#include <thread>
#include <chrono>

struct Counter {
int value;
};

int main(int argc, char** argv) {
const std::string SHM = "/counter_shm";
const std::string MUT = "/counter_mutex";
const std::size_t SIZE = sizeof(Counter);

if (argc > 1 && std::string(argv[1]) == "init") {
// 初始化进程:创建 SHM 和 Mutex
Poco::NamedSharedMemory shm(SHM, SIZE, Poco::SharedMemory::AM_WRITE);
Poco::NamedMutex mut(MUT); // 自动创建(第一次)
auto* c = reinterpret_cast<Counter*>(shm.begin());
c->value = 0;
std::cout << "已初始化" << std::endl;
return 0;
}

// 工作进程:累加 1000 次
Poco::NamedSharedMemory shm(SHM, SIZE, Poco::SharedMemory::AM_WRITE);
Poco::NamedMutex mut(MUT);

auto* c = reinterpret_cast<Counter*>(shm.begin());

for (int i = 0; i < 1000; ++i) {
mut.lock();
c->value++;
mut.unlock();
}
std::cout << "本进程累加完,当前值: " << c->value << std::endl;
return 0;
}

POCO 1.15 之前NamedMutexlock() / unlock() 不是 RAII。建议Poco::NamedMutex::ScopedLock

1
2
3
Poco::NamedMutex::ScopedLock lock(mut);
c->value++;
// 析构时自动 unlock

4.4 Poco::NamedEvent 4 种唤醒模式

模式行为用途
Event::EVENT_AUTORESETwait() 唤醒后自动 reset类似 Win32 CreateEvent(NULL, FALSE, ...)
Event::EVENT_MANUALRESETset() 后所有 wait() 唤醒,需手动 reset()广播信号
1
2
3
4
5
6
7
8
9
// demo16_event_mode.cpp
Poco::NamedEvent evt("/evt", false /* auto-reset 默认 */);
evt.set(); // 唤醒 1 个 wait
evt.set(); // 再唤醒 1 个 wait
// 2 个 wait 全部唤醒,evt 自动 reset

Poco::NamedEvent mevt("/mevt", true /* manual */);
mevt.set(); // 唤醒所有 wait
mevt.reset(); // 重新进入阻塞

4.5 POCO vs POSIX vs Win32 同步原语对比

维度POSIX sem_tPOSIX pthread_mutex_tWin32 CreateMutexPoco::NamedMutex
跨平台❌ 仅 POSIX❌ 仅 POSIX❌ 仅 Win✅ 全部
跨进程✅ 命名⚠️ 需在共享内存✅ 命名✅ 命名
优先级继承⚠️ 需 PTHREAD_PRIO_INHERIT⚠️ 同上⚠️ 取决于底层
RAII 封装ScopedLock
异常安全❌ 需手动 unlock✅ 析构 unlock
超时sem_timedwaitpthread_mutex_timedlockWaitForSingleObject(t)tryLock(ms)
错误信息❌ errno❌ errno⚠️ GetLastErrorPoco::Exception
嵌入式可用

4.6 避坑:跨进程 Mutex 的 5 个死亡陷阱

陷阱后果解决
忘记 unlock所有进程永久阻塞ScopedLock
进程崩溃不释放Mutex 死锁QNX 有 SEM_REMOVEFILE_ON_CLOSE,Linux 用心跳
递归锁死锁同一进程第二次 lock() 阻塞Poco::FastMutex 配合 RecursiveMutex
优先级反转低优先级任务持锁,高优先级等pthread_mutexattr_setprotocol(PRIO_INHERIT)
跨 CPU 架构32/64 位进程混用,原子操作错位强制所有进程同架构编译

4.7 tryLock(ms) 超时模式

1
2
3
4
5
6
7
8
9
// demo17_trylock.cpp
Poco::NamedMutex mut("/m");
Poco::NamedMutex::ScopedLock lock(mut, 100 /* 100ms 超时 */);
if (!lock.locked()) {
std::cerr << "拿不到锁,跳过" << std::endl;
return;
}
// 拿到锁,做事
// lock 析构时自动 unlock

4.8 NamedSemaphore 跨进程计数信号量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// demo18_named_sem.cpp
// 任务池:A 投递任务,B/C 抢
#include <Poco/NamedSemaphore.h>
#include <Poco/Thread.h>
#include <iostream>

int main(int argc, char** argv) {
Poco::NamedSemaphore sem("/task_sem", 0, 100); // 初始 0,上限 100

if (argc > 1 && std::string(argv[1]) == "producer") {
for (int i = 0; i < 5; ++i) {
sem.set(); // +1
std::cout << "投递任务 " << i << std::endl;
Poco::Thread::sleep(500);
}
} else {
for (int i = 0; i < 5; ++i) {
sem.wait(); // -1
std::cout << "消费者拿到任务" << std::endl;
}
}
return 0;
}

五、Environment 环境变量:进程配置传递

5.1 Poco::Environment 是什么?

POSIX 下环境变量是 extern char** environ,C++17 才加 std::getenv。POCO 把环境变量封装成 类型安全的 C++ 接口

5.2 核心 API

API作用平台
Environment::get(name)取环境变量,不存在抛 NotFoundException全部
Environment::get(name, default)取环境变量,不存在返回 default全部
Environment::has(name)是否存在全部
Environment::set(name, value)设置全部
Environment::unset(name)删除全部
Environment::list()列出所有全部

5.3 完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// demo19_env.cpp
#include <Poco/Environment.h>
#include <iostream>

int main() {
// 1. 读取
std::string home = Poco::Environment::get("HOME", "/tmp");
std::string user = Poco::Environment::get("USER", "anonymous");
std::cout << "HOME=" << home << " USER=" << user << std::endl;

// 2. 检查存在
if (Poco::Environment::has("DEBUG")) {
std::cout << "DEBUG 模式已开启" << std::endl;
}

// 3. 写入
Poco::Environment::set("APP_VERSION", "1.0.0");
Poco::Environment::set("START_TIME", "2026-06-24T10:00:00");

// 4. 列出
for (const auto& [k, v] : Poco::Environment::list()) {
std::cout << k << "=" << v << std::endl;
}

// 5. 删除
Poco::Environment::unset("DEBUG");
return 0;
}

5.4 平台差异

维度Linux/QNXWindows
存储environ 全局变量进程环境块(PEB)
大小限制无硬限制32 KB
区分大小写✅ 区分不区分
持久化仅当前进程 + 子进程同左,但 cmd.exe 看不到 setlocale 改的
UnicodeUTF-8 字节UTF-16 宽字符

5.5 在 Process::launch 时传环境变量

1
2
3
4
5
6
7
// demo20_env_launch.cpp
Poco::Process::Env env;
env["LANG"] = "zh_CN.UTF-8";
env["TZ"] = "Asia/Shanghai";
env["APP_DEBUG"] = "1";

Poco::Process::launch("my_app", Poco::Process::Args{}, nullptr, nullptr, nullptr, env);

六、Socket 套接字:Net 模块的基石

6.1 POCO Net 模块架构

graph TB
    subgraph "高层"
        HTTP["HTTPClient/HTTPServer"]:::high
        TCPS["TCPServer/TCPServerConnection"]:::high
        UDPS["UDPServer/DatagramSocket"]:::high
        SMTP["SMTP/SFTP/WebSocket"]:::high
    end
    subgraph "中层"
        SS["ServerSocket"]:::mid
        CS["ClientSocket"]:::mid
        SA["SocketAddress"]:::mid
    end
    subgraph "底层"
        SK["Socket 基类<br/>(raw BSD/Winsock)"]:::low
        DNS["DNS"]:::low
        NI["NetworkInterface"]:::low
        IF["IPAddress"]:::low
    end

    HTTP --> CS
    HTTP --> SS
    TCPS --> SS
    TCPS --> CS
    UDPS --> SK
    SS --> SK
    CS --> SK
    SS --> SA
    CS --> SA
    SK --> DNS
    SK --> NI
    SK --> IF

    style HTTP fill:#E8D5F5,stroke:#CE93D8,color:#333
    style TCPS fill:#E8D5F5,stroke:#CE93D8,color:#333
    style UDPS fill:#E8D5F5,stroke:#CE93D8,color:#333
    style SMTP fill:#E8D5F5,stroke:#CE93D8,color:#333
    style SS fill:#FFDAB9,stroke:#FFAB76,color:#333
    style CS fill:#FFDAB9,stroke:#FFAB76,color:#333
    style SA fill:#FFF9C4,stroke:#F9A825,color:#333
    style SK fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style DNS fill:#B5EAD7,stroke:#80CBC4,color:#333
    style NI fill:#B5EAD7,stroke:#80CBC4,color:#333
    style IF fill:#B5EAD7,stroke:#80CBC4,color:#333

3 个层次

  1. 底层Socket(裸 BSD/Winsock 封装)+ SocketAddress + DNS + IPAddress
  2. 中层ServerSocket / ClientSocket(单连接)/ DatagramSocket(UDP)
  3. 高层TCPServer(多连接)/ HTTPServer(HTTP 协议)

6.2 Poco::Net::SocketAddress:地址的”瑞士军刀”

统一 IPv4 和 IPv6——SocketAddress 是 POCO 最被低估的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// demo21_socket_address.cpp
#include <Poco/Net/SocketAddress.h>
#include <Poco/Net/IPAddress.h>
#include <iostream>

int main() {
// 1. IPv4
Poco::Net::SocketAddress v4("192.168.1.100", 8080);
std::cout << "IPv4: " << v4.toString() << std::endl; // 192.168.1.100:8080
std::cout << "host=" << v4.host().toString() << " port=" << v4.port() << std::endl;

// 2. IPv6
Poco::Net::SocketAddress v6("::1", 8080);
std::cout << "IPv6: " << v6.toString() << std::endl; // [::1]:8080
std::cout << "family=" << (v6.family() == Poco::Net::AddressFamily::IPv6 ? "IPv6" : "IPv4") << std::endl;

// 3. 任意地址(0.0.0.0 / ::)
Poco::Net::SocketAddress any4(Poco::Net::IPAddress(), 8080);
std::cout << "ANY: " << any4.toString() << std::endl;

// 4. 解析 hostname(会触发 DNS)
try {
Poco::Net::SocketAddress resolved("www.example.com", 80);
std::cout << "解析: " << resolved.toString() << std::endl;
} catch (const Poco::Exception& ex) {
std::cerr << "DNS 失败: " << ex.displayText() << std::endl;
}
return 0;
}

6.3 IPAddress:纯 IP 地址

方法作用
IPAddress()0.0.0.0(任意地址)
IPAddress("1.2.3.4")解析 IPv4
IPAddress("::1")解析 IPv6
IPAddress::parse("1.2.3.4:8080")解析带端口
isLoopback()是否 127.0.0.0/8
isPrivate()是否 10/8, 172.16/12, 192.168/16
isMulticast()是否组播
toString()转字符串
1
2
3
4
5
6
7
8
9
10
11
12
// demo22_ipaddress.cpp
Poco::Net::IPAddress ip("10.0.0.1");
std::cout << "loopback? " << ip.isLoopback() << std::endl; // 0
std::cout << "private? " << ip.isPrivate() << std::endl; // 1
std::cout << "v4? " << ip.family() << std::endl; // IPv4

// 子网判断
Poco::Net::IPAddress mask("255.255.255.0");
Poco::Net::IPAddress subnet = Poco::Net::IPAddress::parse("192.168.1.0/24");
std::cout << "192.168.1.50 in 192.168.1.0/24? "
<< ((Poco::Net::IPAddress("192.168.1.50") & Poco::Net::IPAddress("255.255.255.0")) == subnet)
<< std::endl; // 1

6.4 TCP ClientSocket 完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// demo23_tcp_client.cpp
#include <Poco/Net/ClientSocket.h>
#include <Poco/Net/SocketAddress.h>
#include <Poco/Net/StreamSocket.h>
#include <iostream>
#include <string>

int main() {
try {
// 1. 创建 socket 并连接
Poco::Net::SocketAddress sa("127.0.0.1", 9000);
Poco::Net::StreamSocket ss(sa);

// 2. 发送 HTTP 请求
std::string request =
"GET / HTTP/1.1\r\n"
"Host: 127.0.0.1:9000\r\n"
"Connection: close\r\n"
"\r\n";
ss.sendBytes(request.data(), request.size());

// 3. 接收响应
char buf[4096];
int n = ss.receiveBytes(buf, sizeof(buf));
std::cout << "收到 " << n << " 字节:\n" << std::string(buf, n) << std::endl;

// 4. 关闭
ss.close();
} catch (const Poco::Exception& ex) {
std::cerr << "错误: " << ex.displayText() << std::endl;
return 1;
}
return 0;
}

6.5 TCP ServerSocket 完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// demo24_tcp_server.cpp
#include <Poco/Net/ServerSocket.h>
#include <Poco/Net/StreamSocket.h>
#include <Poco/Net/SocketAddress.h>
#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> g_conn_id{0};

void handleClient(Poco::Net::StreamSocket ss) {
int id = ++g_conn_id;
std::cout << "[conn " << id << "] 来自 "
<< ss.peerAddress().toString() << std::endl;

char buf[1024];
int n = ss.receiveBytes(buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
std::cout << "[conn " << id << "] 收到: " << buf;
std::string reply = "HTTP/1.0 200 OK\r\nContent-Length: 2\r\n\r\nOK";
ss.sendBytes(reply.data(), reply.size());
}
ss.close();
}

int main() {
// 1. 创建 ServerSocket 并 bind
Poco::Net::ServerSocket server(Poco::Net::SocketAddress("0.0.0.0", 9000));
std::cout << "监听 " << server.address().toString() << std::endl;

// 2. 循环 accept
while (true) {
Poco::Net::StreamSocket client = server.acceptConnection();
std::thread(handleClient, std::move(client)).detach();
}
return 0;
}

6.6 Poco::Net::Socket vs 原生 BSD socket

维度原生 socket(int,...)Poco::Net::Socket
跨平台❌ Win/Linux API 略不同✅ 统一
错误处理errno / WSAGetLastErrorPoco::Exception
RAII 析构❌ 需手动 close✅ 析构自动 close
非阻塞fcntl(O_NONBLOCK)setBlocking(false)
复用地址SO_REUSEADDRsetReuseAddress(true)
接收超时SO_RCVTIMEOsetReceiveTimeout(t)
缓冲区SO_SNDBUFsetSendBufferSize
keep-aliveSO_KEEPALIVEsetKeepAlive(true)
TCP_NODELAYsetsockoptsetNoDelay(true)
直接拿 fd⚠️ sockfd() / impl()(仅 Unix)

6.7 Socket 的 4 大配置方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// demo25_socket_options.cpp
Poco::Net::StreamSocket ss(sa);

// 1. 阻塞 vs 非阻塞
ss.setBlocking(false);

// 2. 接收超时(对阻塞模式有效)
ss.setReceiveTimeout(Poco::Timespan(5, 0)); // 5 秒

// 3. 端口复用(重启时避免 TIME_WAIT)
ss.setReuseAddress(true);

// 4. TCP_NODELAY(关闭 Nagle,对小包响应快)
ss.setNoDelay(true);

// 5. 缓冲区
ss.setSendBufferSize(64 * 1024);
ss.setReceiveBufferSize(64 * 1024);

6.8 UDP DatagramSocket 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// demo26_udp_server.cpp
#include <Poco/Net/DatagramSocket.h>
#include <Poco/Net/SocketAddress.h>
#include <iostream>

int main() {
Poco::Net::DatagramSocket server(Poco::Net::SocketAddress("0.0.0.0", 9001));
std::cout << "UDP 监听 " << server.address().toString() << std::endl;

char buf[2048];
Poco::Net::SocketAddress sender;
int n = server.receiveFrom(buf, sizeof(buf), sender);
std::cout << "来自 " << sender.toString() << ": " << std::string(buf, n) << std::endl;

std::string reply = "ack: " + std::string(buf, n);
server.sendTo(reply.data(), reply.size(), sender);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// demo26b_udp_client.cpp
#include <Poco/Net/DatagramSocket.h>
#include <Poco/Net/SocketAddress.h>
#include <iostream>

int main() {
Poco::Net::DatagramSocket client;
Poco::Net::SocketAddress dest("127.0.0.1", 9001);

std::string msg = "hello UDP";
client.sendTo(msg.data(), msg.size(), dest);

char buf[1024];
Poco::Net::SocketAddress sender;
int n = client.receiveFrom(buf, sizeof(buf), sender);
std::cout << "回复: " << std::string(buf, n) << std::endl;
return 0;
}

6.9 时序图:HTTP 客户端完整交互

sequenceDiagram
    actor User as 👤 用户
    participant App as 📱 ClientSocket
    participant DNS as 🌐 DNS
    participant Server as 🖥️ ServerSocket

    User->>App: 启动 HTTP GET
    App->>DNS: 解析 www.example.com
    DNS-->>App: 93.184.216.34
    App->>Server: TCP SYN (port 80)
    Server-->>App: SYN-ACK
    App->>Server: ACK + HTTP Request
    Server-->>App: HTTP Response 200
    App->>App: receiveBytes()
    App-->>User: 渲染页面
    App->>Server: FIN
    Server-->>App: FIN-ACK

6.10 多路复用:PollSet 监听多 socket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// demo27_pollset.cpp
// 用 PollSet 同时监听多个 socket
#include <Poco/Net/PollSet.h>
#include <Poco/Net/ServerSocket.h>
#include <Poco/Net/StreamSocket.h>
#include <iostream>

int main() {
Poco::Net::ServerSocket server(Poco::Net::SocketAddress("0.0.0.0", 9002));
Poco::Net::PollSet pollSet;
pollSet.add(server, Poco::Net::PollSet::POLL_READ);

std::map<Poco::Net::StreamSocket, std::string> clients;

while (true) {
Poco::Net::PollSet::SocketMap readable;
if (pollSet.poll(readable, 1000 /* 1s */) > 0) {
for (auto& [sock, mode] : readable) {
if (sock == server) {
auto client = server.acceptConnection();
pollSet.add(client, Poco::Net::PollSet::POLL_READ);
clients[client] = client.peerAddress().toString();
std::cout << "新连接: " << clients[client] << std::endl;
} else {
char buf[1024];
int n = sock.receiveBytes(buf, sizeof(buf));
if (n <= 0) {
std::cout << "断开: " << clients[sock] << std::endl;
pollSet.remove(sock);
clients.erase(sock);
} else {
std::string echo(buf, n);
sock.sendBytes(echo.data(), echo.size());
}
}
}
}
}
return 0;
}

真实生产:用 Poco::Net::TCPServer(基于 PollSet)替代手写。

6.11 Poco::Net::Socket 11 个常用方法

方法作用平台
connect(addr)主动连接全部
bind(addr, reuse)绑定全部
listen(backlog)监听全部
acceptConnection(client)接受连接全部
sendBytes(buf, len, flags)发送全部
receiveBytes(buf, len, flags)接收全部
sendTo(buf, len, addr, flags)UDP 发送全部
receiveFrom(buf, len, addr, flags)UDP 接收全部
shutdown(how)半关闭全部
close()关闭全部
getBlocking(), setBlocking(b)阻塞模式全部

七、DNS 域名解析

7.1 Poco::Net::DNS 是什么?

getaddrinfo 的 C++ 封装,支持同步 / 异步 / 超时 3 种模式。

7.2 核心 API

API作用
DNS::resolve(host)同步解析,返回 HostEntry
DNS::resolve(host, timeout)同步 + 超时
DNS::hostByName(...)resolve
DNS::hostByAddress(...)反向解析(IP → 域名)
DNS::thisHost()本机 hostname
DNS::reload()重新加载 /etc/hosts(Unix only)

7.3 完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// demo28_dns.cpp
#include <Poco/Net/DNS.h>
#include <Poco/Net/HostEntry.h>
#include <Poco/Net/IPAddress.h>
#include <iostream>

int main() {
try {
// 1. 正向解析
Poco::Net::HostEntry he = Poco::Net::DNS::resolve("www.example.com");
std::cout << "主机名: " << he.name() << std::endl;
std::cout << "别名: ";
for (const auto& a : he.aliases()) std::cout << a << " ";
std::cout << std::endl;
std::cout << "IP 列表: ";
for (const auto& ip : he.addresses()) std::cout << ip.toString() << " ";
std::cout << std::endl;

// 2. 反向解析
Poco::Net::HostEntry rev = Poco::Net::DNS::hostByAddress("1.1.1.1");
std::cout << "1.1.1.1 反查: " << rev.name() << std::endl;

// 3. 本机信息
std::cout << "本机: " << Poco::Net::DNS::thisHost() << std::endl;

// 4. 超时解析
try {
auto he2 = Poco::Net::DNS::resolve("nonexistent.invalid",
Poco::Timespan(2, 0));
} catch (const Poco::TimeoutException&) {
std::cout << "DNS 超时(正常)" << std::endl;
}
} catch (const Poco::Exception& ex) {
std::cerr << "DNS 错误: " << ex.displayText() << std::endl;
return 1;
}
return 0;
}

7.4 嵌入式场景:无 DNS 服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// demo29_dns_embedded.cpp
// 嵌入式设备通常没 DNS,用 hosts 文件兜底
#include <Poco/Net/DNS.h>
#include <Poco/File.h>
#include <Poco/Path.h>
#include <Poco/Exception.h>
#include <iostream>

bool tryResolve(const std::string& host) {
try {
auto he = Poco::Net::DNS::resolve(host);
std::cout << host << " -> ";
for (const auto& ip : he.addresses()) std::cout << ip.toString() << " ";
std::cout << std::endl;
return true;
} catch (const Poco::Exception&) {
return false;
}
}

int main() {
// 1. 尝试正常 DNS
if (tryResolve("www.baidu.com")) return 0;

// 2. 失败时读 /etc/hosts
std::cout << "DNS 失败,读 /etc/hosts" << std::endl;
Poco::File hosts("/etc/hosts");
if (hosts.exists()) {
// 简单 grep,真实项目用正则
std::ifstream in("/etc/hosts");
std::string line;
while (std::getline(in, line)) {
if (line.find("baidu") != std::string::npos) {
std::cout << "命中: " << line << std::endl;
}
}
}
return 0;
}

7.5 平台实现

平台底层配置文件
Linux / QNX / Androidgetaddrinfo(3)/etc/hosts, /etc/resolv.conf
macOS同上同上
Windowsgetaddrinfo (WinSock)C:\Windows\System32\drivers\etc\hosts
VxWorksgetaddrinfo/etc/hosts, /etc/resolv.conf

7.6 异步 DNS(高阶)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// demo30_dns_async.cpp
// 真正异步需要配合线程
#include <Poco/Net/DNS.h>
#include <Poco/Thread.h>
#include <Poco/ThreadTarget.h>
#include <atomic>
#include <iostream>

std::atomic<bool> g_done{false};
std::string g_ip;

void resolveThread(const std::string& host) {
try {
auto he = Poco::Net::DNS::resolve(host);
g_ip = he.addresses().begin()->toString();
} catch (const Poco::Exception& ex) {
g_ip = "ERROR: " + ex.displayText();
}
g_done = true;
}

int main() {
Poco::Thread t;
t.start(Poco::ThreadTarget(resolveThread, "www.example.com"));

while (!g_done) {
std::cout << "." << std::flush;
Poco::Thread::sleep(100);
}
t.join();
std::cout << "\nIP=" << g_ip << std::endl;
return 0;
}

八、NetworkInterface:网络接口枚举

8.1 嵌入式为什么要枚举网卡?

真实场景:车机同时有 4 个网卡——以太网(调试)、Wi-Fi(手机互联)、4G(远程)、CAN(车身)——每个网卡绑不同的服务

8.2 核心 API

API作用
NetworkInterface::list()列出所有网卡
NetworkInterface::forName("eth0")按名取
NetworkInterface::forIndex(2)按索引取
name()网卡名
address()主 IP
subnetMask()子网掩码
broadcastAddress()广播地址
macAddress()MAC
mtu()MTU
type()NI_TYPE_ETHERNET
isUp()是否启用
isLoopback()是否回环

8.3 完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// demo31_netif.cpp
#include <Poco/Net/NetworkInterface.h>
#include <iostream>

int main() {
auto interfaces = Poco::Net::NetworkInterface::list();

std::cout << "=== 系统中所有网卡 ===" << std::endl;
for (const auto& ni : interfaces) {
std::cout << "--------------------------------------" << std::endl;
std::cout << "Name: " << ni.name() << std::endl;
std::cout << "Index: " << ni.index() << std::endl;
std::cout << "IP: " << ni.address().toString() << std::endl;
std::cout << "Mask: " << ni.subnetMask().toString() << std::endl;
std::cout << "Broadcast:" << ni.broadcastAddress().toString() << std::endl;
std::cout << "MAC: " << ni.macAddress() << std::endl;
std::cout << "MTU: " << ni.mtu() << std::endl;
std::cout << "Up? " << (ni.isUp() ? "yes" : "no") << std::endl;
std::cout << "Loopback? " << (ni.isLoopback() ? "yes" : "no") << std::endl;
std::cout << "Type: " << (ni.type() == Poco::Net::NetworkInterface::NI_TYPE_ETHERNET ? "Ethernet" : "Other") << std::endl;
}

// 按名查
try {
auto eth0 = Poco::Net::NetworkInterface::forName("eth0");
std::cout << "eth0 IP: " << eth0.address().toString() << std::endl;
} catch (const Poco::NotFoundException&) {
std::cerr << "无 eth0" << std::endl;
}
return 0;
}

8.4 平台实现

平台底层备注
Linux / QNXgetifaddrs(3)完整
Android同上注意权限
macOS同上完整
WindowsGetAdaptersInfo / GetAdaptersAddresses需 Winsock 初始化
VxWorksifConfigShow + ifAddrGet部分接口名不同

8.5 嵌入式实战:按 IP 段自动选网卡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// demo32_pick_iface.cpp
// 车机 4 网卡自动选
#include <Poco/Net/NetworkInterface.h>
#include <Poco/Net/IPAddress.h>
#include <iostream>

Poco::Net::NetworkInterface pickBySubnet(const std::string& subnet) {
Poco::Net::IPAddress target = Poco::Net::IPAddress::parse(subnet);
Poco::Net::IPAddress targetNet = target & Poco::Net::IPAddress("255.255.255.0");

for (const auto& ni : Poco::Net::NetworkInterface::list()) {
if (ni.isLoopback() || !ni.isUp()) continue;
Poco::Net::IPAddress ipNet = ni.address() & ni.subnetMask();
if (ipNet == targetNet) {
std::cout << " 命中: " << ni.name()
<< " (" << ni.address().toString() << ")" << std::endl;
return ni;
}
}
throw Poco::NotFoundException("no matching interface");
}

int main() {
try {
auto wifi = pickBySubnet("192.168.43.0/24"); // Wi-Fi AP
auto eth = pickBySubnet("10.0.0.0/24"); // 以太网调试
} catch (const Poco::Exception& ex) {
std::cerr << ex.displayText() << std::endl;
}
return 0;
}

8.6 IPv6 多地址

1
2
3
4
5
// demo33_ipv6.cpp
Poco::Net::NetworkInterface ni = Poco::Net::NetworkInterface::forName("eth0");
for (const auto& addr : ni.addressList()) {
std::cout << " " << addr.get<IPAddress>().toString() << std::endl;
}

九、多进程通信综合实战:车机 Agent IPC

9.1 场景设定

车机有 3 个进程:

graph TB
    subgraph "车机 IPC 系统"
        DAEMON["🛡️ vehicle_daemon<br/>CAN 总线<br/>100Hz 写共享内存"]:::daemon
        SHM["📦 /vehicle_shm<br/>64 KB<br/>车速/转速/水温/...<br/>+ 1 KB 命令队列"]:::shm
        EVT["🚥 /data_ready<br/>NamedEvent"]:::event
        MTX["🔒 /shm_mutex<br/>NamedMutex"]:::mutex
        AUDIO["🔊 audio_app<br/>订阅共享内存<br/>+ NamedEvent 唤醒"]:::audio
        NAV["🗺️ nav_app<br/>订阅共享内存<br/>+ 4G 远程控制 (Socket)"]:::nav
        PHONE["📱 远程 App<br/>4G/5G"]:::remote
    end

    DAEMON -- "1. 写" --> SHM
    DAEMON -- "2. 锁" --> MTX
    DAEMON -- "3. set" --> EVT
    EVT -- "4. 唤醒" --> AUDIO
    EVT -- "4. 唤醒" --> NAV
    AUDIO -- "5. 读" --> SHM
    NAV -- "5. 读" --> SHM
    PHONE -- "TCP 9000" --> NAV

    style DAEMON fill:#FFB3C6,stroke:#F48FB1,color:#333
    style SHM fill:#FFF9C4,stroke:#F9A825,color:#333
    style EVT fill:#B5EAD7,stroke:#80CBC4,color:#333
    style MTX fill:#E8D5F5,stroke:#CE93D8,color:#333
    style AUDIO fill:#FFDAB9,stroke:#FFAB76,color:#333
    style NAV fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style PHONE fill:#C7CEEA,stroke:#9FA8DA,color:#333

3 个进程 + 3 种 IPC 方式 联合使用

  1. vehicle_daemonSharedMemory + NamedEvent → 广播车速
  2. nav_app ↔ 远程 App → Socket
  3. 任意进程间命令队列 → SharedMemory + NamedMutex

9.2 共享内存数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// ipc_protocol.h
#pragma once
#include <cstdint>

namespace vehicle {

constexpr const char* SHM_NAME = "/vehicle_shm";
constexpr const char* EVENT_NAME = "/vehicle_event";
constexpr const char* MUTEX_NAME = "/vehicle_mutex";
constexpr std::size_t SHM_SIZE = 64 * 1024; // 64 KB
constexpr int MAX_COMMANDS = 64;

enum class CommandType : uint32_t {
NONE = 0,
PLAY_AUDIO = 1,
NAVIGATE_TO = 2,
UPDATE_FIRMWARE = 3,
REBOOT = 4
};

struct VehicleState {
uint32_t magic; // 0xCAFE2026
uint32_t version; // 协议版本
float speed_kmh;
float rpm;
float coolant_c;
int gear; // 0=P, 1=R, 2=N, 3=D
int door_lock; // bitmask
int64_t timestamp_ms;
char reserved[64];
};

struct Command {
CommandType type;
uint32_t id;
char payload[256];
};

struct SharedLayout {
VehicleState state;
char pad1[128]; // 避免 false sharing
uint32_t head;
uint32_t tail;
char pad2[120];
Command queue[MAX_COMMANDS];
};

static_assert(sizeof(SharedLayout) <= SHM_SIZE, "SHM too small");

} // namespace vehicle

9.3 完整实现:vehicle_daemon.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// vehicle_daemon.cpp
// CAN 总线模拟,100Hz 写共享内存,发事件
#include <Poco/NamedSharedMemory.h>
#include <Poco/NamedEvent.h>
#include <Poco/NamedMutex.h>
#include <Poco/Thread.h>
#include <Poco/Environment.h>
#include <Poco/Exception.h>
#include <Poco/DateTime.h>
#include <iostream>
#include <csignal>
#include <atomic>
#include "ipc_protocol.h"

using namespace vehicle;

std::atomic<bool> g_running{true};
void onSignal(int) { g_running = false; }

int main() {
std::signal(SIGINT, onSignal);
std::signal(SIGTERM, onSignal);

try {
// 1. 初始化所有 IPC 资源
Poco::NamedSharedMemory shm(SHM_NAME, SHM_SIZE,
Poco::SharedMemory::AM_WRITE);
Poco::NamedEvent evt(EVENT_NAME, false);
Poco::NamedMutex mtx(MUTEX_NAME);

auto* layout = reinterpret_cast<SharedLayout*>(shm.begin());
std::memset(layout, 0, SHM_SIZE);
layout->state.magic = 0xCAFE2026;
layout->state.version = 1;
layout->head = layout->tail = 0;

std::cout << "[daemon] 启动,PID=" << Poco::Process::id() << std::endl;

// 2. 主循环:每 10ms 模拟一次 CAN 总线数据
int tick = 0;
while (g_running) {
{
Poco::NamedMutex::ScopedLock lock(mtx);
// 模拟车速变化
layout->state.speed_kmh = 50.0f + 30.0f * std::sin(tick * 0.05f);
layout->state.rpm = 1500.0f + 500.0f * std::sin(tick * 0.1f);
layout->state.coolant_c = 85.0f + 5.0f * std::sin(tick * 0.02f);
layout->state.gear = (tick / 100) % 4;
layout->state.timestamp_ms =
Poco::DateTime().timestamp().epochMicroseconds() / 1000;
}

// 3. 发事件唤醒订阅者
evt.set();

// 4. 处理命令队列(如果有)
Poco::NamedMutex::ScopedLock lock(mtx);
while (layout->head != layout->tail) {
const Command& cmd = layout->queue[layout->head % MAX_COMMANDS];
std::cout << "[daemon] 处理命令: type=" << (int)cmd.type
<< " id=" << cmd.id
<< " payload=" << cmd.payload << std::endl;
layout->head++;
}

Poco::Thread::sleep(10);
tick++;
}

std::cout << "[daemon] 退出" << std::endl;
} catch (const Poco::Exception& ex) {
std::cerr << "[daemon] 错误: " << ex.displayText() << std::endl;
return 1;
}
return 0;
}

9.4 完整实现:audio_app.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// audio_app.cpp
// 订阅车速:NamedEvent 唤醒 + SharedMemory 读
#include <Poco/NamedSharedMemory.h>
#include <Poco/NamedEvent.h>
#include <Poco/Thread.h>
#include <Poco/Exception.h>
#include <iostream>
#include <atomic>
#include <csignal>
#include "ipc_protocol.h"

using namespace vehicle;

std::atomic<bool> g_running{true};
void onSignal(int) { g_running = false; }

int main() {
std::signal(SIGINT, onSignal);
std::signal(SIGTERM, onSignal);

try {
Poco::NamedSharedMemory shm(SHM_NAME, SHM_SIZE,
Poco::SharedMemory::AM_READ);
Poco::NamedEvent evt(EVENT_NAME, false);

const auto* layout = reinterpret_cast<const SharedLayout*>(shm.begin());

std::cout << "[audio] 等待数据..." << std::endl;
while (g_running) {
if (evt.tryWait(500)) {
std::cout << "[audio] 收到事件: speed="
<< layout->state.speed_kmh << " km/h" << std::endl;
// 实际项目:调音频 API 调整音量
} else {
std::cout << "[audio] 500ms 无数据,可能 daemon 挂了" << std::endl;
}
}
} catch (const Poco::Exception& ex) {
std::cerr << "[audio] 错误: " << ex.displayText() << std::endl;
return 1;
}
return 0;
}

9.5 完整实现:nav_app.cpp(带远程 Socket)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// nav_app.cpp
// 订阅车速 + 接受远程 4G 控制
#include <Poco/Net/ServerSocket.h>
#include <Poco/Net/StreamSocket.h>
#include <Poco/Net/SocketAddress.h>
#include <Poco/NamedSharedMemory.h>
#include <Poco/NamedEvent.h>
#include <Poco/NamedMutex.h>
#include <Poco/Thread.h>
#include <Poco/Exception.h>
#include <iostream>
#include <atomic>
#include <csignal>
#include <thread>
#include "ipc_protocol.h"

using namespace vehicle;

std::atomic<bool> g_running{true};
void onSignal(int) { g_running = false; }

void eventListener() {
try {
Poco::NamedSharedMemory shm(SHM_NAME, SHM_SIZE,
Poco::SharedMemory::AM_READ);
Poco::NamedEvent evt(EVENT_NAME, false);
const auto* layout = reinterpret_cast<const SharedLayout*>(shm.begin());
while (g_running) {
if (evt.tryWait(500)) {
std::cout << "[nav] 车速=" << layout->state.speed_kmh
<< " rpm=" << layout->state.rpm << std::endl;
}
}
} catch (const Poco::Exception& ex) {
std::cerr << "[nav] 事件监听错误: " << ex.displayText() << std::endl;
}
}

void networkServer() {
try {
Poco::Net::ServerSocket server(Poco::Net::SocketAddress("0.0.0.0", 9000));
std::cout << "[nav] TCP 监听 0.0.0.0:9000" << std::endl;

while (g_running) {
Poco::Net::StreamSocket client = server.acceptConnection();
std::cout << "[nav] 远程连接: " << client.peerAddress().toString() << std::endl;

// 投递命令到队列
Poco::NamedSharedMemory shm(SHM_NAME, SHM_SIZE,
Poco::SharedMemory::AM_WRITE);
Poco::NamedMutex mtx(MUTEX_NAME);
auto* layout = reinterpret_cast<SharedLayout*>(shm.begin());

char buf[512] = {0};
int n = client.receiveBytes(buf, sizeof(buf) - 1);
if (n > 0) {
Poco::NamedMutex::ScopedLock lock(mtx);
Command& cmd = layout->queue[layout->tail % MAX_COMMANDS];
cmd.type = CommandType::NAVIGATE_TO;
cmd.id = layout->tail;
std::strncpy(cmd.payload, buf, sizeof(cmd.payload) - 1);
layout->tail++;

std::string reply = "OK: 收到导航指令 " + std::string(buf, n);
client.sendBytes(reply.data(), reply.size());
}
client.close();
}
} catch (const Poco::Exception& ex) {
std::cerr << "[nav] 网络错误: " << ex.displayText() << std::endl;
}
}

int main() {
std::signal(SIGINT, onSignal);
std::signal(SIGTERM, onSignal);

// 启动 2 个线程:1 个事件监听、1 个网络服务
std::thread t1(eventListener);
std::thread t2(networkServer);

t1.join();
t2.join();
return 0;
}

9.6 编译脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
# build.sh
set -e

POCO_INC=/usr/local/include
POCO_LIB=/usr/local/lib
COMMON_FLAGS="-std=c++17 -O2 -I${POCO_INC} -I."

g++ ${COMMON_FLAGS} vehicle_daemon.cpp -o vehicle_daemon \
-L${POCO_LIB} -lPocoFoundation -lPocoNet -lpthread

g++ ${COMMON_FLAGS} audio_app.cpp -o audio_app \
-L${POCO_LIB} -lPocoFoundation -lPocoNet -lpthread

g++ ${COMMON_FLAGS} nav_app.cpp -o nav_app \
-L${POCO_LIB} -lPocoFoundation -lPocoNet -lpthread

echo "✅ 编译完成"

9.7 启动顺序

1
2
3
4
5
6
7
8
9
10
11
# 终端 1:先启动 daemon(它会创建 SHM/Event/Mutex)
./vehicle_daemon

# 终端 2:启动 audio
./audio_app

# 终端 3:启动 nav
./nav_app

# 终端 4:模拟远程
echo "北京天安门" | nc 127.0.0.1 9000

9.8 时序图:一次完整的远程控制

sequenceDiagram
    participant Phone as 📱 远程App
    participant Nav as 🗺️ nav_app
    participant Daemon as 🛡️ vehicle_daemon
    participant Audio as 🔊 audio_app
    participant SHM as 📦 共享内存
    participant EVT as 🚥 NamedEvent
    participant MTX as 🔒 NamedMutex

    Phone->>Nav: TCP 连接 9000
    Phone->>Nav: "导航到天安门"
    Nav->>MTX: lock()
    Nav->>SHM: 写 Command 队列
    Nav->>MTX: unlock()
    Nav-->>Phone: "OK 收到"
    Daemon->>MTX: lock()
    Daemon->>SHM: 读 Command, 弹出
    Daemon->>MTX: unlock()
    Daemon->>EVT: set() (广播数据)
    EVT-->>Audio: wait 返回
    EVT-->>Nav: wait 返回
    Audio->>SHM: 读 VehicleState
    Nav->>SHM: 读 VehicleState
    Audio-->>Phone: 播放"开始导航"

十、嵌入式特殊场景

10.1 QNX Neutrino RTOS

QNX 是 POSIX 兼容的实时 OS——POCO 在 QNX 上大部分代码可直接编译

维度QNX 7+LinuxPOCO 兼容
进程模型微内核 + 进程内核 + 进程
共享内存shm_open (POSIX)同左
消息队列mq_open (POSIX)同左⚠️ POCO 无 Native wrapper
进程优先级1-255 实时优先级-20 ~ 19⚠️ 需平台特定 API
网络QNET / TCP/IPTCP/IP
Binder

QNX 实时优先级设置(POCO 未封装,需平台 API)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// demo34_qnx_priority.cpp
// QNX 上把进程设为实时优先级 30
#ifdef __QNX__
#include <sys/neutrino.h>
#include <sched.h>

void setRealtimePriority(int prio) {
// 1. 告诉内核:本进程要跑实时
struct _thread_attr attr;
pthread_getattr_np(pthread_self(), &attr);
attr.__param.__sched_priority = prio;
attr.__flags |= PTHREAD_EXPLICIT_SCHED;
pthread_setattr_np(pthread_self(), &attr);

// 2. 或用 SetRtid
int tid = gettid();
SetRtid(tid, prio);
}
#else
void setRealtimePriority(int) {
std::cerr << "非 QNX,跳过" << std::endl;
}
#endif

int main() {
setRealtimePriority(30);
std::cout << "已设实时优先级" << std::endl;
return 0;
}

QNX 进程间消息队列(POCO 没封装,用 POSIX API):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// demo35_qnx_mq.cpp
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <iostream>
#include <cstring>
#include <unistd.h>

struct Msg {
long type;
char text[64];
};

int main(int argc, char** argv) {
const char* MQ_NAME = "/my_mq";

if (argc > 1 && std::string(argv[1]) == "sender") {
mq_unlink(MQ_NAME);
mq_attr attr{};
attr.mq_maxmsg = 10;
attr.mq_msgsize = sizeof(Msg);
mq_t mq = mq_open(MQ_NAME, O_CREAT | O_WRONLY, 0644, &attr);

Msg m{1, "hello QNX"};
mq_send(mq, (const char*)&m, sizeof(m), 0);
mq_close(mq);
} else {
mq_t mq = mq_open(MQ_NAME, O_RDONLY);
Msg m;
ssize_t n = mq_receive(mq, (char*)&m, sizeof(m), nullptr);
std::cout << "收到 type=" << m.type << " " << m.text << std::endl;
mq_close(mq);
mq_unlink(MQ_NAME);
}
return 0;
}

10.2 Android Binder

Android 用 Binder 进程间通信——POCO 完全不支持。替代方案:

方案描述适用
JNI 调 Java Binder性能差但能用简单场景
Unix Domain SocketPOCO SocketAddress("localabstract:/...")车机 Android
共享内存POCO 完整支持同上
gRPC over TCP跨语言、跨进程微服务

Android 走 Unix Domain Socket(POCO 实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// demo36_android_socket.cpp
#include <Poco/Net/StreamSocket.h>
#include <Poco/Net/SocketAddress.h>
#include <Poco/Exception.h>
#include <iostream>

// Android 的"local abstract" namespace:进程名 @ 套接字名
int main() {
try {
// 连接 Android system_server 的 activity 服务
Poco::Net::SocketAddress sa("localabstract:activity");
Poco::Net::StreamSocket ss(sa);
std::cout << "已连 Android activity 服务" << std::endl;
// ... 后续走 binder 协议
} catch (const Poco::Exception& ex) {
std::cerr << ex.displayText() << std::endl;
}
return 0;
}

10.3 实时进程优先级(Linux SCHED_FIFO)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// demo37_realtime.cpp
// Linux 把进程设为实时 SCHED_FIFO 优先级 50
#include <sched.h>
#include <pthread.h>
#include <iostream>
#include <cerrno>
#include <cstring>

void setRealtime(int prio) {
sched_param param{};
param.sched_priority = prio;
if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &param) != 0) {
std::cerr << "setschedparam 失败: " << std::strerror(errno)
<< "(需要 root 或 CAP_SYS_NICE)" << std::endl;
} else {
std::cout << "已设 SCHED_FIFO 优先级 " << prio << std::endl;
}
}

int main() {
setRealtime(50);
return 0;
}

注意:实时优先级没有内存保护——一个死循环会独占 CPU 锁死整个系统。车机项目慎用。

10.4 VxWorks

维度VxWorksPOCO 兼容
进程模型RTP(Real-Time Process)
共享内存mmap
消息队列msgQCreate❌ 需用平台 API
SocketBSD socket
文件系统dosFs / hrfs⚠️ 部分
NamedSharedMemory❌ 无文件系统❌ 用 mmap + 自定义

10.5 RT-Thread

POCO 在 RT-Thread 上不官方支持,但 RT-Thread 有 POSIX 层——简单应用可跑。生产环境建议用 RT-Thread 原生 IPC


十一、避坑指南

11.1 共享内存同步的 8 大陷阱

#陷阱后果解决
1没有同步读到撕裂数据NamedMutex / 原子变量
2忘记 unlockMutex 死锁ScopedLock
3magic 校验缺失拿到野指针头部加 4 字节 magic
4版本号缺失协议升级不兼容头部加 version 字段
5结构体对齐32/64 位进程不兼容强制 1 字节对齐?或同架构
6字节序不一致大小端混用Poco::ByteOrder
7大小超过物理内存创建失败监控共享内存使用量
8进程崩溃不清理残留 SHMLinux 用 shm_unlink,QNX 用 SEM_REMOVEFILE_ON_CLOSE

11.2 Socket 阻塞 / 非阻塞的 7 大陷阱

#陷阱后果解决
1阻塞模式忘记超时recv() 永久阻塞setReceiveTimeout(t)
2非阻塞模式忘记 errno EAGAIN死循环EAGAIN/EWOULDBLOCKPollSet.wait
3TCP 半关闭客户端无法复用连接shutdown(how)
4TIME_WAIT重启 server 失败setReuseAddress(true)
5Nagle 算法小包延迟 200mssetNoDelay(true)
6SIGPIPEwrite 触发崩溃signal(SIGPIPE, SIG_IGN)
7缓冲区溢出读超 length严格检查返回值

11.3 DNS 超时的 3 大陷阱

#陷阱解决
1getaddrinfo 默认不超时Poco::Net::DNS::resolve(host, timeout)
2DNS 服务器不可达兜底用 /etc/hosts 或 mDNS
3DNS 缓存中毒用 DoH(DNS over HTTPS)

11.4 跨平台一致性

维度LinuxQNXWindows注意点
路径分隔符//\Poco::Path
换行符\n\n\r\nPoco::LineEnding
字节序LELELE一致
字符编码UTF-8UTF-8UTF-16Poco::UnicodeConverter
进程优先级-20~191~2550~5平台特定 API
文件大小写区分区分不区分避免 Makefile vs makefile
文件锁fcntlLockFileEx行为略不同

11.5 资源泄漏:5 大死亡陷阱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 反例 1:忘记 wait 子进程
Poco::ProcessHandle ph = Poco::Process::launch(...);
return 0; // 僵尸进程!

// 正例:
Poco::ProcessHandle ph = Poco::Process::launch(...);
ph.wait(); // 必 wait

// 反例 2:Pipe 没关
Poco::Pipe p;
Poco::ProcessHandle ph = Poco::Process::launch("cat", {}, &p, nullptr, nullptr);
p.close(); // 必 close
ph.wait();

// 反例 3:Socket 没关
Poco::Net::StreamSocket ss(sa);
// ... 异常分支没 close
// 正例:用 RAII(SS 出作用域自动 close)

// 反例 4:SharedMemory 没 unlink
Poco::NamedSharedMemory nsm("/x", 1024, AM_WRITE);
// 程序结束前要 unlink,否则下次启动残留
// Linux: shm_unlink; QNX: 同

// 反例 5:Mutex 没 unlock 抛出异常
mut.lock();
doStuff(); // 抛异常
mut.unlock(); // 永远跑不到
// 正例:用 ScopedLock

11.6 性能调优 7 招

#优化收益
1共享内存代替 Pipe/Socket1000x
2setNoDelay(true)TCP 小包响应 -200ms
3setSendBufferSize(64K)吞吐 +30%
4setReuseAddress(true)避免重启失败
5PollSet 替代多线程内存 -50%
6批量写共享内存syscall -80%
7std::vector<char> 代替 std::string 传大量数据拷贝 -100%

11.7 调试技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 打印当前所有 IPC 资源
// Linux: ipcs -m / ipcs -s / lsof | grep shm
// QNX: pidin -P <pid> mem
// Windows: Get-Process | Select-Object -First 1 | Format-List

// 2. strace 跟踪
// strace -e trace=mmap,shm_open,munmap,shm_unlink ./your_app

// 3. POCO 自己的堆栈
try { ... }
catch (const Poco::Exception& ex) {
std::cerr << ex.displayText() << std::endl;
// 包含:消息 + 嵌套 cause + 堆栈
}

// 4. 启用 POCO 调试日志
// 在 cmake 时 -DPOCO_ENABLE_DEBUG=ON

11.8 与 boost::interprocess 的取舍

维度boost::interprocessPoco::SharedMemory
跨平台
共享内存✅ 丰富✅ 基础
消息队列
文件锁❌(用 NamedMutex)
池分配器⚠️ MemoryPool(单进程)
编译依赖boost(巨大)POCO(轻量)
学习曲线平缓
嵌入式适用⚠️ 体积大

我的建议:嵌入式首选 POCO(轻量、API 简单);服务端 / 大型项目可考虑 boost::interprocess


十二、与同类方案的横向对比

12.1 进程管理方案对比

维度std::systemposix_spawnPoco::Processboost::process
跨平台
捕获 stdout⚠️ 手动✅ 内置
拿 PID
错误信息⚠️ errno✅ Exception
嵌入式⚠️ 需 shell⚠️
C++ 标准✅ C++11❌ POSIX✅ POCO❌ Boost
代码量1 行30 行5 行8 行
推荐场景临时脚本性能敏感POCO 项目复杂管道

12.2 共享内存方案对比

维度mmap 原生boost::interprocessPoco::SharedMemorySystem V shm
跨平台
命名✅(ftok)
偏移访问✅ 灵活⚠️ 需整段✅ 灵活
同步原语❌ 需自带✅ 内置✅ NamedEvent/Mutex⚠️ 需自带
调试⚠️⚠️✅ Exception
现代 C++
推荐极致性能复杂场景POCO 项目兼容老代码

12.3 Socket 库对比

维度原生 BSD/WinsockPoco::Net::Socketboost::asiolibuv
跨平台⚠️ API 略不同
错误处理❌ errno✅ Exception✅ ErrorCode✅ ErrorCode
异步❌ 需 select⚠️ PollSet✅ Proactor✅ Callback
协议层❌ 裸 socket⚠️ HTTP/FTP⚠️ Beast
体积0
学习曲线
推荐学习用POCO 项目复杂网络Node 风格

12.4 嵌入式 IPC 选型决策树

graph TD
    START["🤔 要通信的两端<br/>是什么关系?"]:::start
    SAME["同主机?"]:::q
    CROSS["跨主机?"]:::q
    PARENT["父子?"]:::q
    ANY["任意进程?"]:::q

    START --> SAME
    SAME -- "✅ 是" --> ANY
    SAME -- "❌ 否" --> CROSS
    CROSS -- "走 TCP/UDP" --> SOCK["📡 Poco::Net::Socket"]
    ANY -- "✅ 父子" --> PARENT
    ANY -- "❌ 任意" --> SHM_R["📦 共享内存 + 同步"]
    PARENT -- "✅ 是" --> PIPE["🛤️ Poco::Pipe"]
    PARENT -- "❌ 否" --> SHM_R

    PIPE --> USE1["配合 Process"]
    SHM_R --> USE2["SharedMemory<br/>+ NamedEvent<br/>+ NamedMutex"]
    SOCK --> USE3["Socket + SocketAddress<br/>+ DNS"]

    style START fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style SAME fill:#FFF9C4,stroke:#F9A825,color:#333
    style CROSS fill:#FFF9C4,stroke:#F9A825,color:#333
    style PARENT fill:#FFF9C4,stroke:#F9A825,color:#333
    style ANY fill:#FFF9C4,stroke:#F9A825,color:#333
    style SOCK fill:#B5EAD7,stroke:#80CBC4,color:#333
    style PIPE fill:#B5EAD7,stroke:#80CBC4,color:#333
    style SHM_R fill:#B5EAD7,stroke:#80CBC4,color:#333
    style USE1 fill:#E8D5F5,stroke:#CE93D8,color:#333
    style USE2 fill:#E8D5F5,stroke:#CE93D8,color:#333
    style USE3 fill:#E8D5F5,stroke:#CE93D8,color:#333

12.5 综合建议

场景推荐方案POCO 组件
车机 IVI 进程间共享内存 + 命名同步SharedMemory + NamedEvent + NamedMutex
手机 App 远程控制TCP 长连接Socket + ServerSocket + SocketAddress
进程内日志聚合多线程 + 共享内存Thread + SharedMemory
命令行工具父进程捕获 stdoutProcess + Pipe
服务发现UDP 组播DatagramSocket + MulticastSocket
OTA 升级HTTP/HTTPSHTTPClient + HTTPSClient
本地 RPC共享内存 + 命令队列同车机

十三、回顾与下一步

13.1 13 个核心类的全景表

头文件模块一句话
Poco::ProcessPoco/Process.hFoundation跨平台 fork/exec
Poco::ProcessHandlePoco/Process.hFoundation子进程 RAII 句柄
Poco::PipePoco/Pipe.hFoundation匿名管道
Poco::PipeStreamPoco/PipeStream.hFoundation管道流式 IO
Poco::SharedMemoryPoco/SharedMemory.hFoundation父子共享内存
Poco::NamedSharedMemoryPoco/NamedSharedMemory.hFoundation命名共享内存
Poco::NamedEventPoco/NamedEvent.hFoundation跨进程事件
Poco::NamedMutexPoco/NamedMutex.hFoundation跨进程互斥
Poco::NamedSemaphorePoco/NamedSemaphore.hFoundation跨进程信号量
Poco::EnvironmentPoco/Environment.hFoundation环境变量
Poco::Net::SocketPoco/Net/Socket.hNetBSD socket 封装
Poco::Net::ServerSocketPoco/Net/ServerSocket.hNetTCP 服务端
Poco::Net::ClientSocketPoco/Net/ClientSocket.hNetTCP 客户端
Poco::Net::StreamSocketPoco/Net/StreamSocket.hNetTCP 流式
Poco::Net::DatagramSocketPoco/Net/DatagramSocket.hNetUDP
Poco::Net::SocketAddressPoco/Net/SocketAddress.hNet统一 IPv4/IPv6
Poco::Net::IPAddressPoco/Net/IPAddress.hNet纯 IP 地址
Poco::Net::DNSPoco/Net/DNS.hNet域名解析
Poco::Net::HostEntryPoco/Net/HostEntry.hNetDNS 结果
Poco::Net::NetworkInterfacePoco/Net/NetworkInterface.hNet网卡枚举
Poco::Net::PollSetPoco/Net/PollSet.hNet多路复用

13.2 11 大 API 速记表

启动关闭
Processlaunchstdout 通过 Pipestdin 通过 Pipewait / kill
Pipe构造readwriteclose
SharedMemory构造begin()begin()RAII
NamedEvent构造wait / tryWaitsetRAII
NamedMutex构造lock / tryLocklockunlock
ServerSocket构造+listenacceptConnectionclose
ClientSocket构造receiveBytessendBytesclose
DatagramSocket构造receiveFromsendToclose
SocketAddress构造host/port/familyRAII
DNSresolvehostByName静态方法
NetworkInterfacelistforName静态方法

13.3 选型 5 条黄金法则

#法则解读
1同主机高频数据用共享内存Pipe/Socket 在 1kHz 下不可用
2跨主机必走 Socket共享内存不能跨主机
3同步原语不可省没有同步的共享内存 = 炸弹
4超时永远要设阻塞调用 = 系统崩
5平台差异用 POCO 抹平6 大平台 = 6 套代码 → 1 套代码

13.4 下一步

推荐描述
第 8 篇:HTTP 客户端/服务端基于 Net::HTTPServer 实现车机 OTA 升级
第 9 篇:多线程ThreadPool / Task / ActiveMethod
第 10 篇:TCPServer 框架Net::TCPServer / Connection / Dispatcher
实战项目 Craton下一阶段:从 0 写一个车载 IPC 中间件(基于 POCO 改造)

13.5 资源推荐

资源链接
POCO 官方文档https://pocoproject.org/docs/
POCO GitHubhttps://github.com/pocoproject/poco
POCO Net 教程https://pocoproject.org/slides/Net.pdf
POCO 嵌入式移植https://pocoproject.org/docs/99150-Platforms.html
QNX POSIX 指南https://www.qnx.com/developers/docs/
Linux mmap(2)man 2 mmap
APUE 第 15 章《UNIX 环境高级编程》进程间通信
UNP 第 4 章《UNIX 网络编程》socket 基础

结尾金句:嵌入式 IPC 的难度,从来不在”通信”两个字本身——而在于跨平台、跨进程、跨主机的协议一致性。POCO 给了你同一套 C++ API 在 6 大平台上写出工业级 IPC 的能力——剩下的,是你对”同步、超时、错误”三件事的敬畏。共享内存 + 命名同步 + Socket 这三件套搭起来的车机系统,能从样机跑到量产;少一件,就只能跑 demo。


本文为 POCO 实战系列第 7 篇。所有代码基于 POCO 1.15+ 编写、Linux 5.10+ 验证。QNX / Windows 路径已注释说明。完整可运行代码见作者 GitHub 仓库。