Linux进程通信——共享内存:System V 进程间通信的极速方案

目录

1.什么是共享内存?

[2 共享内存的数据结构](#2 共享内存的数据结构)

3.共享内存的相关函数

[3.1 ftok 函数](#3.1 ftok 函数)

[3.2 shmget() 创建共享内存函数](#3.2 shmget() 创建共享内存函数)

[3.3 shmat 进程挂接](#3.3 shmat 进程挂接)

[3.4 shmdt()函数 取消挂接](#3.4 shmdt()函数 取消挂接)

[3.5 共享内存控制 shmctl](#3.5 共享内存控制 shmctl)

[1. 获取共享内存状态 (IPC_STAT)](#1. 获取共享内存状态 (IPC_STAT))

[2. 设置共享内存状态 (IPC_SET)](#2. 设置共享内存状态 (IPC_SET))

[3. 删除共享内存 (IPC_RMID)](#3. 删除共享内存 (IPC_RMID))

[4. 共享内存的大小](#4. 共享内存的大小)

5.共享内存"快"的原因

6.共享内存与命名管道配合完成通信

设计步骤

设计实现

[1. 服务端(Server)](#1. 服务端(Server))

[2. 客户端(Client)](#2. 客户端(Client))

共享内存作为System V 标准的一部分,是所有进程间通信(IPC)机制中最快的解决方案之一 。在许多传统的进程间通信方法中**,数据需要通过内核进行拷贝和传递,而共享内存则不需要这个过程。**通过让多个进程直接访问同一块内存区域,它减少了数据拷贝,极大地提高了通信效率。本文将深入探讨共享内存的工作原理、创建过程、常见的 API 使用方法以及如何结合其他 IPC 方式(如命名管道)实现更加高效的进程间通信。

🏙️ 共享内存简介

1.什么是共享内存?

共享内存(Shared Memory)是 System V 标准中的一种进程间通信机制,它允许多个进程共享物理内存的一部分。共享内存的出现解决了多个进程之间如何高效交换数据的问题,它是最适合高速、大量数据传输的 IPC 方式之一。

与其他通信方式(如消息队列、信号量等)不同,共享内存的特点在于,它提供了进程直接操作同一块内存的能力。进程无需经过内核的中介,即可直接在内存中读写数据,从而避免了多次内核交互所产生的开销。这种方法不仅提高了数据传输效率,而且避免了不必要的数据复制,因此被广泛应用于需要高效数据交换的场景中。

简单来说共享内存:就是操作系统在物理内存(长效的保存)开辟特殊的公共的内存空间,不同的进程通过虚拟地址加页表,让不同的进程看到同一份资源,实现通信的目的。

关于共享区:共享区作为虚拟地址空间中一块缓冲区域,既可作为堆栈生长扩展的区域,也可用来存储各种进程间的公共资源,比如这里的共享内存,以及之前学习的动态库,相关信息都是存储在共享区中

注意: 共享内存块的创建、进程间建立映射都是由 OS 实际执行的,共享内存生命周期与进程生命周期不相同,共享内存具有长久性,共享内存本质是存储在物理内存上的。


2 共享内存的数据结构

共享内存的管理由操作系统负责。由于共享内存的生命周期不依赖于任何单个进程,它需要有一个系统级的数据结构来描述和维护其状态。 这个数据结构通常存储在内核空间,并且用于记录共享内存的各种信息,如创建者进程的 ID、内存的大小、访问权限等。

shmid_ds 结构体

在 System V 标准中,共享内存的状态由 shmid_ds 结构体来描述。该结构体用于保存共享内存的元数据,包括共享内存的大小、创建者的进程 ID、最后一次操作的进程 ID、共享内存段的访问权限等信息。操作系统通过对shimd_ds的管理实现对共享内存的管理。

cpp 复制代码
struct shmid_ds {
    struct ipc_perm shm_perm;   // 共享内存的权限和基本信息
    int shm_segsz;              // 共享内存段的大小(字节数)
    __kernel_time_t shm_atime;  // 最后一次附加时间
    __kernel_time_t shm_dtime;  // 最后一次分离时间
    __kernel_time_t shm_ctime;  // 最后一次更改时间
    __kernel_ipc_pid_t shm_cpid; // 创建共享内存的进程 PID
    __kernel_ipc_pid_t shm_lpid; // 最后操作共享内存的进程 PID
    unsigned short shm_nattch;  // 当前附加共享内存的进程数
    unsigned short shm_unused;  // 兼容性字段
    void *shm_unused2;          // 兼容性字段,用于 DIPC
    void *shm_unused3;          // 未使用字段
};

ipc_perm 结构体

shm_perm 是一个 ipc_perm 类型的结构体,用 来存储共享内存的权限信息。它包含了 keyuid(用户 ID)、gid(组 ID)等信息。

cpp 复制代码
struct ipc_perm {
    __kernel_key_t key;      // 共享内存的唯一标识符(key)
    __kernel_uid_t uid;      // 创建者的用户 ID
    __kernel_gid_t gid;      // 创建者的组 ID
    __kernel_uid_t cuid;     // 创建者的用户 ID(继承)
    __kernel_gid_t cgid;     // 创建者的组 ID(继承)
    __kernel_mode_t mode;    // 权限模式(如 0666)
    unsigned short seq;      // 序列号
};

共享内存的数据结构用途

  1. 共享内存块大小shm_segsz 字段表示共享内存的大小,这有助于系统了解分配给共享内存的实际物理内存空间。

  2. 时间戳信息shm_atimeshm_dtimeshm_ctime 分别记录了共享内存最后一次附加、分离和更改的时间。这些信息有助于监控共享内存的使用情况。

  3. 进程 IDshm_cpidshm_lpid 分别记录了创建共享内存的进程和最后操作该共享内存的进程的 PID。这有助于跟踪共享内存的生命周期和操作情况。

  4. 附加进程数量shm_nattch 用来记录当前附加到共享内存的进程数量。这个字段可以帮助操作系统管理共享内存的使用,确保只有当没有进程使用共享内存时,才能安全地删除它。

  5. 权限信息ipc_perm 结构体中的字段提供了共享内存的权限信息,包括创建者的身份、权限模式(如可读写权限)等。

共享内存的组织与管理

为了高效管理共享内存,操作系统通常会将多个共享内存块组织成一个共享内存段链表或类似的数据结构。这样,在系统中存在多个共享内存时,操作系统可以方便地查找和管理这些共享内存段。

共享内存块的创建与标识

每个共享内存块通过 shmid(共享内存标识符)来唯一标识。在多个共享内存块存在时,操作系统会根据 shmid 来管理它们,并通过 key(由 ftok 生成)确保它们在进程间能够共享和访问。

共享内存的生命周期

共享内存的生命周期不依赖于任何单一进程,而是由操作系统管理。在共享内存创建之后,它会一直存在,直到显式地被删除。在删除之前,多个进程可以反复附加和分离共享内存。操作系统需要通过 shmid_ds 结构体中的信息,管理共享内存的生命周期和使用状态。


3.共享内存的相关函数

3.1 ftok 函数

ftok 函数生成一个唯一的标识符,通常用于生成共享内存、消息队列、信号量等 IPC 资源的 key。使用该 key,可以在多个进程之间共享同一块共享内存。

s**hm_perm 是一个 ipc_perm 类型的结构体,其中包含着共享内存的唯一标识key,shmid实际上是shmid_array*[](共享内存数组)的一个下标,key值是唯一的,每一个共享内存的key值都是唯一的。共享资源** :多个进程使用相同的 key 来引用同一个共享内存块。这保证了进程间能够访问同一块内存区域,进行数据共享。

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

参数:

  • pathname :用于生成 key 的文件路径。通常,这个路径应该是一个存在的文件或目录。ftok 通过文件的 inode 信息和路径来确保生成的 key 在系统中是唯一的。

  • proj_id :项目编号,是一个用户自定义的整数值。它与 pathname 共同用于生成唯一的 key。不同的 proj_id 值可以为同一文件路径生成不同的 key

返回值:

  • 成功时 ,返回一个 key_t 类型的唯一标识符,该标识符用于创建和访问共享内存。

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

cpp 复制代码
 key_t key = ftok(".", 1);  // 生成共享内存的唯一 key,路径和项目编号
    if (key == -1) {
        perror("ftok failed");
        return 1;
    }
3.2 shmget() 创建共享内存函数

shmget 用于创建或获取一个共享内存段,并返回共享内存的标识符 shmid,通过该标识符后续可以对共享内存进行操作。

cpp 复制代码
int shmget(key_t key, size_t size, int shmflg);

参数:

  • key :共享内存的唯一标识符。可以通过 ftok 函数生成。不同进程需要通过相同的 key 来访问同一块共享内存。

创建共享内存时候,如果key值发现相同,那么返回的就是已经存在的共享内存的shmid了

cpp 复制代码
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{

   int k=ftok("/home/xuwenhao/",0xFF);
   if(k==-1) 
   {
    perror("ftok error");
    exit(1);
   }

   int shmid1=shmget(k,4096,IPC_CREAT|0664);
   char* shmaddr=(char*)shmat(shmid1,NULL,0);

   int shmid2=shmget(k,4096,IPC_CREAT|0664);
   if(shmid1==shmid2)
  {
    printf("打开了同一块共享内存\n");
   }

    shmctl(shmid1,0,NULL);

    return 0;
}

打开了同一块共享内存

  • size:共享内存的大小,以字节为单位。 size的大小为内存页(4096)大小的整数倍,为了提高I/O流的效果。

  • shmflg:控制共享内存创建的标志位,可以设置共享内存的创建方式以及访问权限。常用的标志包括:

  • IPC_CREAT 创建共享内存,如果存在,则使用已经存在的

  • IPC_EXCL 避免使用已存在的共享内存,不能单独使用,需要配合 IPC_CREAT 使用,作用是当创建共享内存时,如果共享内存已经存在,则创建失败

  • 权限 因为共享内存也是文件,所以权限可设为文件的起始权限 0666

在linux操作系统下,如果没有给权限,进程调用的shmget()创建的共享内存连调用的进程本身都不能访问,权限对于所有人都是0;

返回值:

  • 成功时,返回共享内存的标识符 shmid

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

3.3 shmat 进程挂接

享内存在被成功创建后,进程还不 "认识" 它,只有让待通信进程都 "认识" 同一个共享内存后,才能进行正常通信,让进程 "认识" 共享内存这一操作称为 关联

当进程与共享内存关联后,共享内存才会 通过页表映射至进程的虚拟地址空间中的共享区中

需要使用 shmat 函数进行关联

cpp 复制代码
void* shmat(int shmid, const void* shmaddr, int shmflg);

参数说明:

  • shmid :待关联的共享内存段的标识符,通过 shmget 获取。这个 shmid 用于标识特定的共享内存块。

  • shmaddr :指定共享内存映射至进程共享区的地址。通常,您不需要指定这个地址,可以将其设为 NULL,让操作系统自动选择一个合适的地址。一般情况下,使用 NULL 来让系统自动选择映射的地址位置。如果指定非 NULL 地址,系统会尝试将共享内存映射到这个指定地址,前提是该地址是合适的。

  • shmflg:指定共享内存的访问权限和映射选项。常见的选项包括:

    • 0:表示默认的读写权限。

    • SHM_RDONLY:表示映射后的共享内存只能进行读取操作,禁止写入。

    • SHM_RND:如果 shmaddr 不为 NULL,则将映射地址自动向下调整为系统页面大小的整数倍,确保映射位置的对齐。

返回值:

  • 成功:返回映射的共享内存的起始地址(void* 类型) ,这个地址是进程的虚拟地址空间中映射的共享内存区域的起始地址。进程可以通过这个地址直接访问共享内存。一般通信数据为字符,所以可以将 shmat 的返回值强转为 char*

  • 失败 :返回 (void*) -1,并设置 errno 以指示错误原因。

常见选项:

  • SHM_RDONLY:使用此标志时,进程只能读取共享内存,不能进行写操作。这对于只需要读取共享内存中的数据而不需要修改的场景非常有用。

  • SHM_RND :如果你需要指定特定的映射地址(shmaddr),使用 SHM_RND 会让操作系统自动调整地址,以确保它满足页面大小(通常是操作系统的内存页大小)。这对于某些特定的内存布局要求非常重要。

共享内存映射至共享区时,我们可以指定映射位置(即传递参数2),但我们一般不知道具体地址,所以 可以传递 NULL,让编译器自动选择位置进行映射。

3.4 shmdt()函数 取消挂接

shmdt() 是用于取消共享内存映射 的函数,它将共享内存从进程的虚拟地址空间中分离。进程结束后会自动取消挂接,也可以调用 shmdt() 后,进程不能再访问这块共享内存,操作系统会释放映射所占用的资源,但共享内存本身仍然存在,直到所有进程都取消了映射并且没有进程引用它时,才会被删除。

shmdt 函数解析

cpp 复制代码
int shmdt(const void *shmaddr);

参数:

  • shmaddr :指向共享内存的指针,这是通过 shmat() 映射共享内存时返回的地址。调用 shmdt() 时需要传递该地址来解除共享内存的映射。

返回值:

  • 成功时 ,返回 0

  • 失败时 ,返回 -1,并将 errno 设置为相应的错误代码。

shmdt() 的作用:

  • 解除共享内存的映射 :在调用 shmat() 后,共享内存会被映射到进程的虚拟地址空间中。调用 shmdt() 后,进程的虚拟地址空间中就不再有共享内存的映射,进程不能再访问这块共享内存。

  • 内存清理:解除映射后,操作系统会清理资源,但共享内存本身并不会立即被删除。共享内存将继续存在,直到所有与之关联的进程都取消了映射,并且没有进程再使用它时,操作系统才会删除共享内存。

何时使用 shmdt()

  • 结束时解除映射 :每当一个进程不再需要访问共享内存时,应该调用 shmdt() 解除映射。这样可以确保资源被及时释放,并避免内存泄漏。

  • 防止内存访问错误:如果进程不再使用共享内存,但没有解除映射,进程仍然能访问到不再有效的内存区域,可能会导致内存访问错误或系统崩溃。

3.5 共享内存控制 shmctl

shmctl() 函数是 System V 共享内存中的一个重要函数,它用于控制和管理共享内存段的状态。该函数可以用来获取共享内存的信息、设置共享内存的属性、删除共享内存等。它提供了一些控制命令,可以执行不同的操作。

shmctl() 函数原型

cpp 复制代码
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数说明:

  • shmid :共享内存的标识符,它是通过 shmget() 创建或获取的共享内存的 shmid

  • cmd:控制命令,用于指定对共享内存段执行的操作。常见的命令包括:

    • IPC_STAT:获取共享内存的状态,并将状态信息存储到 shmid_ds 结构体中。

    • IPC_SET:设置共享内存的状态,shmid_ds 结构体中的信息设置为共享内存的新的状态。

    • IPC_RMID:删除共享内存,释放与该共享内存段相关的资源。

  • buf :一个指向 shmid_ds 结构体的指针,用于存储共享内存的状态信息或设置共享内存的属性。shmid_ds 结构体包含了共享内存的权限、大小、附加进程数、时间戳等信息。

返回值:

  • 成功时 :返回 0

  • 失败时 :返回 -1,并设置 errno,可以通过 perror() 获取详细的错误信息。

1. 获取共享内存状态 (IPC_STAT)

cmdIPC_STAT 时,shmctl() 会将共享内存的状态信息存储到 shmid_ds 结构体中。该结构体包含了共享内存段的权限、创建时间、最后附加时间、进程数量等信息。

cpp 复制代码
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <errno.h>

struct shmid_ds buf;

int shmid = shmget(12345, 4096, IPC_CREAT | 0666);
if (shmid == -1) {
    perror("shmget failed");
    return 1;
}

if (shmctl(shmid, IPC_STAT, &buf) == -1) {
    perror("shmctl IPC_STAT failed");
    return 1;
}

printf("Shared Memory Segment Size: %d\n", buf.shm_segsz);
printf("Last attach time: %ld\n", buf.shm_atime);
printf("Last detach time: %ld\n", buf.shm_dtime);

在此示例中,shmctl() 获取了共享内存的状态信息,并将它存储在 buf 中。你可以查看共享内存的大小、最后一次附加和分离的时间等信息。

2. 设置共享内存状态 (IPC_SET)

cmdIPC_SET 时,shmctl()shmid_ds 结构体中的信息设置为共享内存的新的状态。只有当进程具有足够的权限时,才可以使用此命令。

示例:

cpp 复制代码
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <errno.h>

struct shmid_ds buf;

// 假设共享内存已经存在
int shmid = shmget(12345, 4096, IPC_CREAT | 0666);
if (shmid == -1) {
    perror("shmget failed");
    return 1;
}

// 修改一些字段
buf.shm_perm.mode = 0660;  // 设置共享内存的权限为 0660

// 设置新的状态
if (shmctl(shmid, IPC_SET, &buf) == -1) {
    perror("shmctl IPC_SET failed");
    return 1;
}

printf("Shared memory permissions updated.\n");

在此示例中,shmctl() 被用来更改共享内存的权限。IPC_SET 使得进程可以修改共享内存的属性(如访问权限)。

3. 删除共享内存 (IPC_RMID)

cmdIPC_RMID 时,shmctl() 会删除共享内存段并释放系统资源。删除操作只能由拥有该共享内存的进程执行。当没有进程再附加到该共享内存时,系统会自动回收内存

cpp 复制代码
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <errno.h>

int shmid = shmget(12345, 4096, IPC_CREAT | 0666);
if (shmid == -1) {
    perror("shmget failed");
    return 1;
}

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

printf("Shared memory deleted.\n");

在此示例中,shmctl() 被用来删除共享内存段。当 IPC_RMID 被指定时,系统会释放与共享内存段相关的所有资源。

4. 共享内存的大小

共享内存的大小与操作系统的页面管理机制密切相关,特别是在 Unix 或 Linux 等操作系统中,内存是按页(Page)进行管理的。操作系统的内存分页系统通常使用固定大小的内存页,常见的页大小为 4KB、8KB 或 16KB。

为什么共享内存大小会被对齐到页面大小?

  1. 内存分页机制 :操作系统会将物理内存划分为大小固定的块,称为 页面。每个页面的大小通常是 4KB。由于内存是按页来管理的,为了提高内存管理的效率,操作系统通常会确保分配给进程的内存大小是页面大小的整数倍。这样,系统能够更高效地进行内存分配、访问控制和保护。

  2. 内存对齐:操作系统为了避免因非法操作导致越界访问,会在共享内存的分配上进行内存对齐。共享内存的实际大小是按页大小进行对齐的,即使请求的内存大小不足一个完整的页。例如,如果你请求了 4097 字节的共享内存,操作系统会分配 8192 字节(2 页)来满足请求。这些额外的内存空间不会被共享内存使用,但它们用于保护内存访问,防止进程访问非法内存区域。

  3. 示例说明

    • 如果你请求 4097 字节,操作系统会分配 8192 字节,剩余的 4095 字节将不会被使用。

    • 如果你请求的是 8192 字节,操作系统会分配 8192 字节,正好符合一个页面的大小。

这就是为什么在请求共享内存时,实际分配的内存大小往往是 页面大小的整数倍。这不仅是为了对齐,也与操作系统的内存管理策略有关。

例子:

  • 请求 4096 字节的共享内存:操作系统分配 4096 字节。

  • 请求 4097 字节的共享内存:操作系统分配 8192 字节。

这种行为是操作系统的内存管理的一部分,目的是减少内存管理的复杂性,同时保证内存访问的安全性。

5.共享内存"快"的原因

共享内存在进程间通信中的效率之所以高,主要是因为它能 减少数据拷贝减少 I/O 操作。在传统的进程间通信机制中,如管道、消息队列或套接字等,数据需要在进程间传递时经过内核缓冲区或网络协议栈,这会引入很多不必要的拷贝和 I/O 操作。

共享内存的高效性:

  1. 避免内核缓冲区 :在使用共享内存时,进程 A 将数据直接写入共享内存,进程 B 可以直接从共享内存中读取数据。没有中间的内核缓冲区,因此 避免了多次数据拷贝

  2. 减少 I/O 操作:在其他 IPC 机制中,例如使用管道时,数据需要经过多次 I/O 操作:

    • 进程 A 从内存中读取数据,进行系统调用,将数据写入管道。

    • 然后,数据需要通过内核缓冲区传输。

    • 进程 B 通过系统调用从管道中读取数据,再写入内存。

    这种过程中,数据至少会经历 4 次 I/O 操作:

    1. 进程 A 将数据从内存复制到管道缓冲区。

    2. 内核从管道缓冲区传递数据。

    3. 进程 B 从管道缓冲区读取数据。

    4. 进程 B 将数据从管道复制到自己的内存。

  3. 共享内存的工作方式

    • 进程 A 直接将数据写入共享内存。

    • 进程 B 直接从共享内存读取数据。

    在这种情况下,数据的传输完全依赖于进程的内存空间,进程之间通过共享同一块物理内存区域来交换数据。由于 不需要经过内核的缓冲区,所以数据传输的效率比其他 IPC 方式要高得多。

  4. 避免内存拷贝:共享内存直接映射到进程的地址空间,进程对共享内存的操作就像访问自己的内存一样,因此不会像管道那样进行多次内存拷贝。数据直接在进程的虚拟内存中传递,不经过额外的内存复制步骤。

总结:

  • 减少拷贝:共享内存允许进程直接读写同一块内存区域,避免了多次数据拷贝。

  • 减少 I/O 操作:与管道等需要多次 I/O 操作的 IPC 机制不同,使用共享内存只需要进程之间在物理内存中共享数据,极大减少了内核的参与,避免了 I/O 阻塞和数据传输延迟。

  • 高效数据传递:由于数据是直接共享的,不经过内核的中转和缓冲,因此共享内存的通信速度非常快,适合需要快速交换大量数据的场景。

对比其他 IPC 方式:

  1. 管道(Pipes)

    • 数据需要从进程 A 写入管道,然后通过内核缓冲区传递给进程 B。每次读写都需要系统调用和内核干预,导致额外的内存拷贝。
  2. 消息队列

    • 类似于管道,数据存放在内核中的消息队列中,需要进程通过系统调用进行传输,数据传输过程中会有内存拷贝。
  3. 共享内存

    • 进程直接在物理内存中操作数据,不需要经过内核中转。数据传输非常快速,适用于高效的进程间通信。

6.共享内存与命名管道配合完成通信

由于共享内存本身缺乏同步机制,容易出现数据冲突,因此我们可以通过结合命名管道(FIFO)来控制数据的读写流程,确保通信的安全性和顺序。

在进程间通过共享内存进行通信时,由于共享内存没有固有的同步机制,可能会导致进程对共享内存的竞争访问。因此,为了确保数据的可靠传输,我们引入了 命名管道(FIFO) 来控制进程的通信流程,避免共享内存被同时读写,防止数据竞争和冲突。

场景描述:

  • 两个进程(客户端和服务端)通过共享内存交换数据。

  • 需要使用两条 命名管道 来辅助进程间的通信:

    • 管道 1:服务端将数据写入共享内存,并通过管道通知客户端读取数据。

    • 管道 2:客户端读取共享内存中的数据后,通过管道通知服务端继续进行下一步操作。

通过命名管道,我们实现了 双向通知:一个管道用于控制服务端和客户端之间的通知,另一个管道用于数据流动的控制。

为什么要使用命名管道而非直接共享内存?

命名管道的作用是提供一个同步信号机制,因为共享内存本身并没有内建的同步机制。如果没有这种机制,两个进程可能同时访问共享内存,导致数据的冲突和不可预测的结果。通过命名管道,进程间可以通过一个有序的通知机制,确保数据的读写操作按顺序进行。

设计步骤

  1. 共享内存:进程通过共享内存进行数据传输,进程之间可以直接读取和写入数据。

  2. 命名管道:通过命名管道来控制进程间的同步,确保共享内存的读写操作不发生冲突。

设计实现

1. 服务端(Server)

服务端进程负责创建共享内存,向共享内存写入数据,并通过命名管道通知客户端进行读取。然后它将等待客户端的响应,通过另一个命名管道接收客户端的通知,继续执行后续任务。

2. 客户端(Client)

客户端进程通过命名管道接收服务端的通知,读取共享内存中的数据并进行处理。处理后,客户端通过另一条命名管道通知服务端继续进行下一个操作。

代码:server.cc

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

int main()
{
    int shmid=Getshm();
    char *shmaddr=(char*)shmat(shmid,NULL,0);
    
    int i=0;
 
        int x=mkfifo("myfifo",0664);
        if(x==-1) 
        {
            perror("myfifi error");
            return -1;
        }

        int id=open("myfifo",O_RDONLY);
        while(true)
        {
            char buffer[10];
            int n=read(id,buffer,sizeof(buffer));
            buffer[n]=0;//添加上\0 细节注意
            if(n>0)
            {
                printf("client say@ %s\n",shmaddr);
                sleep(1);
            }
            else if(n==0)
            {
                printf("通信结束\n");
                break;
            }
            else 
            {
                perror("read error");
                break;
            }
        }
     unlink("myfifo");
    shmdt(shmaddr);//进程结束,关联取消
    shmctl(shmid,IPC_CREAT,NULL);
    return 0;
}

client.cc

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

int main()
{
    int shmid=Getshm();
    char *shmaddr=(char*)shmat(shmid,nullptr,0);

    int id=open("myfifo",O_WRONLY);
    
    int cnt=0;
    int i=0;
    while(cnt<6)
    {
        char buffer[10]="YES";
        int n=write(id,buffer,strlen(buffer));
        
        shmaddr[i]='A'+i++;
        cnt++;
        shmaddr[i]=0;
        sleep(1);
    }

    shmdt(shmaddr);
    return 0;
}

comm.hpp

cpp 复制代码
#pragma  once
#include <iostream>
#include <sys/ipc.h>//shm
#include <sys/shm.h>
#include <cstring>
#include <cerrno>
#include <sys/types.h>//管道
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdlib>

using namespace std;

#include "log.hpp"
#define Path "/home/xuwenhao/"
#define Proj_id 0x55
#define FIFO_FILE "./myfifo"
#define MODE 0664 

Log log;

key_t getkey()
{
    key_t k=ftok(Path,Proj_id);
    if(k<0)
    {
        log("fatal","ftok error %s",strerror(errno));
        return 0;
    }
    log("Info","ftok success k=ox%x",k);
    
    return k;
}

int getshmid(int shmflag)
{
    key_t k=getkey();
    int shmid=shmget(k,SIZE,shmflag);
    if(shmid<0)
    {
        log("fatal","shmget error %s",strerror(errno));
        return 0;
    }

     log("Info","shmid success k=ox%x",shmid);
     return shmid;
}


int Getshm()
{
    return getshmid(IPC_CREAT|0664); //IPC_CREAT
}


class Init
{
public:
  Init()
  {
     int n=mkfifo(FIFO_FILE,MODE);
     if(n==-1)
     {
        perror("open fifo");
        exit(FIFO_CREAT_ERR);
     }
  }

  ~Init()
  {
    int m=unlink(FIFO_FILE);
    if(m==-1)
    {
        perror("delete fifo");
        exit(FIFO_DELETE_ERR);
    }
  }
};

log.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdlib>
#include <error.h>
using namespace std;

#define SIZE 4096
#define LogFile "log.txt" //日志文件的名字

enum 
{
   FIFO_OPEN_ERR=1,
   FIFO_DELETE_ERR,
   FIFO_CREAT_ERR
};


class Log
{
public:
    Log()
    {
        printmethod="Screen";//默认打印在屏幕上面
        path="./mylog/";//默认路径
    }
    void Eable(string method)
    {
        printmethod=method;
    }

    void operator()(const string method,const char *format,...)
    {
        time_t t=time(nullptr);
        struct tm*ctime=localtime(&t);//返回一个指针

        char leftbuffer[SIZE];
        snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d %d:%d:%d]",method.c_str(),ctime->tm_year+1900,
        ctime->tm_mon+1,ctime->tm_mday,ctime->tm_hour,ctime->tm_min,ctime->tm_sec);

        va_list s;
        va_start(s,format);

        char rightbuffer[SIZE];
        vsnprintf(rightbuffer,sizeof(rightbuffer),format,s);//解析va_arg();的作用
        va_end(s);

        char logtxt[SIZE*2];
        snprintf(logtxt,sizeof(logtxt),"%s %s\n",leftbuffer,rightbuffer);

        Printlog(method,logtxt);
    }

    void Printlog(const string method,string logtxt)
    {
        if(method=="Screen")
        {
            cout<<logtxt<<endl;
        }
        else
        {
            string s=path+method+".txt";//./mylog/init.
            
            int fd=open(s.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
            if(fd<0) return ;
            write(fd,logtxt.c_str(),logtxt.size());
            close(fd);
        }
    }
    

private:
    string printmethod;
    string path;
};
相关推荐
ChinaRainbowSea3 小时前
用户中心——比如:腾讯的QQ账号可以登录到很多应用当中 02
java·服务器·spring boot·后端·mysql·mybatis
青竹易寒4 小时前
Linux命令技术笔记-sed+awk命令详解
linux·运维·服务器
Kiri霧4 小时前
Kotlin泛型约束
android·linux·windows·kotlin
试着5 小时前
零基础学习性能测试第二章-linux服务器监控:CPU监控
linux·服务器·学习·零基础·性能测试·cpu监控
Littlewith5 小时前
Node.js:Stream、模块系统
java·服务器·开发语言·node.js·编辑器
Gappsong8746 小时前
浅析网络安全面临的主要威胁类型及对应防护措施
运维·网络·安全·web安全·网络安全
步、步、为营6 小时前
.NET 8 中的 KeyedService
运维·服务器·.net
老马啸西风6 小时前
windows wsl ubuntu 如何安装 open-jdk8
linux·windows·ubuntu·docker·容器·k8s·kvm
<但凡.6 小时前
Git 完全手册:从入门到团队协作实战(2)
服务器·git·bash