Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Linux篇】共享内存实战:打造高性能服务端与客户端通信的终极指南(赋源码)

【Linux篇】共享内存实战:打造高性能服务端与客户端通信的终极指南(赋源码)

作者头像
熬夜学编程的小王
发布于 2025-04-25 03:15:37
发布于 2025-04-25 03:15:37
16800
代码可运行
举报
文章被收录于专栏:编程小王编程小王
运行总次数:0
代码可运行
突破传统通信模式:利用共享内存实现极速服务端与客户端对话

共享内存是一种高效的进程间通信(IPC)方式,允许多个进程访问同一块内存区域。通过共享内存,进程之间可以直接读写数据,而无需经过操作系统的中介,从而减少了上下文切换的开销。在服务端和客户端通信中,服务端通常会创建共享内存并将数据写入该区域,客户端可以直接读取该数据。由于共享内存不需要通过管道、套接字等其他通信方式,因此在高性能应用中非常有用。实现简单的服务端与客户端通信时,服务端负责创建并初始化共享内存,客户端则连接到该内存并读取或写入数据。这样,双方可以通过共享内存高效地交换信息。

💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力! 👍点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力! 🚀分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对Linux OS感兴趣的朋友,让我们一起进步!

前言

System V标准(又称System V IPC)是UNIX操作系统中的一组标准,定义了进程间通信(IPC)机制,旨在使不同UNIX系统之间的通信操作具有一致性。System V提供多种IPC机制,包括共享内存,消息队列,信号量等。

一. 共享内存(Shared Memory)

1.1 基本概念

允许多个进程访问同一块物理内存区域。这是最有效的IPC机制,速度最快,因为数据不需要通过内核传递,而是直接在进程之间共享。

1.2 共享内存相关函数接口

1.2.1 shmget()函数
  • 语法如下:

int shmget(key_t key, size_t size, int shmflg);

  • 功能:

shmget 是在 System V 标准中用于获取共享内存段的系统调用。它的作用是创建一个新的共享内存段,或者打开一个已存在的共享内存段,并返回一个标识符,供后续操作使用。 参数:

  • key:共享内存段的键值。通过它标识共享内存,可以在不同进程间共享。
  • size:共享内存段的大小,单位是字节。
  • shmflg:标志位,用于指定权限和其他控制选项(例如 IPC_CREAT 用于创建共享内存,如果不存在就创建;否则打开这个已经创建的共享内存,并返回它的shmid。IPC_EXCL单独使用无意义,需结合是使用:IPC_CREAT | IPC_EXCL,如果要创建的共享内存不存在,就创建;如果已经存在就返回-1,表示发生错误)。
  • 返回值:

返回值是共享内存的标识符(shmid),用于后续的操作。如果返回值是 -1,则表示发生错误。

注意上述的key值需要用户手动传入给内核:调用ftok()系统调用。

原型如下:

key_t ftok(const char *pathname, int proj_id);

  • 参数:

pathname:指定一个存在的文件路径。该文件用作生成键值的基础。该路径必须存在,可以与当前的工作区不相关联。

proj_id:是一个单字节的项目标识符。它通常是一个字母或数字(范围为 0 到 255)。与文件路径结合,生成一个唯一的键值。

  • 返回值:

成功时,返回生成的唯一键值(key_t 类型)。该键值通常是一个整数。

失败时,返回 -1,并设置 errno。

因为创建的共享内存随内核,所以要手动删除,如果要删除共享内存,可以使用指令:

  • 语法如下:

ipcrm -m [shmid值],注意:shmid不是key值,key只使用OS来识别的,而用户使用shmid来进行操作。

1.2.2 shmat()函数
  • 函数原型:

void *shmat(int shmid, const void *shmaddr, int shmflg);

  • 功能: shmat 是在 System V 标准中用于将共享内存段附加到当前进程地址空间的系统调用。它允许进程访问通过 shmget 创建的共享内存段。调用 shmat 后,进程可以像访问普通内存一样读取和写入共享内存段中的数据。简单点说就是:将共享内存与进程地址空间建立映射关系。 参数:
  • shmid:共享内存段的标识符,它是通过 shmget 获取的。它用于标识要附加的共享内存段。
  • shmaddr:建议附加共享内存段的地址。通常设置为 NULL,让操作系统自动选择一个合适的地址。如果不为 NULL,系统会尽量将共享内存段附加到指定的地址。
  • shmflg:标志位。常见的标志包括:SHM_RDONLY:以只读模式操作共享内存。0:以读写模式操作共享内存(默认)。

