Linux:利用System V系列的-共享内存,消息队列实现进程间通信

对于管道的进程间通信方式,需要频繁的调用系统调用(read,write)。而我们今天首先要介绍的共享内存,在开辟好空间之后,便可以跳过系统调用,直接进行读写操作。

一.System V共享内存(主要)

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

所以,共享内存就像我们malloc的一块空间一样,指出地址即可直接访问,不需要使用任何的系统调用:

1.1几个主要的共享内存函数

ftok函数

cpp 复制代码
key_t ftok(const char *pathname, int proj_id)

参数:
pathname:一个已存在的文件路径。ftok 会使用该文件的 inode 号和设备号来生成键值。
proj_id:一个 8 位的项目标识符(通常是一个字符,范围是 0 到 255)。用于在同一文件路径下生成不同的键值。

返回值:
成功时返回生成的键值(key_t 类型)。
失败时返回 -1,并设置 errno 为相应的错误代码。

注意事项
文件存在:pathname 必须指向一个已存在的文件,否则 ftok 会失败。
唯一性:不同的 pathname 和 proj_id 组合通常会生成不同的键值,但在某些情况下(如文件系统更改)可能会产生冲突。
可移植性:ftok 生成的键值在不同系统上可能不同,因此在跨平台应用中使用时需谨慎。

shmget函数

cpp 复制代码
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。
取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出
错返回。
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmat函数

cpp 复制代码
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

说明:

cpp 复制代码
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。
公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

cpp 复制代码
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

cpp 复制代码
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

cmd:

cpp 复制代码
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID:删除共享内存段

1.2实现一个基于共享内存的进程间通信

主体思路:共享内存虽然可以快速写入和读取数据,但这也是它的缺点。无法确保数据同步,可能会出现客户端在写,还没写完服务端就读取的情况。我们这里则用命名管道,当客户端写完消息后,发送消息给服务端,告知数据写入完毕再让服务端进行读取,也就达到了我们数据同步的目的。

实现命名管道的文件Fifo.hpp:

cpp 复制代码
#pragma once
#include "comm.hpp"

#define PATH "."
#define FILENAME "fifo"



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

private:
    std::string _path;
    std::string _name;
    std::string _fifoname;
};

class FileOper
{
public:
    FileOper(const std::string &path, const std::string &name)
        : _path(path), _name(name), _fd(-1)
    {
        _fifoname = _path + "/" + _name;
    }
    void OpenForRead()
    {
        // 打开, write 方没有执行open的时候,read方,就要在open内部进行阻塞
        // 直到有人把管道文件打开了,open才会返回!
        _fd = open(_fifoname.c_str(), O_RDONLY);
        if (_fd < 0)
        {
            ERR_EXIT("open");
        }
        std::cout << "open fifo success" << std::endl;
    }
    void OpenForWrite()
    {
        // write
        _fd = open(_fifoname.c_str(), O_WRONLY);
        if (_fd < 0)
        {
            ERR_EXIT("open");
        }
        std::cout << "open fifo success" << std::endl;
    }
    void Write()
    {
        write(_fd, message.c_str(), message.size());
    }
    void Read()
    {
        char buffer[1024];
        int number = read(_fd, buffer, sizeof(buffer) - 1);
        if (number > 0)
        {
            buffer[number] = 0;
            std::cout << "Client Say# " << buffer << std::endl;
        }
        else if (number == 0)
        {
            std::cout << "client quit! me too!" << std::endl;
        }
        else
        {
            std::cerr << "read error" << std::endl;
        }
    }
    void Close()
    {
        if (_fd > 0)
            close(_fd);
    }
    ~FileOper()
    {
    }

private:
    std::string _path;
    std::string _name;
    std::string _fifoname;
    int _fd;
};

实现共享内存接口的文件shm.hpp:

cpp 复制代码
#include "comm.hpp"

const int gdefaultid = -1;
const int gsize = 4096;
const std::string pathname = ".";
const int projid = 0x64;
const int gmod = 0666;

const std::string Creater = "Creater";
const std::string User = "User";


class Shm
{
    void CreatHelper(int flag)
    {
        key_t k = ftok(pathname.c_str(),projid);
        if(k < 0)
        {
            ERR_EXIT("ftok");
        }
        _key = k;
        _shmid = shmget(k,_size,flag);
        if(_shmid < 0)
        {
            ERR_EXIT("shmget");
        }
        std::cout << "shmid:" << _shmid << std::endl;
    }

    void Attach()
    {
        _start_mem = shmat(_shmid,nullptr,0);
        if((long long)_start_mem < 0)
        {
            ERR_EXIT("shmat");
        }
        printf("attach success\n");
    }
    void Creat()
    {
        CreatHelper(IPC_CREAT | IPC_EXCL | gmod);
    }

