【进程间通信】——基于共享内存的同文件进行读写

目录

上篇文章《【进程间通信】——共享内存收发》介绍了一个进程发,另一个进程收,本章介绍两个进程分别进行收发,其中基于共享内存的动态库仍然保持不变。

一、基于共享内存的动态库

1.1 源码

头文件shared_memory.h内容如下,通过结构体SharedData定义了传输的消息类型,此时结构体内声明了一个uint8类型的数组,数组长度为1024。

// shared_memory.h
#ifndef SHARED_MEMORY_H
#define SHARED_MEMORY_H

#include <cstdint>

struct SharedData {
    uint16_t len;
    uint8_t data[1024];  // 分配一个足够大的空间来存储数据
};

bool create_shared_memory(const char* name);
bool write_shared_memory(const char* name, const uint8_t* data, uint16_t len);
bool read_shared_memory(const char* name, uint8_t* data, uint16_t len);
bool close_shared_memory(const char* name);


#endif

函数定义文件shared_memory.cpp主要包含创建共享内存空间、向共享内存地址写数据、从共享内存地址读数据、销毁共享内存空间。

// shared_memory.cpp
// g++ -shared -fPIC shared_memory.cpp -o libsharedmemory.so -lrt


#include "shared_memory.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <cstring>
#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <errno.h>

bool create_shared_memory(const char* name) {
    int shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) {
        std::cerr << "Failed to create shared memory." << std::endl;
        return false;
    }

    if (ftruncate(shm_fd, sizeof(SharedData)) == -1) {
        std::cerr << "Failed to set shared memory size." << std::endl;
        return false;
    }
    
    close(shm_fd);
    return true;
}

bool write_shared_memory(const char* name, const uint8_t* data, uint16_t len) {
    int shm_fd = shm_open(name, O_RDWR, 0666);
    if (shm_fd == -1) {
        std::cerr << "Failed to open shared memory." << std::endl;
        return false;
    }

    SharedData* shared_data = reinterpret_cast<SharedData*>(mmap(nullptr, sizeof(SharedData), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0));

    shared_data->len = len;
    memcpy(shared_data->data, data, len);

    munmap(shared_data, sizeof(SharedData));
    close(shm_fd);
    return true;
}

bool read_shared_memory(const char* name, uint8_t* data, uint16_t len) {
    int shm_fd = shm_open(name, O_RDONLY, 0666);
    if (shm_fd == -1) {
        std::cerr << "Failed to open shared memory." << std::endl;
        return false;
    }

    SharedData* shared_data = reinterpret_cast<SharedData*>(mmap(nullptr, sizeof(SharedData), PROT_READ, MAP_SHARED, shm_fd, 0));

    len = shared_data->len;
    memcpy(data, shared_data->data, len);

    munmap(shared_data, sizeof(SharedData));
    close(shm_fd);
    return true;
}

bool close_shared_memory(const char* name) {
    return shm_unlink(name) == 0;
}

此时不再在函数定义文件shared_memory.cpp指定固定的共享内存名称,而是作为一个参数传入,同样在写数据时,也是将要写入的变量传入,通过memcpy函数写入到共享内存中;在读数据时,将准备好的变量传入,通过memcpy函数拷贝出来。

在写和读共享内存时,通过mmap()函数映射临时变量shared_data到共享内存空间后,在操作完数据后需要通过munmap()函数解除进程虚拟地址空间的映射。

1.2 编译命令

g++ -shared -fPIC shared_memory.cpp -o libshared_memory.so

注意其它程序要动态链接该库,需要添加动态库寻找的路径:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

最后的.代表动态库处于当前目录。

1.3 函数解释

1.3.1 shm_open()函数

shm_open是一个POSIX共享内存API的函数,用于创建或打开一个命名的共享内存对象。这个函数的原型如下:
int shm_open(const char *name, int oflag, mode_t mode);

参数:

  • name:共享内存对象的名称,通常以一个斜杠(/)开头。
  • oflag:打开或创建共享内存对象的标志,如O_CREAT(创建)、O_RDWR(读写模式)等。
  • mode:设置共享内存对象的权限,如S_IRUSR(用户读权限)和S_IWUSR(用户写权限)等。
  • 返回值:成功时返回一个非负整数的文件描述符,失败时返回-1。

1.3.2 ftruncate()函数

ftruncate函数用于调整文件大小,通常用于设置共享内存对象的大小。这个函数的原型如下:
int ftruncate(int fd, off_t length);

参数:

  • fd:文件描述符,通常是shm_open函数的返回值。
  • length:文件的新大小。
  • 返回值:成功时返回0,失败时返回-1。

1.3.3 mmap()函数

mmap函数用于将一个文件或共享内存对象映射到进程的虚拟地址空间。这个函数的原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

参数:

  • addr:建议的映射起始地址,通常设置为NULL,让系统自动选择。
  • length:映射区域的大小。
  • prot:映射区域的保护属性,如PROT_READ(可读)、PROT_WRITE(可写)等。
  • flags:映射类型,如MAP_SHARED(共享映射)或MAP_PRIVATE(私有映射)等。
  • fd:文件描述符,通常是shm_open函数的返回值。
  • offset:文件偏移量。
  • 返回值:成功时返回映射区域的起始地址,失败时返回MAP_FAILED。