返回值:

  • 成功时,shmat 返回共享内存段的起始地址(是一个指向共享内存的指针)。这个地址是物理地址,可以直接使用,不再需要进行映射了。
  • 失败时,返回 -1,并设置 errno。
1.2.3 shmdt()函数
  • 函数原型:

int shmdt(const void *shmaddr);

  • 功能: shmdt 是 System V IPC(进程间通信)中的一个系统调用,用于 分离 共享内存段。它的作用是将之前通过 shmat 映射到进程地址空间的共享内存段从进程的地址空间中移除。调用 shmdt 后,进程不能再通过该地址访问共享内存。 参数:
  • shmaddr:指向共享内存的起始地址,通常是通过 shmat 返回的地址。它是指向共享内存区域的指针。

返回值:

  • 成功时,返回 0。
  • 失败时,返回 -1,并设置 errno。

注意事项:

  • 共享内存的删除:shmdt 只是从进程的地址空间中分离共享内存,并不会删除共享内存段。如果共享内存不再需要,可以使用 shmctl 函数来删除共享内存段。
  • 多进程共享内存:当多个进程共享同一块内存时,每个进程在完成共享内存的操作后,都应调用 shmdt 来分离共享内存。如果不再需要共享内存,最终应通过 shmctl 删除共享内存段。
  • 清理:在分离共享内存后,如果其他进程仍然附加该共享内存,系统不会立即删除共享内存,直到最后一个进程调用 shmdt并且没有其他进程附加它时,才会被完全清理。
1.2.4 shmctl()函数
  • 函数原型:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

  • 功能:

shmctl 是一个用于控制共享内存段的 System V IPC 函数。通过 shmctl,你可以对共享内存段进行多种管理操作,如获取共享内存的状态、删除共享内存段等。它是对共享内存资源的高级管理工具。 参数:

  1. shmid:共享内存段的标识符,这是通过 shmget 返回的 ID。
  2. cmd:命令,指定对共享内存段执行的操作。常见的命令有:

<一> IPC_STAT:获取共享内存段的状态,并将信息存储在 shmid_ds 结构中。

<二> IPC_SET:修改共享内存段的权限或其他属性。

<三> IPC_RMID:删除共享内存段,标记该共享内存段为待删除。只有在所有进程都断开对该共享内存的连接后,内存段才会被系统回收。

  1. buf:指向 shmid_ds 结构体的指针,该结构体用于存储共享内存段的信息(用于 IPC_STAT 和 IPC_SET 命令)。对于IPC_RMID,该参数可以为 NULL。

示例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 创建共享内存
    int shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget failed");
        return -1;
    }

    // 获取共享内存的状态
    struct shmid_ds shm_info;
    if (shmctl(shmid, IPC_STAT, &shm_info) == -1) {
        perror("shmctl IPC_STAT failed");
        return -1;
    }

    // 打印共享内存信息
    printf("Shared memory size: %zu bytes\n", shm_info.shm_segsz);
    printf("Last attach time: %s", ctime(&shm_info.shm_atime));

    // 删除共享内存
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl IPC_RMID failed");
        return -1;
    }

    return 0;
}

二. 服务端与客户端通信的实现

2.1 简介

在操作系统中,共享内存是进程间通信(IPC)的一种高效方式,因为它允许多个进程直接访问同一块内存区域,而不需要通过管道、套接字等其他通信机制。使用共享内存,服务端和客户端可以通过读写共享内存来交换数据。

2.2 实现通信

2.2.1 Shm 类(共享内存管理)

功能:

  • 用于创建、获取、附加、分离和销毁共享内存。通过 shmget 获取共享内存的标识符,shmat 将共享内存附加到进程的地址空间。
  • 使用 ftok 生成唯一的共享内存 key,并通过 IPC_CREAT 创建共享内存。shmctl 用于删除共享内存。
  • 这个类区分了两种用户类型:CREATER 和 USER。CREATER 创建共享内存并将数据写入共享内存,而 USER从共享内存读取数据。

示例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma once

#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "comm.hpp"

const int defaultid = 1;
const int gsize = 4096;
const std::string pathname = ".";
const int projid = 0X66;
const int gmode = 0666;
#define CREATER "creater"
#define USER "user"

