共享内存类的封装与使用

共享内存机制

  共享内存机制是通过将物理内存中的某一区域映射到不同进程的地址空间中实现进程间通讯的,其基本原理类似于生产者和消费者模式,原理如下图所示:

共享内存的封装

  为了方便使用,我们对共享内存进行封装,分别为生产者类——ShareMemProvider和消费者类——ShareMemConsumer。虽然明确划分了生产者和消费者,但是考虑到实际使用,两者都添加了读写函数,不仅可以进行读操作,也可以进行写操作。
  适用的操作系统:
  · windows

共享内存生产者

  生产者的写入过程为:先创建映射文件句柄,如果创建成功则创建对应的指针来读写共享内存区域,如果成功,进一步创建互斥量句柄。最后,通过互斥量加锁访问共享内存。
  生产者ShareMemProvider类的声明如下:
  ShareMemProvider.h:

#ifndef SHAREMEMPROVIDER_H
#define SHAREMEMPROVIDER_H

#include <iostream>
#include <windows.h>
#include <vector>

/******************************************
 * @brief 共享内存的生产者
 * 这里实际给出的权限是可读可写的
 ******************************************/
class ShareMemProvider{
public:
    /******************************************
     * @brief Construct a new ShareMemProvider object
     * 共享内存生产者的构造函数
     * @param  memoryName       const string& 类型,共享内存的名称(唯一,不可重复)
     * @param  mutexName        const string& 类型,共享内存的互斥量名称
     * @param  dataSize         int类型 共享内存的空间大小,请提前预估大小
     ******************************************/
    ShareMemProvider(const std::string &memoryName, const std::string &mutexName, int dataSize);

    /******************************************
     * @brief Destroy the ShareMemProvider object
     * 析构函数
     ******************************************/
    virtual ~ShareMemProvider();

public:
    /******************************************
     * @brief 不带时间戳的write方法
     * 向共享内存中写入字符串数据,不带时间戳
     * @param  data             const string &类型,写入的数据
     ******************************************/
    void write(const std::string & data);

    /******************************************
     * @brief 带时间戳的write方法
     * 向共享内存中写入字符串数据,消费者可以根据时间戳决定是否要更新数据
     * @param  data             const string &类型,写入的数据
     * @param  timeStamp        time_t &类型,写入数据的时间戳
     ******************************************/
    void write(const std::string & data, const time_t & timeStamp);

    /******************************************
     * @brief 读取共享内存数据,和不带时间戳的write配对使用
     * 读取共享内存中的数据,并由data参数返回
     * @param  data             string &类型,用于存放读取的数据         
     ******************************************/
    void read(std::string & data);

    /******************************************
     * @brief 读取共享内存数据,和带时间戳的write配对使用
     * 读取共享内存中的数据,并由data参数返回
     * @param  data             string &类型,用于存放读取的数据 
     * @param  timeStamp        time_t &类型 时间戳,用于判定是否更新,所以为上一次的时间戳
     ******************************************/
    void read(std::string & data, time_t & timeStamp);

private:
    std::string memory_name_;
    std::string mutex_name_;
    int data_size_;

    char* pData = nullptr;          //数据区指针
    HANDLE  hFileMap = nullptr;     //文件映射句柄
    HANDLE hMutex_;                 //互斥量句柄
};

#endif //SHAREMEMPROVIDER_H

  生产者ShareMemProvider类的定义如下:
  ShareMemProvider.cpp:

#include "ShareMemProvider.h"

ShareMemProvider::ShareMemProvider(const std::string &memoryName, const std::string &mutexName, int dataSize)
                : memory_name_(memoryName), mutex_name_(mutexName), data_size_(dataSize) {
    //先打开一次,当前名称的内存空间已存在则则直接创建地址映射,如果不存在,则创建
    hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, memory_name_.c_str());
    if (!hFileMap)
        hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, data_size_, memory_name_.c_str());

    if (hFileMap != nullptr){
        pData = (char*)MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0);    //创建映射指针
        if (pData == nullptr){                                              //如果创建失败则关闭句柄并置空
            CloseHandle(hFileMap);
            hFileMap = nullptr;
        }
    }
    //创建互斥量
    hMutex_ = CreateMutex(nullptr, FALSE, mutex_name_.c_str());
}

ShareMemProvider::~ShareMemProvider() {
    if(nullptr != hMutex_){
        CloseHandle(hMutex_);
        ReleaseMutex(hMutex_);
        std::cout<<"m_hMutex closed!"<<std::endl;
    }

    if (nullptr != hFileMap){
        UnmapViewOfFile(pData);
        CloseHandle(hFileMap);
        ReleaseMutex(hFileMap);
        hFileMap = nullptr;
        pData = nullptr;
    }
}

