当前位置: 首页 > news >正文

c/c++ UNIX 域Socket和共享内存实现本机通信

  • 一、UNIX 域Socket 特点

  1. 只能在本机使用
    • 不能跨机器通信,只能在 Linux/Unix 系统同一台主机上。
  2. 高性能
    • 因为数据不走网卡,也不经过网络协议栈,速度比 TCP/UDP 快。
  3. 通过文件表示
    • UNIX Socket 会对应一个 文件路径,比如你的示例里:

      c
      复制编辑
      #define SOCK_PATH "/data/.../ads_demo.sock"
    • 进程通过这个文件“找到对方”,然后就能通信。
  4. 支持面向连接和无连接
    • 面向连接(SOCK_STREAM)类似 TCP
    • 无连接(SOCK_DGRAM)类似 UDP
  • 二、 项目框架

  • socket_demo/
    ├── include/
    │   ├── common.h        # 公共头文件
    │   ├── client.h
    │   └── server.h
    ├── src/
    │   ├── client.cpp
    │   ├── server.cpp
    │   └── common.cpp
    ├── CMakeLists.txt
    └── README.md

Server 做的事情

  1. 初始化信号处理
    • 捕获 SIGINT 和 SIGTERM,用于优雅退出。
  2. 清理残留 socket 文件
    • 防止上次异常退出导致 UNIX socket 文件还存在。
  3. 创建共享内存
    • shm_open 创建/打开共享内存
    • ftruncate 设置大小
    • mmap 映射到进程地址空间
  4. 建立 UNIX 域 Socket 并监听
    • socket(AF_UNIX)
    • bind 绑定路径
    • listen 等待 client 连接
  5. 等待客户端连接
    • accept 阻塞直到 client 连接
  6. 主循环处理消息
    • 收到控制消息(PUSH / EXIT / 其他)
    • PUSH
      • server 从共享内存读取数据
      • 处理后写回共享内存
      • 通过 socket 发送 PULL 控制消息通知 client
    • EXIT:退出循环
  7. 清理资源
    • 关闭 socket、取消映射共享内存、删除共享内存和 socket 文件

Client 做的事情

  1. 连接 Server 的 UNIX socket
  2. 映射共享内存(同名)
  3. 写数据到共享内存
  4. 通过 socket 发送 PUSH 消息
  5. 等待 server 响应 PULL 消息
  6. 从共享内存读取 server 的返回数据
  7. 循环或结束
  8. 清理资源

server.c