class Shm
{
private:
    void CreateHelper(int flg)
    {
        printf("key: 0x%x\n", _key);
        // 共享内存的生命周期,随内核
        // shmget() 会失败(因为共享内存已存在),导致程序直接终止。
        _shmid = shmget(_key, _size, flg);
        if (_shmid < 0)
        {
            ERR_EXIT("shmget");
        }
        printf("shmid: %d\n", _shmid);
    }
    void Create()
    {
        CreateHelper(IPC_CREAT | IPC_EXCL | gmode);
    }
    void Attach()
    {
        _start_mem = shmat(_shmid, nullptr, 0);
        if ((long long)_start_mem < 0)
        {
            ERR_EXIT("shmat");
        }
        printf("attach success\n");
    }
    void Detach()
    {
        int n = shmdt(_start_mem);//去关联系统调用
        if (n == 0)
        {
            printf("detach success\n");
        }
    }
    void Get()
    {
        CreateHelper(IPC_CREAT); // 不存在则创建;否则打开它,并返回
    }
    void Destory()
    {
        Detach();
        if (_usertype == CREATER)
        {
            int n = shmctl(_shmid, IPC_RMID, nullptr);
            if (n >= 0)
            {
                printf("shmcyl delete shm: %d success!\n", _shmid);
            }
            else
            {
                ERR_EXIT("shmctl");
            }
        }
    }

public:
    Shm(const std::string &pathname, int projid, const std::string &usertype)
        : _shmid(defaultid),
          _size(gsize),
          _start_mem(nullptr),
          _usertype(usertype)
    {
        _key = ftok(pathname.c_str(), projid); // 手动创建key值,用于让不同的进程看到一份资源(共享内存)
        if (_key < 0)
        {
            ERR_EXIT("ftok");
        }
        if (_usertype == CREATER)
            Create();
        else if (_usertype == USER)
            Get();
        else
        {
        }
        Attach();
    }
    void *VirtualAddr()
    {
        printf("VirtualAddr: %p\n", _start_mem); // 存在于堆栈区间的虚拟地址,堆栈之间的空间是共享区,
        // 属于用户的空间,不需要用系统调用
        // 共享内存是进程间通信最快的方式:原因1:映射之后直接被对方看到,原因2:不需要系统调用,花费额外的时间开销
        return _start_mem;
    }
    int Size()
    {
        return _size;
    }
    void Attr()
    {
        struct shmid_ds ds;
        int n =shmctl(_shmid, IPC_STAT,&ds);
        printf("shm_segsz: %ld\n",ds.shm_segsz);
        printf("key: 0x%x\n",ds.shm_perm.__key);
    }
    ~Shm()
    {
        if (_usertype == CREATER)
            Destory();
    }

private:
    int _shmid;
    key_t _key;
    int _size;
    void *_start_mem;
    std::string _usertype;
};
2.2.2 nameFifo 类(命名管道管理)
  • 用于创建和删除命名管道(FIFO)。mkfifo 用于创建管道,unlink 删除管道文件。
  • 提供了管道路径和名称的配置,允许在指定路径下创建命名管道。

注意:该类主要用了实现数据的同步,防止数据不一致,同时当客户端端有数据时(wakeup函数)通知服务端读取(wait函数)数据,真正的数据存在共享内存中。 示例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <unistd.h>
#include <cstdio>
#include "comm.hpp"

#define PATH "."
#define FIFENAME "fifo"

class NameFifo
{
public:
    NameFifo(const std::string &path, const std::string &name)
        : _path(path), _name(name)
    {
        _filoname = _path + "/" + _name;
        umask(0);
        // 新建管道
        int n = mkfifo(_filoname.c_str(), 0666);
        if (n < 0)
        {
            ERR_EXIT("mkfifo");
        }
        else
        {
            std::cout << "mkfifo success" << std::endl;
        }
    }

    ~NameFifo()
    {
        // 删除管道文件
        int n = unlink(_filoname.c_str());
        if (n == 0)
        {
            //ERR_EXIT("unlink");
        }
        else
        {
            std::cout << "remove fifo failed" << std::endl;
        }
    }

private:
    std::string _path;
    std::string _name;
    std::string _filoname;
};
2.2.3 FileOper 类(管道文件操作)
  • 提供了打开管道的功能,支持读写操作。
  • OpenForRead 打开管道以进行读取,OpenForWrite 打开管道以进行写入。
  • Wait 方法等待从管道中读取数据,而 Wakeup 方法用于唤醒阻塞的读取操作。
  • 使用 close 方法关闭文件描述符。