void ShareMemProvider::write(const std::string &data) {
    try{
        if (pData != nullptr){
            WaitForSingleObject(hMutex_, INFINITE);
            long long len=data.size();
            std::string temp="$$" + std::to_string(len) + "$$" + data;   //使用 $$数据长度$$ 作为数据头
            memcpy(pData, temp.c_str(), temp.size());
            ReleaseMutex(hMutex_);
        }
    }catch (std::exception &e){
        std::cout << "Write Share Memory data Error!" << std::endl;
        std::cout << e.what() << std::endl;
    }
}

void ShareMemProvider::write(const std::string & data, const time_t & timeStamp){
    try{
        if (pData != nullptr){
            WaitForSingleObject(hMutex_, INFINITE);
            long long len=data.size();
            std::string temp="$$" + std::to_string(len) + "," + std::to_string(timeStamp) + "$$" + data;   //使用 $$数据长度,时间戳$$ 作为数据头
            memcpy(pData, temp.c_str(), temp.size());
            ReleaseMutex(hMutex_);
        }
    }catch (std::exception &e){
        std::cout << "Write Share Memory data Error!" << std::endl;
        std::cout << e.what() << std::endl;
    }
}

void ShareMemProvider::read(std::string &data){
    data.clear();                                   //先清空数据
    const int HEADLEN = 40;
    try{
        if (pData != nullptr){
            /************************************************************
             * step1:首次读共享内存的数据头,查看数据是否为空,不为空则获取数据长度
             ***********************************************************/
            std::string headData="";                //存放数据头
            long long len = 0;                      //存放长度
            int pos = 0;                            //用于存放查找位置
            char *strHead = new char[HEADLEN];      //临时存储字符

            WaitForSingleObject(hMutex_, INFINITE);
            memcpy(strHead, pData, HEADLEN);
            for (size_t i = 0; i < HEADLEN; ++i) 
                headData.push_back(strHead[i]);
            delete [] strHead;

            //解析数据头
            if(headData.substr(0,2)=="$$"){         //如果数据是以"$$"开始
                pos = headData.find("$$",2);        //查找数据头末尾的"$$"
                len = std::stoll(data.substr(2,pos-2)); //数据头只有长度
                /************************************************************
                 * step2:如果长度不为0,则第二次获取数据
                 ***********************************************************/
                if (len > 0){
                    char *strTemp = new char[len];
                    memcpy(strTemp, pData+pos+2, len);  //从数据的第一个位置开始读取
                    ReleaseMutex(hMutex_);
                    for (long long i = 0; i < len; ++i)
                        data.push_back(strTemp[i]);
                    delete [] strTemp;
                }
                else
                    ReleaseMutex(hMutex_);              //如果没有数据直接释放锁
            }
            else
                ReleaseMutex(hMutex_);                  //如果不是以$$开始,则直接释放锁

        }
    }catch (std::exception &e){
        std::cout << "Read Share Memory data Error!" << std::endl;
        std::cout << e.what() << std::endl;
    }
}

void ShareMemProvider::read(std::string & data, time_t & timeStamp){
    data.clear();                                   //先清空数据
    const int HEADLEN = 40;
    try{
        if (pData != nullptr){
            /************************************************************
             * step1:首次读共享内存的头,获取是否为空,不为空则获取数据长度
             ***********************************************************/
            std::string  headData="";
            long long len = 0;
            int pos = 0, splitPos=0;                //pos用于存放查找位置,splitPos用于存放头部分隔符的位置
            time_t now;                             //保存数据的时间戳
            char *strHead = new char[HEADLEN];

           WaitForSingleObject(hMutex_, INFINITE);
            memcpy(strHead, pData, HEADLEN);
            for (size_t i = 0; i < HEADLEN; ++i) 
                headData.push_back(strHead[i]);
            delete [] strHead;

            //解析数据头
            if(headData.substr(0,2)=="$$"){         //如果数据是以"$$"开始
                pos = headData.find("$$",2);        //查找数据头末尾的"$$"
                headData=headData.substr(2,pos-2);  //获取数据头
                splitPos = headData.find(",");      //查找分隔符位置,并获取长度和时间戳
                len = std::stoll(headData.substr(0, splitPos));
                now = std::stoll(headData.substr(splitPos + 1));
                /************************************************************
                 * step2:如果长度不为0,且时间戳和上次不同则第二次获取数据
                 ***********************************************************/
                if (len > 0 && now != timeStamp){
                    char *strTemp = new char[len];
                    memcpy(strTemp, pData+pos+2, len);
                    ReleaseMutex(hMutex_);
                    timeStamp = now;                //更新时间戳并获取数据
                    for (long long i = 0; i < len; ++i)
                        data.push_back(strTemp[i]);
                    delete [] strTemp;
                }
                else
                    ReleaseMutex(hMutex_);              //如果没有数据直接释放锁
            }
            else
                ReleaseMutex(hMutex_);                  //如果不是以$$开始,则直接释放锁

        }
    }catch (std::exception &e){
        std::cout << "Read Share Memory data Error!" << std::endl;
        std::cout << e.what() << std::endl;
    }
}