1.3.4 munmap()函数

munmap函数用于解除进程虚拟地址空间的映射。这个函数的原型如下:
int munmap(void *addr, size_t length);

参数:

  • addr:映射区域的起始地址。
  • length:映射区域的大小。
  • 返回值:成功时返回0,失败时返回-1。

1.3.5 shm_unlink()函数

shm_unlink函数用于删除一个命名的共享内存对象。当共享内存对象的引用计数降至0时,它会被系统释放。这个函数的原型如下:

int shm_unlink(const char *name);

参数:

  • name:共享内存对象的名称,通常与shm_open函数中使用的相同。
  • 返回值:成功时返回0,失败时返回-1。

1.3.6 close()函数

close函数用于关闭一个文件描述符。这个函数的原型如下:

int close(int fd);

参数:

  • fd:要关闭的文件描述符。
  • 返回值:成功时返回0,失败时返回-1。

二、sender.cpp

2.1 源码

// sender.cpp
// export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
// g++ sender.cpp -o sender -L. -lsharedmemory -lrt -lpthread

#include <iostream>
#include <string.h>
#include "shared_memory.h"
#include <thread>
#include <chrono>
#include <atomic>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <errno.h>

int main() {
    const char* shared_memory_name = "my_shared_memory";
    bool succes = create_shared_memory(shared_memory_name);

    uint8_t data[] = {1, 2, 3, 4, 5};
    uint16_t len = 5;
    if(write_shared_memory(shared_memory_name, data, len)){
        std::cout << "Data written to shared memory: ";
        for (int i = 0; i < len; ++i) {
            std::cout << (int)data[i] << " ";
        }
        std::cout << std::endl;
    } else {
        std::cerr << "Failed to create shared memory." << std::endl;
    }

    char ch = getchar();
    // 再次从共享内存中读取数据并打印出来
    uint8_t data2[5];
    uint16_t len2 = 5;
    if(read_shared_memory(shared_memory_name, data2, len2)){
        std::cout << "Data read from shared memory: ";
        std::cout << "len2=" <<len2<< std::endl;
        for (int i = 0; i < len2; ++i) {
            std::cout << static_cast<int>(data2[i]) << " ";
        }
        std::cout << std::endl;
    } else {
        std::cerr << "Failed to read shared memory." << std::endl;
    }
    std::cout << "Closing shared memory." << std::endl;
    close_shared_memory(shared_memory_name);
    return 0;
}

2.2 编译运行命令

编译:
g++ sender.cpp -o sender -L. -lshared_memory -lrt

  • g++:C++ 编译器。
  • sender.cpp:要编译的源文件。
  • -o sender:指定输出文件名。
  • -L.:告诉编译器在当前目录中查找共享库文件。
  • -lshared_memory:告诉编译器共享库的名称

运行:
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
./sender

注意运行该文件时,需要export命令添加共享内存动态库的路径,这里用$PWD获取动态库目录,并添加到$LD_LIBRARY_PATH目录之前。

2.3 源码解释

sender写入共享内存数据后,receiver接收数据,并更新数据,再次写入共享内存中,然后sender从共享内存中读出指定数量为len的数据。

三、receiver.cpp

3.1 源码

// receiver.cpp
// export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
// g++ receiver.cpp -o receiver -L. -lsharedmemory -lrt -lpthread

#include <iostream>
#include <string.h>
#include "shared_memory.h"
#include <thread>
#include <chrono>
#include <atomic>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <errno.h>

int main() {
    const char* shared_memory_name = "my_shared_memory";
    uint8_t data[] = {0, 0, 0, 0, 0};
    uint16_t len = 5;
    // 从共享内存读出数据并打印出来
    read_shared_memory(shared_memory_name, data, len);
    std::cout << "Data read from shared memory: ";
    for (int i = 0; i < len; ++i) {
        std::cout << static_cast<int>(data[i]) << " ";
    }
    std::cout << std::endl;

    // 再将这些数据全部+1再次写入
    for (int i = 0; i < len; ++i) {
        data[i] += 1;
    }
    bool success = write_shared_memory(shared_memory_name, data, len);
    if (success) {
        std::cout << "Data write to shared memory: ";
        for (int i = 0; i < len; ++i) {
            std::cout << static_cast<int>(data[i]) << " ";
        }
        std::cout << std::endl;
    } else
    std::cerr << "Failed to write shared memory." << std::endl;

    read_shared_memory(shared_memory_name, data, len);
    std::cout << "Data read from shared memory: ";
    for (int i = 0; i < len; ++i) {
        std::cout << static_cast<int>(data[i]) << " ";
    }
    std::cout << std::endl;
    return 0;
}

3.2 编译运行命令

编译:
g++ receiver.cpp -o receiver -L. -lshared_memory -lrt

运行:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
./receiver

注意运行该文件时,需要export命令添加共享内存动态库的路径。

3.3 源码解释

在上一篇文章的基础上,将数据更新,重新又写入共享内存中。

四、注意事项

1 注意std::cout无法输出uint类型数据,需要将其转换为int数据进行打印输出。

2 有个小缺陷,这里的len在收和发文件中必须相同,可以在数组中填充空的数据。

3 注意这里共享内存名称没加/似乎也可以。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容