    void Detach()
    {
        int n = shmdt((char*)_start_mem);
        if(n == 0)
        printf("detach success\n");
    }

    void Get()
    {
        CreatHelper(IPC_CREAT);
    }
    
    void DestoryShm()
    {
        Detach();
        int n = shmctl(_shmid,IPC_RMID,nullptr);
        if(n == 0)
        {
            printf("delete mem success\n");
        }
        else
        {
            ERR_EXIT("shmctl");
        }
    }
public:
    Shm(const std::string& username)
        :_shmid(gdefaultid)
        ,_size(gsize)
        ,_username(username)
    {
        if(_username == Creater)
        {
            Creat();
        }
        else Get();
        Attach();
    }

    void* VirtualAddr()
    {
        //printf("VirtalAddr: %p\n",_start_mem);
        return _start_mem;
    }


    ~Shm()
    {
        Detach();
        if(_username == Creater)
        {
            DestoryShm();
        }
    }
private:
    int _shmid;
    key_t _key;
    int _size;
    std::string _username;
    void* _start_mem;
};

二者的公共部分comm.hpp

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define ERR_EXIT(m)         \
    do                      \
    {                       \
        perror(m);          \
        exit(EXIT_FAILURE); \
    } while (0)

服务端server.cc:

cpp 复制代码
#include "Shm.hpp"
#include "Fifo.hpp"
int main()
{
    Shm _sh(Creater);
    NamedFifo fifo(PATH,FILENAME);
    FileOper o_fifo(PATH,FILENAME);
    o_fifo.OpenForRead();
    o_fifo.Read();
    std::cout << (char*)_sh.VirtualAddr() << std::endl;
    sleep(10);
    o_fifo.Close();
    return 0;
}

客户端client.cc:

cpp 复制代码
#include "Shm.hpp"
#include "Fifo.hpp"
int main()
{
    Shm _sh(User);
    FileOper o_fifo(PATH,FILENAME);
    o_fifo.OpenForWrite();
    const char* str = "这是向服务端发送的一条消息:I am a process.";
    char* mem = (char*)_sh.VirtualAddr();
    int i = 0;
    for(;str[i];i++)
    {
        mem[i] = str[i];
    }
    mem[i] = 0;
    o_fifo.Write();
    sleep(5);
    return 0;
}

运行后效果如下:

当然也可以自己输入消息让服务端接收,实现自定义消息发送。

二.System V消息队列与信号量(了解)

由于System V这种通信方式我们已经很少使用的问题,所以我们只需要了解共享内存即可,消息队列与信号量实现进程间通信的方式与共享内存几乎一致(只是变了个函数名)。这里我们只给出消息队列版本进程间通信的实现以及相关函数的介绍,信号量读者可自行了解相关函数进行实现。

2.1消息队列的相关主要函数

msgget函数

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);

参数
• key : 某个消息队列的名字
• msgflg :由九个权限标志构成,它们的⽤用法和创建⽂文件时使⽤用的mode模式标志是⼀样的
返回值
• 成功返回一个非负整数,即该消息队列的标识码;失败返回-1

msgctl函数

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msgid : 由msgget 函数返回的消息队列标识码
cmd :将要采取的动作(有三个可取值),分别如下:
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID:删除共享内存段

buf : 属性缓冲区

返回值
成功返回0;失败返回-1

msgsnd函数

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数
msgid : 由msgget 函数返回的消息队列标识码
msgp:是一个指针,指针指向准备发送的消息
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgflg:控制着当前消息队列满或到达系统上限时将要发⽣生的事情, 0即可
( msgflg=IPC_NOWAIT 表⽰示队列满不等待,返回EAGAIN 错误 )。

返回值
成功返回0;失败返回-1

关于消息主体
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
// 以一个long int长整数开始,接收者函数将利⽤用这个长整数确定消息的类型

msgrcv函数

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数
msgid : 由msgget 函数返回的消息队列标识码
msgp :是一个指针,指针指向准备接收的消息
msgsz :是msgp 指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgtype :它可以实现接收消息的类型,也可以模拟优先级的简单形式进行接收
msgflg :控制着队列中没有相应类型的消息可供接收时将要发⽣生的事

返回值
成功返回实际放到接收缓冲区⾥里去的字符个数,失败返回-1

msgflg标志位-了解
msgtype=0返回队列第一条信息
msgtype>0返回队列第一条类型等于msgtype的消息 
msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息,并且是满⾜足条件的消息类型最小
的消息
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
msgtype>0且msgflg=MSG_EXCEPT,接收6 类型不等于msgtype的第一条消息

2.2消息队列实现进程间通信

MsgQueue.hpp