共享内存消费者

  消费者的读取过程为:先访问互斥量句柄,再创建文件映射句柄,如果成功再创建数据区的读写指针。最后通过互斥量加锁访问共享内存。
  消费者ShareMemConsumer类的声明如下:
  ShareMemConsumer.h:

#ifndef SHAREMEMCONSUMER_H
#define SHAREMEMCONSUMER_H

#include <iostream>
#include <windows.h>
#include <vector>

/******************************************
 * @brief 共享内存的消费者
 * 这里实际给出的权限是可读可写的
 ******************************************/
class ShareMemConsumer{

public:
    /******************************************
     * @brief Construct a new ShareMemConsumer object
     * 共享内存消费者的构造函数
     * @param  memoryName       const string& 类型,共享内存的名称(唯一,不可重复)
     * @param  mutexName        const string& 类型,共享内存的互斥量名称
     * @param  dataSize         int类型 共享内存的空间大小,请提前预估大小   
     ******************************************/
    ShareMemConsumer(const std::string &memoryName, const std::string &mutexName, int dataSize);

    /******************************************
     * @brief Destroy the ShareMemConsumer object
     * 析构函数
     ******************************************/
    virtual ~ShareMemConsumer();

    /******************************************
     * @brief 不带时间戳的write方法
     * 向共享内存中写入字符串数据,不带时间戳
     * @param  data             const string &类型,写入的数据
     ******************************************/
    void write(const std::string & data);

    /******************************************
     * @brief 带时间戳的write方法
     * 向共享内存中写入字符串数据,消费者可以根据时间戳决定是否要更新数据
     * @param  data             const string &类型,写入的数据
     * @param  timeStamp        time_t &类型,写入数据的时间戳
     ******************************************/
    void write(const std::string & data, const time_t & timeStamp);

    /******************************************
     * @brief 读取共享内存数据,和不带时间戳的write配对使用
     * 读取共享内存中的数据,并由data参数返回
     * @param  data             string &类型,用于存放读取的数据         
     ******************************************/
    void read(std::string & data);

    /******************************************
     * @brief 读取共享内存数据,和带时间戳的write配对使用
     * 读取共享内存中的数据,并由data参数返回
     * @param  data             string &类型,用于存放读取的数据 
     * @param  timeStamp        time_t &类型 时间戳,用于判定是否更新,所以为上一次的时间戳
     ******************************************/
    void read(std::string & data, time_t & timeStamp);

private:
    std::string memory_name_;
    std::string mutex_name_;
    int data_size_;

    char* pData = nullptr;              //数据区指针
    HANDLE  hFileMap = nullptr;         //文件映射句柄
    HANDLE hMutex_;                     //互斥量句柄
};

#endif //SHAREMEMCONSUMER_H

  消费者ShareMemConsumer类的定义如下:
  ShareMemConsumer.cpp:

#include "ShareMemConsumer.h"

ShareMemConsumer::ShareMemConsumer(const std::string &memoryName, const std::string &mutexName, int dataSize)
                : memory_name_(memoryName), mutex_name_(mutexName), data_size_(dataSize) {
    //根据生产者的创建的互斥量创建句柄
    while (true){
        hMutex_ = OpenMutex(MUTEX_ALL_ACCESS, FALSE, mutex_name_.c_str());
        if (nullptr != hMutex_)
            break;
        Sleep(200);
    }
    //如果获取成功,创建文件映射句柄
    hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, memory_name_.c_str());
    //如果创建文件映射句柄成功,创建数据区指针
    if (nullptr != hFileMap)
        pData = (char*)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0,  0, data_size_);

}

ShareMemConsumer::~ShareMemConsumer() {
    if(nullptr != hMutex_)
    {
        CloseHandle(hMutex_);
        ReleaseMutex(hMutex_);
        std::cout<<"m_hMutex被关闭"<<std::endl;
    }

    if (nullptr != hFileMap){
        UnmapViewOfFile(pData);
        CloseHandle(hFileMap);
        ReleaseMutex(hFileMap);
        hFileMap = nullptr;
        pData = nullptr;
    }
}

