进程间通信(2)——共享内存

System V 共享内存(System V Shared Memory, SHM)是一种进程间通信(IPC)机制,允许多个进程共享一块内存区域,从而实现高效的数据交换。共享内存是最快的 IPC 方式之一,因为数据直接在内存中读写,而无需经过内核进行复制。

1.system V共享内存

1.1.共享内存的基本概念

System V 共享内存基于键(key)标识,并使用 shmget 创建或获取共享内存段。创建的共享内存可以被多个进程映射到自己的地址空间,实现数据共享。

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

1.2.共享内存函数

在 System V 共享内存(SHM)中,主要涉及以下几个系统调用:

1.2.1.shmget - 获取共享内存标识符

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

参数:
key:共享内存的键值,可使用 ftok 生成,也可直接指定一个整数。
size:共享内存的大小(单位:字节)。如果 shmget 需要创建新的共享内存区域,
则必须指定 size,否则可以为 0。
shmflg:权限标志,通常设置为 0666 | IPC_CREAT,表示创建一个可读写的共享内存:
    0666:文件权限,表示所有用户可读写。
    IPC_CREAT:如果共享内存不存在,则创建新共享内存。
    IPC_EXCL:与 IPC_CREAT 组合使用,确保共享内存不存在,否则 shmget 失败并返回 -1。

    返回值:

    成功:返回共享内存 ID(shmid)。

    失败:返回 -1,可使用 perror("shmget") 查看错误原因。

    示例:

    key_t key = ftok("shmfile", 65);  // 生成 key
    int shmid = shmget(key, 1024, 0666 | IPC_CREAT);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }
    

    1.2.2.shmat - 将共享内存映射到进程地址空间

    void *shmat(int shmid, const void *shmaddr, int shmflg);
    
    参数:
    shmid:共享内存 ID,由 shmget 返回。
    shmaddr:建议映射地址,一般设为 NULL 让系统自动分配合适的地址。
    shmflg:访问模式:
      0:可读可写。
      SHM_RDONLY:只读模式。

      返回值:

      成功:返回共享内存地址(指向 void*)。

      失败:返回 (void*) -1,可用 perror("shmat") 检查错误原因。

      1.2.3.shmdt - 解除共享内存映射

      int shmdt(const void *shmaddr);
      
      参数:
      shmaddr:共享内存的起始地址,即 shmat 返回的指针。

      返回值:

      成功:返回 0。

      失败:返回 -1,可用 perror("shmdt") 查看错误原因。

      示例:

      if (shmdt(data) == -1) {
          perror("shmdt");
          exit(EXIT_FAILURE);
      }
      

      共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除。

      共享内存生命周期随内核,只要不删除,就一直存在于内核中,除非重启系统。

      1.2.4.shmctl - 控制共享内存

      int shmctl(int shmid, int cmd, struct shmid_ds *buf);
      
      参数:
      shmid:共享内存 ID,由 shmget 返回。
      cmd:控制命令
      buf:shmid_ds 结构体指针,用于存储共享内存信息或修改参数(IPC_RMID 时可设为 NULL)。

      cmd 控制命令:

        IPC_STAT:获取共享内存的状态信息,存储到 buf 指向的 struct shmid_ds 结构体中。
        IPC_SET:根据 buf 中的值修改共享内存的权限(如所有者、权限位),仅允许所有者或 root 操作。
        IPC_RMID:删除共享内存段(标记为待删除,当最后一个进程 detach 后实际销毁)。
        其他命令(如 SHM_LOCK 锁定内存不被换出,需 root 权限)。

          返回值:

          成功:返回 0。

          失败:返回 -1,可用 perror("shmctl") 查看错误原因。

          struct shmid_ds {
              struct ipc_perm  shm_perm;   // 共享内存的权限信息(关键)
              size_t           shm_segsz;  // 共享内存段的大小(字节)
              time_t           shm_atime;  // 最后一次附加(shmat)的时间
              time_t           shm_dtime;  // 最后一次分离(shmdt)的时间
              time_t           shm_ctime;  // 最后一次修改的时间(如权限变更)
              pid_t            shm_cpid;   // 创建共享内存的进程 PID
              pid_t            shm_lpid;   // 最后一次操作附加/分离操作的进程 PID
              shmatt_t         shm_nattch; // 当前附加到该共享内存的进程数
              // 可能存在其他系统相关字段(如 Linux 中的 __pad1、__pad2 等对齐字段)
          };

          1.2.5.ftok - 生成唯一键值(可选)

          key_t ftok(const char *pathname, int proj_id);
          
          参数:
          pathname:一个有效路径(如 "/tmp/shmfile"),该文件必须存在,
          并且pathname必须是时C风格的字符串。
          proj_id:项目 ID,范围 0~255,通常设定为固定值。
          

          返回值:

          成功:返回 key_t 类型的键值。

          失败:返回 -1,可用 perror("ftok") 查看错误原因。

          示例:

          key_t key = ftok("/tmp/shmfile", 65);
          if (key == -1) {
              perror("ftok");
              exit(EXIT_FAILURE);
          }
          

          共享内存的使用步骤

           - 创建或获取共享内存:使用 shmget() 创建或获取共享内存 ID。
           - 映射到进程地址空间:使用 shmat() 获取共享内存指针。
           - 进程间通信:使用共享内存进行数据读写。
           - 解除映射:使用 shmdt() 解除共享内存映射。
           - 删除共享内存(可选):使用 shmctl() 删除共享内存,防止残留。

            共享内存是将同一块物理内存映射到各个进程虚拟地址空间,可以直接通过虚拟地址访问,相较于其它方式少了两步内核态与用户态之间的数据拷贝因此速度最快。

              2.IPC命令

                2.1.ipcs - 显示 IPC 资源信息

                ipcs 命令用于查看当前系统中的 IPC 资源,包括共享内存、消息队列和信号量。

                 基本语法

                ipcs [选项]
                

                常用选项

                -m:显示共享内存(shm)。
                -q:显示消息队列(msg)。
                -s:显示信号量(sem)。
                -a:显示所有 IPC 资源(默认)。
                -c:显示创建者信息。
                -p:显示进程信息(如创建者和最后访问进程)。
                -t:显示时间信息(创建、上次连接、最近访问)。

                显示共享内存信息

                  显示消息队列信息

                  2.2.ipcrm - 删除 IPC 资源

                  ipcrm 命令用于删除共享内存、消息队列和信号量。

                  基本语法

                  ipcrm [资源类型] [ID]
                  

                  常用选项

                  //必须明确指定要删除的资源类型
                  -m shmid:删除共享内存(shmid 为 ipcs -m 查询到的 ID)。
                  -q msqid:删除消息队列。
                  -s semid:删除信号量。
                  --all=shm:删除所有共享内存。
                  --all=msg:删除所有消息队列。
                  --all=sem:删除所有信号量。

                  共享内存的生命周期遵循引用计数规则,ipcrm -m ID 只是标记删除(类似shmctl(IPC_RMID))

                  共享内存只有在当前映射连接数为0时才会被删除释放

                  3.system V消息队列

                  System V 消息队列也是一种进程间通信(IPC)机制,允许不同进程通过消息传递的方式进行数据交换。它与管道(Pipe)相比,具有更灵活的特性,例如消息可以有类型,消息队列不会因进程终止而消失,适用于复杂的进程通信需求。

                  3.1.基本概念

                  消息队列是内核提供的一种先进先出的消息存储结构,不同进程可以向队列中写入消息或从队列中读取消息。每条消息都有一个类型标识,进程可以按顺序读取消息,也可以根据类型选择性读取。

                  3.2.相关系统调用

                  System V 消息队列主要通过 msgget、msgsnd、msgrcv 和 msgctl 进行操作。

                  四个函数都包含以下头文件

                  #include <sys/types.h>
                  #include <sys/ipc.h>
                  #include <sys/msg.h>

                  (1)创建或获取消息队列 - msgget

                  int msgget(key_t key, int msgflg);
                  
                  key_t key:消息队列的唯一标识,进程可以通过相同的 key 访问同一个消息队列。
                  int msgflg:消息队列的标志位,控制创建和权限设置。
                  IPC_CREAT:如果消息队列不存在,则创建它;如果已存在,则直接返回消息队列 ID。
                  IPC_EXCL(与 IPC_CREAT 组合使用):如果队列已存在,则返回 -1 并设置 errno 为 EEXIST,防止已有队列被重复打开。
                  权限位(0666、0600 等):类似文件权限,控制谁可以访问消息队列。
                  成功:返回消息队列的 ID(非负整数)。
                  失败:返回 -1,并设置 errno 说明错误原因。

                  (2)发送消息 - msgsnd

                  int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
                  
                  int msqid:消息队列的 ID,由 msgget 获取。
                  const void *msgp:指向要发送的消息结构体的指针。
                  size_t msgsz:消息正文(不包括消息类型)的大小,单位是字节。
                  int msgflg:消息发送选项:
                  IPC_NOWAIT:如果队列已满,则立即返回 -1,不阻塞进程。
                  0(默认):如果队列已满,则阻塞进程,直到有空间可用。
                  

                  消息结构通常定义如下: 

                    struct msgbuf {
                        long mtype;     // 消息类型,必须大于 0
                        char mtext[1];  // 消息正文
                    };
                    

                    (3)接收消息 - msgrcv

                    int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtype, int msgflg);
                    
                    msgp:指向存放接收消息的缓冲区的指针,通常是一个自定义结构体,
                    缓冲区需包含一个 消息类型字段(long 类型)和 消息数据
                    msgsz:要接收的消息正文部分的大小(不包括 mtype)。
                    msgtype:
                    大于 0:接收队列中消息类型等于 mtype 的第一条消息。
                    等于 0:接收队列中的第一条消息(无论其类型为何)。
                    小于 0:接收队列中消息类型值最小且不超过 |mtype| 的消息。
                    msgflg:
                    IPC_NOWAIT:如果队列为空,立即返回 -1,并设置 errno 为 ENOMSG。
                    MSG_NOERROR:如果消息正文长度大于 msgsz,则截断多余部分(否则会出错)。

                    msgtyp 是带符号的 long 类型,因此能通过其值的符号(正 / 负)和大小来灵活筛选消息类型,这是消息队列机制中实现 “按类型接收” 的核心设计。

                    返回值:

                    成功时,返回接收到的消息正文长度(不含 mtype)。

                    失败时,返回 -1,并设置 errno 以指示错误。

                    (4)控制消息队列 - msgctl

                    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
                    

                    cmd:要执行的操作,常见的有:

                    IPC_RMID:删除消息队列。
                    IPC_STAT:获取消息队列的状态信息。
                    IPC_SET:修改消息队列的状态信息。

                    struct msqid_ds *buf:用于存储或修改消息队列的属性。

                      struct msqid_ds {
                          struct ipc_perm msg_perm; // 访问权限
                          time_t msg_stime;         // 最后发送时间
                          time_t msg_rtime;         // 最后接收时间
                          time_t msg_ctime;         // 最后修改时间
                          unsigned long msg_cbytes; // 当前消息队列字节数
                          msgqnum_t msg_qnum;       // 当前消息数量
                          msglen_t msg_qbytes;      // 队列的最大字节数
                          pid_t msg_lspid;          // 最后发送消息的进程 ID
                          pid_t msg_lrpid;          // 最后接收消息的进程 ID
                      };
                      

                      4.system V信号量

                      4.1.并发编程概念铺垫

                      多个执行流(进程)能看到的同一份公共资源称为共享资源。被保护起来的共享资源叫做临界资源或互斥资源,一次只允许一个进程使用。

                      保护的方式常见的有互斥与同步。任何时刻,只允许一个执行流访问资源,称为互斥。多个执行流访问临界资源时,具有一定的顺序性,称为同步。

                      在进程中涉及到互斥资源的程序段叫做临界区。代码可以分为访问临界资源的代码(临界区)和不访问临界资源的代码(非临界区)。

                      所谓的对共享资源进行保护,本质是对访问共享资源的代码进行保护。


                       

                      锁本身也是共享的,因此必须保证锁的安全性。申请锁时必须是原子的,即要么成功申请,要么完全失败,不会出现中间状态。原子性的特点是要么做,要么不做

                      在 IPC 资源管理方面,System V IPC 资源不会自动清除,必须手动删除,否则会一直存在,除非系统重启,因为 System V IPC 资源的生命周期随内核

                      从理解角度来看,信号量是一个计数器,用来表明临界资源中,资源的多少

                      从作用方面来看,信号量用于保护临界区,确保多个进程或线程访问共享资源时不发生冲突。

                      从本质方面来看,申请信号量的本质是对资源的预定机制

                      在操作方面,申请资源时,计数器减一(P 操作),释放资源时,计数器加一(V 操作)。

                       - P 操作(等待):如果信号量值大于 0,则减 1;如果等于 0,则阻塞进程。
                       - V 操作(释放):将信号量值加 1,并唤醒等待的进程(如果有)。

                      信号量只有1和0两态的信号量,叫做二元信号量,这也是互斥。有多个信号量时叫多元信号量。

                      4.2.信号量

                      System V 信号量是一种进程间通信(IPC)机制,主要用于进程间同步。与互斥锁不同,信号量不仅可以用于互斥访问共享资源,还可以用于控制多个进程对资源的访问数量。

                      想申请临界资源,先申请信号量,对信号量计数器做--操作,申请成功访问临界资源,否则进程阻塞挂起。

                      信号量和通信的关系:

                      1. 先访问信号量P,多个进程得看到同一个信号量。

                      2. 不是传递数据才叫做进程间通信(IPC),通知、同步、互斥也算。

                      4.3.System V 信号量相关 API

                      System V 信号量主要通过 semget semctl semop 进行操作。

                      #include <sys/types.h>
                      #include <sys/ipc.h>
                      #include <sys/sem.h>

                      (1)创建或获取信号量集合:semget

                      int semget(key_t key, int nsems, int semflg);
                      
                      nsems:信号量集中的信号量个数,仅在创建新信号量时有效:
                      若 semget 用于创建新信号量集,则 nsems 需要指定信号量的数量。
                      若 semget 仅用于获取已有信号量集,则 nsems 通常设为 0(忽略该参数)。
                      semflg:标志位,控制信号量的创建和访问权限:
                      
                      IPC_CREAT:如果不存在,则创建新的信号量集。
                      IPC_EXCL:与 IPC_CREAT 组合使用,确保信号量集不存在,否则返回错误。
                      0666(或其他权限位):指定读写权限,如 0666 表示所有用户可读写。
                      

                      返回值: 

                      成功:返回信号量集的标识符(非负整数)。

                      失败:返回 -1,并设置 errno 指定错误原因。

                      (2)对信号量进行控制:semctl

                      int semctl(int semid, int semnum, int cmd, ...);
                      
                      semnum:指定操作的信号量编号(从 0 开始)。
                      如果 cmd 作用于整个信号量集(如 IPC_RMID 删除信号量),该值可以忽略。
                      若对单个信号量执行操作(如 SETVAL 设值),则 semnum 指定目标信号量。
                      cmd:操作命令,控制 semctl 的行为,常见命令包括:
                      SETVAL:设置信号量的值(需要提供 union semun)。
                      GETVAL:获取信号量的值(返回信号量值)。
                      IPC_RMID:删除信号量集(释放系统资源)。
                      SETALL:设置整个信号量集的值(需要提供 union semun)。
                      GETALL:获取整个信号量集的值。
                      IPC_STAT:获取信号量信息。
                      IPC_SET:设置信号量权限。

                      semctl 需要一个可选的第四个参数,通常是 union semun,其定义如下:

                        union semun {
                            int val;                // 用于 SETVAL 设置单个信号量值
                            struct semid_ds *buf;   // 用于 IPC_STAT/IPC_SET 获取/设置信号量属性
                            unsigned short *array;  // 用于 GETALL/SETALL 读取/设置整个信号量集
                        };
                        

                        返回值:

                        成功:

                        SETVAL、SETALL、IPC_SET、IPC_RMID 成功时返回 0。

                        GETVAL、GETALL、IPC_STAT 成功时返回对应的值或填充 buf 结构体。

                        失败:返回 -1,并设置 errno 指定错误原因。

                        (3)对信号量进行操作:semop

                        int semop(int semid, struct sembuf *sops, unsigned nsops);
                        
                        sops:指向 sembuf 结构体数组的指针,每个 sembuf 结构体表示对一个信号量的操作。
                        nsops:sops 数组中的操作数量,即一次操作多少个信号量。

                          返回值

                          成功:返回 0。

                          失败:返回 -1,并设置 errno,常见错误如下:

                          struct sembuf {
                              unsigned short sem_num;  // 信号量编号
                              short sem_op;            // 操作值,-1 表示 P 操作,+1 表示 V 操作
                              short sem_flg;           // 操作标志,如 SEM_UNDO
                          };
                          
                          sem_op 取值含义:
                          -1 (P 操作):等待信号量变为正数,然后执行 semval--(即占用资源)。
                          +1 (V 操作):释放信号量,执行 semval++(即释放资源)。
                          0:等待 semval == 0,用于同步(常用于进程等待)。
                          sem_flg 可选标志:
                          IPC_NOWAIT:如果操作不能立即执行,则立即返回(不会阻塞)。
                          SEM_UNDO:进程退出时自动撤销对该信号量的操作(避免死锁)。

                          示例

                          P操作(获取资源)

                          void P(int semid) {
                              struct sembuf sop = {
                                  .sem_num = 0,  // 信号量编号
                                  .sem_op = -1,   // 操作值(获取资源)
                                  .sem_flg = 0    // 通常为0或IPC_NOWAIT
                              };
                          
                              if (semop(semid, &sop, 1) == -1) {
                                  perror("semop P failed");
                                  exit(EXIT_FAILURE);
                              }
                          }

                          V操作(释放资源) 

                          void V(int semid) {
                              struct sembuf sop = {
                                  .sem_num = 0,  // 信号量编号
                                  .sem_op = 1,    // 操作值(释放资源)
                                  .sem_flg = 0    // 通常为0
                              };
                          
                              if (semop(semid, &sop, 1) == -1) {
                                  perror("semop V failed");
                                  exit(EXIT_FAILURE);
                              }
                          }

                          Linux 内核中,共享内存(shm)、消息队列(msg)、信号量(sem)虽功能不同,但都属于 System V IPC 机制,且其内核管理结构体(shmid_ds、msgid_ds、semid_ds)有一个共同的 “头部”——kern_ipc_perm 结构体。shmid、msgid、semid即数组下标。

                          为什么共享内存、消息队列、信号量可以存储在在同一个数组里呢?

                          ipc_id_array 柔性数组存储的并非完整的 IPC 结构体,而是 kern_ipc_perm* 类型的指针(或直接存储结构体起始地址)。

                           - 因为数组里存储的结构体起始位置都是 kern_ipc_perm,也就是 shmctl 中的参数 shmid_ds 中的 ipc_perm,msgid_ds、segid_ds 中同样存在结构体 ipc_perm

                           - 当需要使用特定 IPC 类型时,只需将数组中的 kern_ipc_perm* 指针 强制转换为目标 IPC 结构体指针,即可访问该类型的特有字段。

                          例如 struct msgid_ds *msg_ptr = (struct msgid_ds*)ipc_id_array[0];。这就是C语言下的多态。

                          5. 共享内存实现通信

                          shm.hpp

                          #pragma once
                          
                          #include <string>
                          #include <cstdio>
                          #include <sys/types.h>
                          #include <sys/ipc.h>
                          #include <sys/shm.h>
                          #include <unistd.h>
                          
                          const std::string pathname = ".";
                          const int projid = 0x66;
                          const int gsize = 4096;
                          const int gmode = 0666;
                          #define CREATER "creater"
                          #define USER "user"
                          
                          #define ERR_EXIT(m)         \
                              do                      \       
                              {                       \
                                  perror(m);          \
                                  exit(EXIT_FAILURE); \
                              } while (0)             \
                          
                          class Shm
                          {
                          private:
                              void CreatHelper(int flg)
                              {
                                  //打印共享内存键值
                                  printf("key: 0x%x\n", _key);
                                  //获取共享内存标识符并打印
                                  _shmid = shmget(_key, _size, flg);
                                  if(_shmid < 0) ERR_EXIT("shmget");
                                  printf("shmid: %d\n", _shmid);
                              }
                              void Create()
                              {
                                  CreatHelper(IPC_CREAT | IPC_EXCL | gmode);
                              }
                              void Open()
                              {
                                  CreatHelper(IPC_CREAT);
                              }
                              void Attr()
                              {
                                  _start_mem = shmat(_shmid, nullptr, 0);
                                  if((long long)_start_mem < 0) ERR_EXIT("shmat");
                          
                                  printf("attach success\n");
                              }
                              void Death()
                              {
                                  int n = shmdt(_start_mem);
                                  if(n < 0) ERR_EXIT("shmdt");
                          
                                  printf("deattach success");
                              }
                              void Destory()
                              {
                                  Death();
                                  if (_usertype == CREATER)
                                  {
                                      int n = shmctl(_shmid, IPC_RMID, nullptr);
                                      if (n > 0)
                                          printf("shmctl delete shm: %d success\n", _shmid);
                                      else
                                          ERR_EXIT("shmctl");
                                  }
                              }
                          public:
                              Shm(const std::string &pathname, int projid, std::string usertype)
                              : _pathname(pathname)
                              , _usertype(usertype)
                              , _size(gsize)
                              , _start_mem(nullptr)
                              {
                                  _key = ftok(pathname.c_str(), projid);
                          
                                  if(_usertype == CREATER)
                                  {
                                      Create();
                                  }else if(_usertype == USER)
                                  {
                                      Open();
                                  }
                                  Attr();
                              }
                              int Size()
                              {
                                  return _size;
                              }
                              void *VirtualAddr()
                              {
                                  printf("VirtualAddr: %p\n", _start_mem);
                                  return _start_mem;
                              }
                              void Attr()
                              {
                                  struct shmid_ds ds;
                                  int n = shmctl(_shmid, IPC_STAT, &ds);
                                  printf("segse: %ld\n", ds.shm_segsz);
                                  printf("atime: %ld\n", ds.shm_atime);
                              }
                              ~Shm() 
                              {
                                  Destory();
                              }
                          private:
                              key_t _key;
                              int _size;
                              int _shmid;
                              std::string _pathname;
                              std::string _usertype;
                              void *_start_mem;
                          };

                          Makefile 

                          .PHONY:all
                          all:client server
                          
                          client:: client.cc
                          	g++ -o $@ $^ -std=c++11
                          server:: server.cc
                          	g++ -o $@ $^ -std=c++11
                          
                          .PHONY:clean
                          clean:
                          	rm client server

                          client.cc 

                          #include "shm.hpp"
                          
                          int main()
                          {
                              Shm shm(pathname, projid, USER);
                              char *mem = (char*)shm.VirtualAddr();
                              for(char c = 'A'; c <= 'Z'; c++)
                              {
                                  mem[c-'A'] = c;
                                  sleep(1);
                              }
                              return 0;
                          }

                           server.cc

                          #include "shm.hpp"
                          
                          int main()
                          {
                              Shm shm(pathname, projid, CREATER);
                              
                              char *mem = (char*)shm.VirtualAddr();
                              while (true)
                              {
                                  printf("%s\n", mem);
                                  sleep(1);
                              }
                              
                              return 0;
                          }

                           ./server先运行,等到client运行后开始输出ABC...

                          6. 借助管道实现控制版的共享内存

                          fifo.hpp

                          #pragma once
                          
                          #include <iostream>
                          #include <cstdio>
                          #include <string>
                          #include <iostream>
                          #include <string>
                          #include <sys/types.h>
                          #include <sys/stat.h>
                          #include <fcntl.h>
                          #include <unistd.h>
                          #include "comm.hpp"
                          
                          #define PATH "."
                          #define FIFONAME "fifo"
                          
                          class NameFifo
                          {
                          public:
                              NameFifo(const std::string &path, const std::string &name)
                                  : _path(path), _name(name)
                              {
                                  _fifoname = _path + "/" + _name;
                                  int n = mkfifo(_fifoname.c_str(), 0666);
                                  if (n == -1)
                                  {
                                      std::cerr << "mkfifo error" << std::endl;
                                  }
                                  std::cout << "mkfifo success" << std::endl;
                              }
                              ~NameFifo()
                              {
                                  int n = unlink(_fifoname.c_str());
                                  if (n == 0)
                                  {
                                      std::cout << "remove FIFO_FILE success" << std::endl;
                                  }
                                  else
                                  {
                                      std::cerr << "remove FIFO_FILE failed" << std::endl;
                                  }
                              }
                          
                          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的时候,就要在open内部进行阻塞
                                  _fd = open(_fifoname.c_str(), O_RDONLY);
                                  if (_fd < 0)
                                  {
                                      std::cerr << "open error" << std::endl;
                                      return;
                                  }
                                  std::cout << "open success" << std::endl;
                              }
                              void OpenForWrite()
                              {
                                  _fd = open(_fifoname.c_str(), O_WRONLY);
                                  if (_fd < 0)
                                  {
                                      std::cerr << "open fifo cerr" << std::endl;
                                      return;
                                  }
                                  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 num = read(_fd, &c, 1);
                                  if (num > 0)
                                  {
                                      printf("醒来: %d\n", num);
                                      return true;
                                  }
                                  else
                                      return false;
                              }
                              void Close()
                              {
                                  if (_fd > 0)
                                      close(_fd);
                              }
                              ~Fileoper() {}
                          
                          private:
                              std::string _path;
                              std::string _name;
                              std::string _fifoname;
                              int _fd;
                          };
                          

                          client.cc

                          #include "shm.hpp"
                          #include "Fifo.hpp"
                          
                          int main()
                          {
                              Fileoper writefifo(PATH, FIFONAME);
                              writefifo.OpenForWrite();
                          
                              Shm shm(pathname, projid, USER);
                              char *mem = (char*)shm.VirtualAddr();
                              int index = 0;
                              for(char c = 'A'; c <= 'Z'; c++, index += 2)
                              {
                                  sleep(1);
                                  mem[index] = c;
                                  mem[index + 1] = c;
                                  sleep(1);
                                  mem[index + 2] = 0;
                          
                                  writefifo.WakeUp();
                              }
                              writefifo.Close();
                              return 0;
                          }

                          server.cc

                          #include "shm.hpp"
                          #include "Fifo.hpp"
                          
                          int main()
                          {
                              Shm shm(pathname, projid, CREATER);
                          
                              // 创建管道文件
                              NameFifo fifo(PATH, FIFONAME);
                          
                              // 文件操作
                              Fileoper readfile(PATH, FIFONAME);
                              readfile.OpenForRead();
                              
                              char *mem = (char *)shm.VirtualAddr();
                              while (true)
                              {
                                  if(readfile.Wait())
                                  {
                                      printf("%s\n", mem);
                                      sleep(1);
                                  }
                                  else break;
                              }
                              readfile.Close();
                              return 0;
                          }

                          评论
                          添加红包

                          请填写红包祝福语或标题

                          红包个数最小为10个

                          红包金额最低5元

                          当前余额3.43前往充值 >
                          需支付:10.00
                          成就一亿技术人!
                          领取后你会自动成为博主和红包主的粉丝 规则
                          hope_wisdom
                          发出的红包
                          实付
                          使用余额支付
                          点击重新获取
                          扫码支付
                          钱包余额 0

                          抵扣说明:

                          1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
                          2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

                          余额充值