示例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class FileOper
{
public:
    FileOper(const std::string &path, const std::string &name)
        : _path(path), _name(name), _fd(-1)
    {
        _filoname = _path + "/" + _name;
    }
    void OpenForRead()
    {
        // 打开管道文件
        _fd = open(_filoname.c_str(), O_RDONLY);
        if (_fd < 0)
        {
            ERR_EXIT("open");
        }
        std::cout << "open fifo sucess" << std::endl;
    }
    void OpenForWrite()
    {
        // write
        _fd = open(_filoname.c_str(), O_WRONLY);
        if (_fd < 0)
        {
            ERR_EXIT("open");
        }
        std::cout << "open fifo success" << std::endl;
    }
    void Wakeup()
    {
        // 写入操作
        char c = 'c';
        int n = write(_fd, &c, 1);
        printf("尝试唤醒: %d\n", n);
    }
    bool Wait()
    {
        char c;
        int number = read(_fd, &c, 1);
        if (number > 0)
        {
            printf("醒来: %d\n", number);
            return true;
        }
        return false;
    }
    void Close()
    {
        if (_fd > 0)
            close(_fd);
    }
    ~FileOper()
    {
    }

private:
    std::string _path;
    std::string _name;
    std::string _filoname;
    int _fd;
};
2.2.4 总结

通过共享内存和命名管道的协作,程序实现了一个高效的进程间通信机制。共享内存提供了高效的直接数据交换通道,而管道则充当了同步信号的角色,确保数据的可靠传输。

三. 最后

本文介绍了利用共享内存实现高效服务端-客户端通信的方法。共享内存作为进程间直接访问的内存区域,极大提升了数据传输效率,避免了传统IPC方式的数据拷贝开销。配合命名管道(FIFO)进行同步控制,服务端写入数据后通过管道发送唤醒信号,客户端阻塞等待信号后读取共享内存,确保数据一致性。Shm类封装了共享内存的创建、映射与销毁,FileOper类处理管道读写,二者协作实现了通知-读取的高效通信模式,适用于高频数据交换场景,兼顾性能与可靠性。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-04-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
深入了解linux系统—— 共享内存
System V IPC(Inter-Process Communication)是Unix System V操作系统引入的一组进程间通信机制,后来被大多数Unix-like系统所采纳。它包含三种主要的通信方式:
星辰与你
2025/06/09
910
深入了解linux系统—— 共享内存
Linux进程通信--共享内存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
南桥
2024/07/26
4640
Linux进程通信--共享内存
【Linux】「共享内存揭秘」:高效进程通信的终极指南
探索Python数据结构与算法:解锁编程的无限可能:https://cloud.tencent.com/developer/article/2471556
Yui_
2024/11/27
4340
【Linux】「共享内存揭秘」:高效进程通信的终极指南
【Linux篇章】进程通信黑科技:System V 共享内存,开启进程间通信的星际数据通道!
在计算机系统里,进程就像一个个独立工作的小员工,各自完成自己的任务。但有时候,这些小员工之间需要交流信息、共享数据,这就涉及到进程间通信(IPC)。System V 共享内存就是进程间通信的一种强大方式,下面就带大家了解一下它的奥秘。
羑悻的小杀马特.
2025/04/30
1270
【Linux篇章】进程通信黑科技:System V 共享内存,开启进程间通信的星际数据通道!
【Linux】进程间通信——System V共享内存
  System V是一种在Linux系统中用于进程间通信(IPC)的机制。它提供了几种不同的通信方式,包括共享内存、消息队列和信号量。以下是关于Linux进程间通信System V共享内存的详细解释:
大耳朵土土垚
2024/12/09
2310
【Linux】进程间通信——System V共享内存
[操作系统] 进程间通信:system V共享内存
System V 是UNIX操作系统的一个版本,它定义了一系列的标准,包括进程间通信(IPC)的标准。Linux操作系统的内核实现了 <font style="color:rgb(6, 6, 7);">System V</font> 定义的IPC标准,并为此专门设计了一个模块来处理进程间的通信。进程间通信(IPC)的核心目的是允许不同的进程能够访问和操作同一份资源。这样,进程之间就可以通过共享资源来交换信息。不同的IPC机制可能在接口和原理上有相似之处,使得开发者可以更容易地理解和使用这些机制。
DevKevin
2025/05/30
900
[操作系统] 进程间通信:system V共享内存
【Linux】从零开始认识进程间通信 —— 共享内存
吃苦受难绝不是乐事一桩,但是如果您恰好陷入困境,我很想告诉您:“尽管眼前十分困难,可日后这段经历说不定就会开花结果。”请您这样换位思考、奋力前行。
叫我龙翔
2024/06/02
3920
【Linux】从零开始认识进程间通信 —— 共享内存
【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)
命名管道是通过文件路径让不同进程看到同一份资源。 命名管道可以让两个毫不相干的进程进行进程间通信。
秦jh
2024/10/29
2940
【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)
【Linux】进程间通信>管道&&共享内存&&消息队列&&信号量详解
vscode远程连接指南:VScode远程连接虚拟机(ubuntu系统)_vscode连接ubuntu-CSDN博客
用户10925563
2024/06/04
2330
【Linux】进程间通信>管道&&共享内存&&消息队列&&信号量详解
【Linux】IPC 进程间通信(二)(共享内存)
🚀 共享内存是一种进程间通信(IPC)机制,它允许多个进程直接访问同一块内存区域,从而实现高效的数据交换。
IsLand1314
2024/11/19
3250
【Linux】IPC 进程间通信(二)(共享内存)
Linux进程间通信【共享内存】
共享内存出自 System V 标准,是众多 IPC 解决方案中最快的一种,使用共享内存进行通信时,不需要借助函数进入内核传递数据,而是直接对同一块空间进行数据访问,至于共享内存是如何使用的、通信原理是怎么实现的、以及共享内存+命名管道的组合通信程序该如何实现,都将在本文中解答
北 海
2023/07/01
6410
Linux进程间通信【共享内存】
【Linux】systemV共享内存
操作系统在物理内存上申请一块空间,然后将申请到的空间通过页表映射到进程地址空间mm_struct的共享区中,然后返回虚拟地址供程序使用,如果多个进程申请的是同一块物理空间,那么它们就可以进行通信 由于同一时间可能有多组进程进行通信,所以系统当中可能存在多个共享内存块,所以操作系统要把这些内存管理起来,所以内核中会有一个结构体来描述共享内存
s-little-monster
2025/03/25
2781
【Linux】systemV共享内存
进程间通信(二)/共享内存
要实现进程间通信,其前提是让不同进程之间看到同一份资源。所谓共享内存,那就是不同进程之间,可以看到内存中同一块资源,这就是共享内存的概念。
二肥是只大懒蓝猫
2023/03/30
9670
进程间通信(二)/共享内存
Linux系统编程-进程间通信(共享内存)
前面陆续介绍了标准管道流、无名管道、命名管道、mmap内存映射,这篇文章介绍共享内存段。
DS小龙哥
2022/02/17
2.2K0
Linux进程间通信(下)之共享内存实践
上节和上上节我们分享了Linux进程间通信的管道、消息队列、信号以及信号量的基本原理和实践,文章如下:
杨源鑫
2020/09/14
2.2K0
进程间通信方式——共享内存「建议收藏」
共享内存就是允许两个或多个进程共享一定的存储区。就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。因为数据不需要在客户机和服务器端之间复制,数据直接写到内存,不用若干次数据拷贝,所以这是最快的一种IPC。
全栈程序员站长
2022/09/27
1.3K0
初识Linux · 共享内存
前文介绍的管道方式的通信,本文介绍的是进程通信的另外一种方式,即共享内存。但是这种通信方式的特点是只能本地通信,并且不像管道那样有保护机制,这里是没有的。
_lazy
2024/11/19
3630
初识Linux · 共享内存
【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)
回车之后管道不会关闭,在终端2查看可以发现他的内存大小仍然是0,当我们在管道2打印出内容后,管道就自动关闭了
用户11036582
2024/11/05
2260
【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)
进程间通信 共享内存
取消共享地址映射 当共享内存使用完毕后,调此接口会结束共享内存与指定的系统地址的映射关系。这里并未从系统中删除标识符,该标识符一直存在直至某个进程带IPC_RMID命令调用shmctl特地删除它为止。
开源519
2021/09/16
1.1K0
进程间通信 共享内存
进程通信(三)共享内存
共享内存是操作系统直接在物理内存上开辟一段空间作为进程间通信的缓冲区域, 与管道、消息队列等其他进程通信方式相比较,共享内存拥有更高的效率,原因是共享内存的设计是基于物理内存的地址直接进行操作的,这样相比其他方式的IPC省去了重重的系统调用,因此在很大程度上提高了其效率。
lexingsen
2022/02/24
1.3K0
进程通信(三)共享内存
相关推荐
深入了解linux系统—— 共享内存
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验