void ShareMemConsumer::write(const std::string &data) {
    try{
        if (pData != nullptr){
            WaitForSingleObject(hMutex_, INFINITE);
            long long len=data.size();
            std::string temp="$$" + std::to_string(len) + "$$" + data;   //使用 $$数据长度$$ 作为数据头
            memcpy(pData, temp.c_str(), temp.size());
            ReleaseMutex(hMutex_);
        }
    }catch (std::exception &e){
        std::cout << "Write Share Memory data Error!" << std::endl;
        std::cout << e.what() << std::endl;
    }
}

void ShareMemConsumer::write(const std::string & data, const time_t & timeStamp){
    try{
        if (pData != nullptr){
            WaitForSingleObject(hMutex_, INFINITE);
            long long len=data.size();
            std::string temp="$$" + std::to_string(len) + "," + std::to_string(timeStamp) + "$$" + data;   //使用 $$数据长度,时间戳$$ 作为数据
            memcpy(pData, temp.c_str(), temp.size());
            ReleaseMutex(hMutex_);
        }
    }catch (std::exception &e){
        std::cout << "Write Share Memory data Error!" << std::endl;
        std::cout << e.what() << std::endl;
    }
}

void ShareMemConsumer::read(std::string &data){
    data.clear();                                   //先清空数据
    const int HEADLEN = 40;
    try{
        if (pData != nullptr){
            /************************************************************
             * step1:首次读共享内存的数据头,查看数据是否为空,不为空则获取数据长度
             ***********************************************************/
            std::string headData="";                //存放数据头
            long long len = 0;                      //存放长度
            int pos = 0;                            //用于存放查找位置
            char *strHead = new char[HEADLEN];      //临时存储字符

            WaitForSingleObject(hMutex_, INFINITE);
            memcpy(strHead, pData, HEADLEN);
            for (size_t i = 0; i < HEADLEN; ++i) 
                data.push_back(strHead[i]);
            delete [] strHead;

            //解析数据头
            if(headData.substr(0,2)=="$$"){         //如果数据是以"$$"开始
                pos = headData.find("$$",2);        //查找数据头末尾的"$$"
                len = std::stoll(data.substr(2,pos-2)); //数据头只有长度
                /************************************************************
                 * step2:如果长度不为0,则第二次获取数据
                 ***********************************************************/
                if (len > 0){
                    char *strTemp = new char[len];
                    memcpy(strTemp, pData+pos+2, len);  //从数据的第一个位置开始读取
                    ReleaseMutex(hMutex_);
                    for (long long i = 0; i < len; ++i)
                        data.push_back(strTemp[i]);
                    delete [] strTemp;
                }
                else
                    ReleaseMutex(hMutex_);              //如果没有数据直接释放锁
            }
            else
                ReleaseMutex(hMutex_);                  //如果不是以$$开始,则直接释放锁

        }
    }catch (std::exception &e){
        std::cout << "Read Share Memory data Error!" << std::endl;
        std::cout << e.what() << std::endl;
    }
}

void ShareMemConsumer::read(std::string & data, time_t & timeStamp){
    data.clear();                                   //先清空数据
    const int HEADLEN = 40;
    try{
        if (pData != nullptr){
            /************************************************************
             * step1:首次读共享内存的头,获取是否为空,不为空则获取数据长度
             ***********************************************************/
            std::string  headData="";
            long long len = 0;
            int pos = 0, splitPos=0;                //pos用于存放查找位置,splitPos用于存放头部分隔符的位置
            time_t now;                             //保存数据的时间戳
            char *strHead = new char[HEADLEN];

           WaitForSingleObject(hMutex_, INFINITE);
            memcpy(strHead, pData, HEADLEN);
            for (size_t i = 0; i < HEADLEN; ++i) 
                headData.push_back(strHead[i]);
            delete [] strHead;

            //解析数据头
            if(headData.substr(0,2)=="$$"){         //如果数据是以"$$"开始
                pos = headData.find("$$",2);        //查找数据头末尾的"$$"
                headData=headData.substr(2,pos-2);  //获取数据头
                splitPos = headData.find(",");      //查找分隔符位置,并获取长度和时间戳
                len = std::stoll(headData.substr(0, splitPos));
                now = std::stoll(headData.substr(splitPos + 1));
                /************************************************************
                 * step2:如果长度不为0,且时间戳和上次不同则第二次获取数据
                 ***********************************************************/
                if (len > 0 && now != timeStamp){
                    char *strTemp = new char[len];
                    memcpy(strTemp, pData+pos+2, len);
                    ReleaseMutex(hMutex_);
                    timeStamp = now;                //更新时间戳并获取数据
                    for (long long i = 0; i < len; ++i)
                        data.push_back(strTemp[i]);
                    delete [] strTemp;
                }
                else
                    ReleaseMutex(hMutex_);              //如果没有数据直接释放锁
            }
            else
                ReleaseMutex(hMutex_);                  //如果不是以$$开始,则直接释放锁

        }
    }catch (std::exception &e){
        std::cout << "Read Share Memory data Error!" << std::endl;
        std::cout << e.what() << std::endl;
    }
}