cpp 复制代码
#ifndef MSGQUEUE_HPP
#define MSGQUEUE_HPP
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cstring>

const int default_msgid = -1;
const int MSG_FLAG_SERVER = IPC_CREAT | IPC_EXCL | 0666;
const int MSG_FLAG_CLIENT = IPC_CREAT;
const int MSG_SIZE = 1024;

#define PATHNAME "/home/ly/My_Linux_Code/lesson17/MsgQueen"
#define MSG_TYPE_Server 1
#define MSG_TYPE_Client 2
#define project_id 1234

class MsgQueue {
    struct msgbuf {
        long mtype;
        char mtext[MSG_SIZE];
    };
public:
    MsgQueue(int msgid = default_msgid)
        : _msgid(msgid) {}
    void Creat(int _msgflg)
    {
        key_t key = ftok(PATHNAME, project_id);
        if(key == -1)
        {
            std::cerr << "ftok error" << std::endl;
            exit(1);
        }
        _msgid = msgget(key, _msgflg);
        if(_msgid == -1)
        {
            std::cerr << "msgget error" << std::endl;
            exit(2);
        }
        std::cout << "msgget create success: " << _msgid << std::endl;
    }

    void send(int type, std::string& text)
    {
        struct msgbuf msg;
        memset(&msg, 0, sizeof(msg));
        msg.mtype = type;
        memcpy(msg.mtext, text.c_str(), text.size());
        int n = msgsnd(_msgid, &msg, MSG_SIZE, 0);
        if(n == -1)
        {
            std::cerr << "msgsnd error" << std::endl;
            exit(4);
        }
    }

    void recv(int type, std::string& text)
    {
        struct msgbuf msg;
        memset(&msg, 0, sizeof(msg));
        int n = msgrcv(_msgid, &msg, MSG_SIZE, type, 0);
        if(n == -1)
        {
            std::cerr << "msgrcv error" << std::endl;
            exit(5);
        }
        text = msg.mtext;
        text[n] = '\0';
    }

    void Destroy()
    {
        int n = msgctl(_msgid, IPC_RMID, 0);
        if(n == -1)
        {
            std::cerr << "msgqueue destroy error" << std::endl;
            exit(3);
        }
    }

    ~MsgQueue() {}   
private:
    int _msgid;
};

class server : public MsgQueue {
public:
    server() {
        MsgQueue::Creat(MSG_FLAG_SERVER);
    }
    ~server() {
        MsgQueue::Destroy();
    }
};

class client : public MsgQueue {
public:
    client() {
        MsgQueue::Creat(MSG_FLAG_CLIENT);
    }
    ~client() {}
};
#endif

客户端代码client.cc

cpp 复制代码
#include "MsgQueue.hpp"

int main() {
    client c;
    std::string msg;
    while (true) {
        std::cin >> msg;
        c.send(MSG_TYPE_Client, msg);
        if (msg == "exit") { 
            break;
        }
    }
    return 0;
}

服务端代码server.cc

cpp 复制代码
#include "MsgQueue.hpp"
#include "ChainOfRespons.hpp"

int main() {
    server s;
    std::string text;
    HandlerEntry he;
    while(true)
    {
        s.recv(MSG_TYPE_Client, text);
        std::cout << "Client says: " << text << std::endl;
        if(text == "exit")
        {
            break;
        }
        //责任链模式处理数据
        //he.Handle(text);
    }
    return 0;
}
相关推荐
海绵波波10711 分钟前
【部署】ubuntu部署olmOCR
linux·运维·ubuntu
自由鬼13 分钟前
OpenAI定义的Agent新范式如何构建自动化系统
运维·ai·自动化·agent
纪伊路上盛名在16 分钟前
vscode中修改快捷键
linux·ide·vscode·编辑器
自律的阿龙34 分钟前
Linux练级宝典->多线程
linux·运维·服务器
酷酷的崽79843 分钟前
如何在AVL树中高效插入并保持平衡:一步步掌握旋转与平衡因子 —— 平衡因子以及AVL结构篇
c语言·数据结构·c++
TravisBytes44 分钟前
在 VMware 中安装 Ubuntu 的超详细实战分享
linux·运维·ubuntu
小米先森1 小时前
Ubuntu “文件系统根目录”上的磁盘空间不足
linux·ubuntu
珹洺1 小时前
计算机网络:(一)详细讲解互联网概述与组成 (附带图谱更好对比理解)
服务器·开发语言·网络·数据库·后端·计算机网络·php
Tipriest_1 小时前
打包当前Ubuntu镜像 制作Ubuntu togo系统
linux·运维·ubuntu·ubuntu to go
阿巴~阿巴~1 小时前
蓝桥杯刷题——第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
c语言·c++·蓝桥杯