#include <stdio.h>      // printf、perror 等标准 I/O
#include <stdlib.h>     // exit、EXIT_xxx、malloc/free 等
#include <string.h>     // memset、strncpy、strncmp 等
#include <stdbool.h>    // 引入 bool / true / false
#include <signal.h>     // signal、SIGINT、SIGTERM
#include <unistd.h>     // close、unlink、read/write、ftruncate 等
#include <fcntl.h>      // O_CREAT、O_RDWR 等 open/shm_open 标志位
#include <sys/mman.h>   // shm_open、mmap、munmap、shm_unlink
#include <sys/socket.h> // socket、bind、listen、accept、send、recv
#include <sys/un.h>     // UNIX 域套接字 sockaddr_un、AF_UNIX
#include <sys/stat.h>   // 权限位(0666 等),有时配合 umask 使用
#include "common_net.h" // 你自定义的公共协议: SHM_NAME/SHM_SIZE/SOCK_PATH/CTRL_MSG_xxx/shm_packet_t/CTRL_MSG_MAX 等static int shm_fd = -1;         // 共享内存对象的文件描述符
static void *shm_addr = NULL;   // 映射后的地址
static int listen_fd = -1;      // 监听用的 UNIX 域 socket fd
static int conn_fd = -1;        // 已接受连接的客户端 fd
static volatile sig_atomic_t g_stop = 0; 
// 信号安全的停止标志volatile sig_atomic_t:保证在信号处理函数里对它的写入是原子的、类型安全的(避免数据竞争)。
// 初始化为无效值或空指针,便于清理阶段判断是否需要释放。// 捕获 SIGINT/SIGTERM 时仅设置标志(异步信号安全):不要在信号处理函数里做耗时/非可重入的操作
static void on_sigint(int sig) { g_stop = 1; }//=============================固定长度收发(TCP 风格、但你这儿用的是 UNIX 域 SOCK_STREAM)===================
// 读取Socket上固定大小的消息(简化处理:期望每次刚好读到一个消息)
static bool recv_fixed(int fd, void *buf, size_t len) {size_t got = 0;while (got < len) {   //为什么循环?:流式套接字不保证一次 send/recv 就把指定长度全部传完,必须循环直至满足长度。ssize_t r = recv(fd, (char*)buf + got, len - got, 0);if (r <= 0) return false;    // 0=对端关闭;<0=出错got += (size_t)r;    // 处理“短读”}return true;
}// 发送固定大小消息
static bool send_fixed(int fd, const void *buf, size_t len) {size_t sent = 0;while (sent < len) {ssize_t r = send(fd, (const char*)buf + sent, len - sent, 0);if (r <= 0) return false;   // 0 很少见;<0 出错sent += (size_t)r;   // 处理“短写”}return true;
}//=====================================================================================================
int main(void) {// ====================安装信号处理器,支持 Ctrl+C 或服务管理器优雅退出。=======================signal(SIGINT, on_sigint);signal(SIGTERM, on_sigint);// 1) ==================清理上次残留的socket文件(若程序异常退出可能残留)=====================unlink(SOCK_PATH);// 2) =================创建/初始化共享内存(创建方一般是Server)=======================shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666); //O_CREAT|O_RDWR:如果没有则创建,有则打开;读写模式。if (shm_fd < 0) {perror("shm_open");return 1;}//必须:新创建的 POSIX 共享内存大小是 0,需要 ftruncate 扩展至 SHM_SIZE。 如果共享内存已存在且更小,也需扩容;若更大,不会自动缩小(可根据需求处理)。if (ftruncate(shm_fd, SHM_SIZE) == -1) {  perror("ftruncate");return 1;}// 把共享内存“映射”到当前进程虚拟地址空间,返回可读写指针。// MAP_SHARED:写入对其他 mmap 了同一对象的进程可见。// 注意:返回后你就可以把 shm_fd 关掉(映射仍有效)。代码里选择保留到最后一并关闭,也可以。shm_addr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);if (shm_addr == MAP_FAILED) {perror("mmap");return 1;}// 可选:把共享内存初始化为 0,避免读到脏内容。memset(shm_addr, 0, SHM_SIZE);printf("[SERVER] Shared memory ready: %s (%d bytes)\n", SHM_NAME, SHM_SIZE);// 3)================================ 建立UNIX域socket监听(AF_UNIX,文件形式)========================//  AF_UNIX + SOCK_STREAM:本机面向连接、字节流语义(类似 TCP,但走文件路径)。// 优点:本机通信延迟低,权限控制细,避免网络栈开销。listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);if (listen_fd < 0) {perror("socket");return 1;}//sockaddr_un 的路径字段SOCK_PATH必须以 NUL 结尾,所以 strncpy(..., n-1) 是对的。// 路径长度有限(典型 108 字节),太长会失败。struct sockaddr_un addr;memset(&addr, 0, sizeof(addr));addr.sun_family = AF_UNIX;strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);//bind 将 socket 与路径绑定;if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {perror("bind");return 1;}//listen 进入监听状态,backlog=1 表示排队上限。// 改进:可调大一点 backlog;可在 bind 前设置 umask 控制 socket 节点权限。if (listen(listen_fd, 1) < 0) {perror("listen");return 1;}printf("[SERVER] Listening on %s\n", SOCK_PATH);// 4) =========================================等待客户端连接=======================================
// 接受第一个客户端,返回已连接的 fd。
// 可选:循环 accept 支持多个客户端;目前代码是“单连接模式”。conn_fd = accept(listen_fd, NULL, NULL);if (conn_fd < 0) {perror("accept");return 1;}printf("[SERVER] Client connected.\n");// 5) ======================================主循环:控制协议与共享内存读写==================================char ctrl[CTRL_MSG_MAX]; // 是固定长度控制消息缓冲区,避免“粘包/拆包”问题。uint32_t seq = 0; //用于响应包的序号(示例用途)。while (!g_stop) {//收到一条固定长度控制消息;失败表示对端关闭或出错,直接退出循环。if (!recv_fixed(conn_fd, ctrl, sizeof(ctrl))) {  printf("[SERVER] client disconnected or recv error.\n");break;}// 关键点:这里比较的是 整个固定缓冲区 与 CTRL_MSG_PUSH(你在 client 端也用 send_fixed(..., sizeof(ctrl)) 发送同样长度,才能完全相等)。// 若 client 端只发 "PUSH" 的长度(而不是 CTRL_MSG_MAX),这儿会匹配失败(尾部有未定义字节)。// 因此**两端必须约定“控制消息固定长度”**并按同一长度收发。//接收到"PUSH"if (strncmp(ctrl, CTRL_MSG_PUSH, sizeof(ctrl)) == 0) {// 客户端写完共享内存,服务端读取shm_packet_t *pkt = (shm_packet_t*)shm_addr; //从共享内存中获取结构体size_t header = sizeof(shm_packet_t);     //data 是“柔性数组成员”(uint8_t data[];),sizeof(shm_packet_t) 只包含头部,不含数据区。if (header + pkt->data_len <= SHM_SIZE) {    //读取时做越界检查:header + data_len <= SHM_SIZE。???????????????printf("[SERVER] <- PUSH: seq=%u len=%u data=\"%.*s\"\n",   //"%.*s" 用于打印指定长度的字符串数据(即便包含 \0 也能按长度显示)pkt->seq, pkt->data_len, pkt->data_len, pkt->data);} else {printf("[SERVER] <- PUSH: invalid length!\n");}// 回写一条响应到共享内存,并通知客户端PULLconst char *resp = "ACK from SERVER";size_t resp_len = strlen(resp);size_t header2 = sizeof(shm_packet_t);if (header2 + resp_len <= SHM_SIZE) { //进行越界检查,避免写爆共享内存。shm_packet_t *out = (shm_packet_t*)shm_addr;out->seq = ++seq;out->data_len = (uint32_t)resp_len;memcpy(out->data, resp, resp_len); // //向共享内存写数据// 通知客户端char msg[CTRL_MSG_MAX] = {0}; //固定长度控制消息缓冲区,服务端写共享内存 → 通过 socket 发送固定长度 "PULL" 通知对端去读。strncpy(msg, CTRL_MSG_PULL, sizeof(msg)-1); //strncpy(..., n-1) 保证以 \0 结尾;随后 send_fixed 以 固定长度 整块发送。send_fixed(conn_fd, msg, sizeof(msg));printf("[SERVER] -> PULL: seq=%u len=%u data=\"%s\"\n", out->seq, out->data_len, resp);} else {printf("[SERVER] response too long for SHM!\n");}//接收到"EXIT"则退出主循环;} else if (strncmp(ctrl, CTRL_MSG_EXIT, sizeof(ctrl)) == 0) {printf("[SERVER] <- EXIT\n");break;//其他无效控制消息打印出来(用于调试协议不一致问题)。} else {printf("[SERVER] <- UNKNOWN CTRL: \"%.*s\"\n", (int)sizeof(ctrl), ctrl);}}// 6) ===================================清理资源=============================================//关闭连接、监听 fd,并删除 Unix 域 socket 文件,避免残留。if (conn_fd >= 0) close(conn_fd);if (listen_fd >= 0) close(listen_fd);unlink(SOCK_PATH); // 清理socket文件//解除映射、关闭共享内存对象的 fd。if (shm_addr && shm_addr != MAP_FAILED) munmap(shm_addr, SHM_SIZE);if (shm_fd >= 0) close(shm_fd);// 注意:是否立即删除共享内存?// 方案A:显式删除,避免残留shm_unlink(SHM_NAME);printf("[SERVER] Exit.\n");return 0;
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>     // shm_open, mmap
#include <sys/socket.h>   // socket, connect, send, recv
#include <sys/un.h>       // sockaddr_un
#include "common_net.h"static int shm_fd = -1;
static void *shm_addr = NULL;
static int sock_fd = -1;
static volatile sig_atomic_t g_stop = 0;static void on_sigint(int sig) { g_stop = 1; }static bool recv_fixed(int fd, void *buf, size_t len) {size_t got = 0;while (got < len) {ssize_t r = recv(fd, (char*)buf + got, len - got, 0);if (r <= 0) return false;got += (size_t)r;}return true;
}
static bool send_fixed(int fd, const void *buf, size_t len) {size_t sent = 0;while (sent < len) {ssize_t r = send(fd, (const char*)buf + sent, len - sent, 0);if (r <= 0) return false;sent += (size_t)r;}return true;
}int main(void) {signal(SIGINT, on_sigint);signal(SIGTERM, on_sigint);// 1) 打开共享内存(由Server已创建)shm_fd = shm_open(SHM_NAME, O_RDWR, 0666);if (shm_fd < 0) {perror("shm_open");fprintf(stderr, "Make sure SERVER is running (it creates the shm).\n");return 1;}shm_addr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);if (shm_addr == MAP_FAILED) {perror("mmap");return 1;}printf("[CLIENT] Shared memory opened: %s\n", SHM_NAME);// 2) 连接服务器的UNIX域socketsock_fd = socket(AF_UNIX, SOCK_STREAM, 0);if (sock_fd < 0) {perror("socket");return 1;}struct sockaddr_un addr;memset(&addr, 0, sizeof(addr));addr.sun_family = AF_UNIX;strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {perror("connect");fprintf(stderr, "Make sure SERVER is listening on %s\n", SOCK_PATH);return 1;}printf("[CLIENT] Connected to server.\n");// 3) 向共享内存写入一条消息,然后通过socket发PUSH通知const char *msg = "Hello from CLIENT";size_t msg_len = strlen(msg);shm_packet_t *pkt = (shm_packet_t*)shm_addr;size_t header = sizeof(shm_packet_t);if (header + msg_len > SHM_SIZE) {fprintf(stderr, "message too long for SHM\n");return 1;}pkt->seq = 1;pkt->data_len = (uint32_t)msg_len;memcpy(pkt->data, msg, msg_len);// 通过socket通知服务端char ctrl[CTRL_MSG_MAX] = {0};strncpy(ctrl, CTRL_MSG_PUSH, sizeof(ctrl)-1);if (!send_fixed(sock_fd, ctrl, sizeof(ctrl))) {fprintf(stderr, "[CLIENT] send PUSH failed.\n");return 1;}printf("[CLIENT] -> PUSH: seq=%u len=%u data=\"%s\"\n", pkt->seq, pkt->data_len, msg);// 4) 等待服务端回写共享内存并通过socket发PULL通知if (!recv_fixed(sock_fd, ctrl, sizeof(ctrl))) {fprintf(stderr, "[CLIENT] recv ctrl failed.\n");return 1;}if (strncmp(ctrl, CTRL_MSG_PULL, sizeof(ctrl)) == 0) {shm_packet_t *in = (shm_packet_t*)shm_addr;if (sizeof(shm_packet_t) + in->data_len <= SHM_SIZE) {printf("[CLIENT] <- PULL: seq=%u len=%u data=\"%.*s\"\n",in->seq, in->data_len, in->data_len, in->data);} else {printf("[CLIENT] <- PULL: invalid length!\n");}} else {printf("[CLIENT] <- UNKNOWN CTRL: \"%.*s\"\n", (int)sizeof(ctrl), ctrl);}// 5) 发送EXIT让服务端优雅退出(演示)memset(ctrl, 0, sizeof(ctrl));strncpy(ctrl, CTRL_MSG_EXIT, sizeof(ctrl)-1);send_fixed(sock_fd, ctrl, sizeof(ctrl));// 6) 清理if (sock_fd >= 0) close(sock_fd);if (shm_addr && shm_addr != MAP_FAILED) munmap(shm_addr, SHM_SIZE);if (shm_fd >= 0) close(shm_fd);printf("[CLIENT] Exit.\n");return 0;
}

common_net.h

#ifndef ADS_COMMON_H
#define ADS_COMMON_H#include <stdint.h>#define SHM_NAME        "/ads_demo_shm"     // POSIX共享内存名(会出现在 /dev/shm/ads_demo_shm)
#define SHM_SIZE        4096                // 共享内存大小
#define SOCK_PATH       "/data/standard_sdk/personal/xal61637/ADS_FUNC_TEST/data/ads_demo.sock"// UNIX域Socket路径(文件形式)// 控制消息(通过Socket传输)——简单起见用定长字符串
#define CTRL_MSG_MAX    64
#define CTRL_MSG_PUSH   "PUSH"   // 客户端通知服务器“我往共享内存写好了”
#define CTRL_MSG_PULL   "PULL"   // 服务器通知客户端“我往共享内存写好了”
#define CTRL_MSG_EXIT   "EXIT"   // 请求对方退出// 协议约定:客户端将数据写到共享内存中的 shm_packet_t,然后发一条 "PUSH" 告知服务端“可以读了”。
//共享内存中的数据格式(示例:简单的报头 + 文本)
typedef struct {uint32_t seq;            // 序号,演示数据变化uint32_t data_len;       // 有效数据长度(<= SHM_SIZE - sizeof(header))char     data[];         // 柔性数组成员,紧随其后
} shm_packet_t;#endif // ADS_COMMON_H

cmakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(SocketDemo)set(CMAKE_CXX_STANDARD 17)
include_directories(include)add_executable(client src/client.cpp src/common.cpp)
add_executable(server src/server.cpp src/common.cpp)target_link_libraries(server PRIVATE rt) 
target_link_libraries(client PRIVATE rt)

 

三、 共享内存和socket

设计要点 & 常见坑

  1. 为什么要“共享内存 + Socket”组合?
  • 共享内存:大数据零拷贝(如图像/点云/矩阵),极快
  • Socket:结构化通知与控制(谁写好了、谁该读、何时退出、异常处理)
  • 组合后既快又可控,是业界常用范式
  1. 共享内存生命周期
  • shm_open(O_CREAT|O_RDWR) + ftruncate 由创建方(通常Server)执行
  • 所有进程 mmap 后即可读写
  • 不 shm_unlink 就会残留,建议 Server 优雅退出时调用 shm_unlink;
    想“进程退出自动释放”效果:创建后立即 shm_unlink,所有进程关闭后会被内核销毁(如同文件 unlink 机制)
  1. 并发/同步
  • 示例用“消息通知”隐式同步(谁先写谁发消息)
  • 复杂情形建议加环形队列 + 原子变量或**POSIX信号量(sem_open)**做互斥/同步
  1. 错误处理与健壮性
  • 真实工程中务必检查 send/recv 的返回值,并处理对端断开
  • 注意 SHM 中 data_len 的 边界,防御性检查避免越界
  1. 权限与安全
  • /tmp/xxx.sock 对权限敏感,必要时用 umask/chmod
  • SHM 权限 0666 仅示例,实际按需求收紧

对比

1. 通信范围

  • 共享内存
    • 只能在同一台机器上的不同进程之间通信(IPC,进程间通信)。
    • 本质上是让两个进程访问同一段物理内存。
    • 跨机器不能用。
  • 网络套接字(Socket)
    • 不限于本机,既可以本机进程之间通信,也可以跨网络通信(不同服务器之间)。
    • TCP/UDP 都是基于 Socket 的。

📌 简单类比

共享内存像两个人用同一个笔记本写字,必须坐在同一张桌子上;

Socket 像两个人通过电话交流,可以隔着世界另一边说话。

2. 速度

  • 共享内存
    • 最快的 IPC 方式之一,因为数据不经过内核缓冲区拷贝(直接在物理内存上操作)。
    • 适合大量数据、高频率的通信,比如视频帧、传感器数据。
  • Socket
    • 会经过操作系统内核协议栈,多次数据拷贝,比共享内存慢很多。
    • 延迟比共享内存大。

3. 实现难度

  • 共享内存
    • 数据结构需要自己管理,比如写指针、读指针、同步锁。
    • 如果两个进程同时写同一个区域,没有锁会乱。
  • Socket
    • 协议栈帮你做好了数据收发的顺序和可靠性(尤其是 TCP)。
    • 不需要自己管理读写位置,但需要考虑网络延迟、丢包等问题(UDP)。

4. 数据存储特性

  • 共享内存
    • 只要你不 shm_unlink(),即使进程退出,数据仍然存在,其他进程还可以访问。
    • 必须手动删除,否则会一直占用内存。
  • Socket
    • 数据发出去后就没了(除非自己写到文件)。
    • 连接断了,数据就丢。

5. 典型使用场景

  • 共享内存
    • 摄像头视频流处理(DMS、ADAS 数据)
    • 大型科学计算中不同进程共享数据集
  • Socket
    • 远程服务调用(比如 HTTP 请求)
    • 游戏服务器与客户端通信
    • 车载 ECU 之间的通信(以太网)

http://www.lryc.cn/news/620980.html

相关文章:

  • 从概率填充到置信度校准:GPT-5如何从底层重构AI的“诚实”机制
  • 【网络安全测试】手机APP安全测试工具NowSecure 使用指导手册(有关必回)
  • PHP 开发全解析:从基础到实战的进阶之路
  • 【CV 目标检测】R-CNN①——Overfeat
  • GPT-5 提示词优化全攻略:用 Prompt Optimizer 快速迁移与提升,打造更稳更快的智能应用
  • RH134 管理基本存储知识点
  • 【车联网kafka】用钟表齿轮理解 Kafka 时间轮​(第七篇)
  • PlantSimulation知识点2025.8.14
  • pycharm远程连接服务器跑实验详细操作
  • 云计算-Docker Compose 实战:从OwnCloud、WordPress、SkyWalking、Redis ,Rabbitmq等服务配置实例轻松搞定
  • UML函数原型中stereotype的含义,有啥用?
  • UE5 C++ 删除文件
  • 4.Ansible部署文件到主机
  • 配置docker pull走http代理
  • 【网络】HTTP总结复盘
  • 河南萌新联赛2025第(五)场:信息工程大学补题
  • TensorFlow深度学习实战(32)——深度Q网络(Deep Q-Network,DQN)
  • Azure微软云内网接入问题
  • 老生常谈之引用计数:《More Effective C++》条款29
  • 位操作:底层编程利器
  • 通过网页调用身份证阅读器http websocket方法-华视电子————仙盟创梦IDE
  • Uniapp 中 uni.request 的二次封装
  • 22.Linux samba服务
  • 15、C语言预处理知识点总结
  • 18.14 全量微调实战手册:7大核心配置提升工业级模型训练效率
  • 并发编程原理与实战(二十三)StampedLock应用实战与其他锁性能对比分析
  • 深度学习ubuntu系统常用指令和技巧
  • VisDrone数据集,专为无人机视觉任务打造
  • Linux面试题及详细答案 120道(1-15)-- 基础概念
  • 9.【C++进阶】继承