共享内存类的测试

生产者进程

  生产者进程provider.cpp如下所示:

#include "ShareMemProvider.h"

#include <iostream>
#include <ctime>

using namespace std;
const string shareMemName = "Name";
const string mutexName = "MutexName";
const int size = 300;
//写入的内容,这里只测试带时间戳的方法,另一个类似
const string content = "If you were a teardrop; In my eye, For fear of losing you, I would never cry. "
                 "And if the golden sun, Should cease to shine its light, Just one smile from you, Would make my whole world bright.";
void Provider(){
    ShareMemProvider shareMemProvider(shareMemName, mutexName, size);
    time_t timeStamp;
    int i = 1;
    while(true){
        timeStamp = std::time(nullptr);
        string temp = to_string(i) + ": " + content;
        shareMemProvider.write(temp, timeStamp);
        cout << "Write once ( i = " << i++ << " )" << endl;
        Sleep(1000);
    }
}

int main(){
    Provider();
    return 0;
}

  写入进程的编译即部分运行结果如下所示:


>> g++ .\provider.cpp .\ShareMemProvider.cpp -o provider.exe
>> .\provider.exe

Write once ( i = 1 )
Write once ( i = 2 )
Write once ( i = 3 )
Write once ( i = 4 )
Write once ( i = 5 )
Write once ( i = 6 )
Write once ( i = 7 )
Write once ( i = 8 )
Write once ( i = 9 )
Write once ( i = 16 )
Write once ( i = 17 )
Write once ( i = 18 )
Write once ( i = 19 )
Write once ( i = 20 )
Write once ( i = 21 )
Write once ( i = 22 )
Write once ( i = 23 )
Write once ( i = 24 )
Write once ( i = 25 )
Write once ( i = 26 )
Write once ( i = 27 )
Write once ( i = 28 )
Write once ( i = 29 )
Write once ( i = 30 )
...

消费者进程

  消费者进程consumer.cpp如下所示:

#include "ShareMemConsumer.h"

#include <iostream>
#include <ctime>

using namespace std;
const string shareMemName = "Name";
const string mutexName = "MutexName";
const int size = 300;

void Consumer(){
    ShareMemConsumer shareMeMConsumer(shareMemName, mutexName, size);
    time_t timeStamp = 0;
    string str;
    while(true){
        shareMeMConsumer.read(str, timeStamp);
        cout <<"Read once : "<< str << endl;
        Sleep(1000);
    }
}

int main(){
    Consumer();
    return 0;
}

  读取进程的编译及部分运行结果如下(因为消费者启动滞后,所以实际读取从第18帧开始):


>> g++ .\consumer.cpp .\ShareMemConsumer.cpp -o consumer.exe  
>> .\consumer.exe

Read once : 18: If you were a teardrop; In my eye, For fear of losing you, I would never cry. And if the golden sun, Should cease to shine its light, Just one smile from you, Would make my whole world bright.
Read once : 19: If you were a teardrop; In my eye, For fear of losing you, I would never cry. And if the golden sun, Should cease to shine its light, Just one smile from you, Would make my whole world bright.
Read once : 20: If you were a teardrop; In my eye, For fear of losing you, I would never cry. And if the golden sun, Should cease to shine its light, Just one smile from you, Would make my whole world bright.
Read once : 21: If you were a teardrop; In my eye, For fear of losing you, I would never cry. And if the golden sun, Should cease to shine its light, Just one smile from you, Would make my whole world bright.
Read once : 22: If you were a teardrop; In my eye, For fear of losing you, I would never cry. And if the golden sun, Should cease to shine its light, Just one smile from you, Would make my whole world bright.
Read once : 23: If you were a teardrop; In my eye, For fear of losing you, I would never cry. And if the golden sun, Should cease to shine its light, Just one smile from you, Would make my whole world bright.
...