Linux 进程通信的方式:信号量、共享内存和消息队列

Linux 进程通信的方式:信号量、共享内存和消息队列

1. 基本概念

  • 主要作用
    • 数据传输
    • 通知事件
    • 资源共享
    • 进程控制
  • 实现方式
    • 单机内的进程通信
    • 跨主机的基于socket 的进程间通信
    • 标准化的进程间通信方式
  • 分类依据信息量:
    • 低级通信:传递控制信息。主要用于进程之间的 同步 、 互斥 、终止、挂起等控制信息的传递
    • 高级通信:主要用于大量数据的传递。
  • 主要方式:
    • 管道
    • 信号
    • 消息队列:是消息所构成的链接表,包括POSIX 消息队列和 System V 消息队列。
    • 共享内存: 使得多个进程可以访问同一块内存空间是最快的可用 IPC 形式。
    • 信号量: 主要作为进程间以及同一进程不同线程之间的同步手段。
    • 套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。

2. System V 信号量(PV操作)

4个步骤

  1. 创建信号量或获得在系统中其它进程已经创建的已存信号量,此时需要调用 semget () 函数。不同进程通过使用同一个信号量键值来获得同一个信号量。
  2. 初始化信号量,此时使用 semctl () 函数的SETVAL 操作 。当使用二维信号量时,通常将信号量初始化为 1
  3. 信号量的 P 和 V 操作,此时,调用 semop 函数。这一步是实现进程间的同步和互斥的核心工作部分。
  4. 当不需要信号量时,从系统中删除它,此时使用semctl 函数 的 IPC_RMID 操作 。

semget()

目标函数的功能为创建一个新的 信号量集 ,或获取一个系统中已经存在的信号量集
头文件sys/sem.h
函数原型int semget(key_t key, int nsems , int semflg)
参数说明key: 信号量的键值
nsems: 创建的信号量数目
semflg: 标志位,设置权限,可与IPC_CREAT, IPC_EXCL 发生与或,IPC_PRIVATE表示当前进程的私有信号量
返回值成功:信号量的标识符;失败:-1,并设置errno
补充errno:
EACCES: 进程无访问权限
ENOENT: 传入的键值不存在
EINVAL: nsens小于0,或信号量达到上限
EEXIST: 信号量已存在

semctl()

目标对信号量或信号量集进行多种控制
头文件sys/sem.h
函数原型int semctl (int semid , int semnum , int cmd ,...)
参数说明semid: 信号量标识符
semnum: 信号量再信号量集中的编号,在使用信号量集时才会使用,通常设置为0
cmd: 对信号量的操作
返回值成功:0;失败:-1
补充cmd取值:
SETVAL: 表示 semctl 的功能为初始化信号量的值,信号量值通过可选参数传入,在使用信号量前应先对信号量值进行设置
IPC_RMID: 。表示 semctl 的功能为从系统中删除指定信号量。信号量的删除应由其所有者或创建者进行,没有被删除的信号量将会一直存在于系统中
// 可选参数,依赖于cmd
union semun {
	int val;	//cmd 为 SETVAL 时,用于指定信号量值
	struct semid_ds *buf;	// cmd 为 IPC_STAT 时或 IPC_SET 时生效
	unsigned short *array;  // cmd 为 GETALL 或 SETALL 时生效
	struct seminfo *_buf;   // cmd 为 IPC_INFO 时生效
}

struct semid_df {
	struct ipc_perm sem_perm;	// 所有者和标识权限
	time_t			sem_otime;	// 最后操作时间
	time_t			sem_ctime;	// 最后更改时间
	unsigned short 	sem_nsems;	// 信号集中的信号数量
}

semop()

目标改变信号量的值
头文件sys/sem.h
函数原型int semop (int semid , struct sembuf *sops, unsigned nsops);
参数说明semid: 信号量标识符
nsops: 参数sops所指数组中的元素个数
sops: 为一个 struct sembuf 类型的数组指针,该数组中的每个元素设置了要对信号量集中的哪个信号做哪种操作。
返回值成功:0;失败:-1, 并设置errno
struct sembuf{
	short sem_num;	// 信号量在信号量集中的编号
	short sem_op;	// 设置为 1 时表示 P 操作;设置为 +1 时表示 V 操作
	short sem_flag; // 标志位 通常设置为 SEM_UNDO, 若进程退出前没有删除信号量,信号量将会自动释放
}

ftok()

当在进程中使用 System V IPC 系列的接口进行通信时,必须指定一个 key 值,这是一个 key_t 类型的变量通过 Linux 系统中的一个函数 ftok 来获取.

目标把一个 已存在的路径名 和 一个整数标识符转换成一个 key_t 值。
头文件sys/types.h
函数原型key_t ftok (const char *pathname, int proj_id)
参数说明pathname: 已存在路径名,一般会设置为当前目录"."
proj_id: 一般设置为0
返回值成功:IPC键值;失败:-1,
补充当 ftok 函数被调用时,该函数首先会获取目录的 inode ,其次将十进制的 inode 及参数 proj_id 否转换为十六进制,最后将这两个十六进制数链接,生成一个 key_t 类型的返回值。

例子

子进程先输出"I am the son",父进程再输出"I am the father".

// semlib.h
#include "sys/sem.h"
#include "sys/types.h"
#define DELAY_TIME 3
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};
int init_sem(int sem_id, int init_value); //信号量初始化函数
int del_sem(int sem_id);                  //信号量删除函数
int sem_p(int sem_id);                    //信号量的 P 操作函数
int sem_v(int sem_id);                    //信号量的 V 操作函数
// semlib.c
#include "semlib.h"

int init_sem(int sem_id, int init_value)
{
    // 设置信号量的初始值
    union semun sem_union;
    sem_union.val = init_value;

    // SETVAL 参数表示设置信号量的初始值
    if (semctl(sem_id, 0, SETVAL, sem_union) == -1)
    {
        perror("initializing semaphore");
        return -1;
    }
    return 0;
}

int del_sem(int sem_id)
{
    union semun sem_union;
    // IPC_RMID 参数表示删除 sem_id 信号量
    if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
    {
        perror("Delete semaphore failed.");
        return -1;
    }
    return 0;
}

int sem_p(int sem_id)
{
    struct sembuf sem_b;
    // 信号量编号,单个信号量时,设置为 0
    sem_b.sem_num = 0;
    // 设置为 1 表示进行 P 操作
    sem_b.sem_op = -1;
    // 当程序退出时未释放该信号量时,由操作系统负责释放
    // 对编号为 sem_id 的信号量执行 P 操作
    sem_b.sem_flg = SEM_UNDO;

    if (semop(sem_id, &sem_b, 1) == -1)
    {
        perror("P operation failed");
        return -1;
    }
    return 0;
}

int sem_v(int sem_id)
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = 1;
    sem_b.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sem_b, 1) == -1)
    {
        perror("V operation failed");
        return -1;
    }
    return 0;
}
// semtest.c
#include "semlib.h"
#include <stdlib.h>
#include <stdio.h>
int main()
{
    pid_t result;
    int sem_id;

    /*获取信号量的标识符,在下列中,不同独立进程可获取相同的信号量标识符,
    该操作通过 ftok 函数获得信号量的键值。 ftok 函数通过获取第一个参数
    所指定的文件的 i 节点号,在其之前加上子序号作为键值返回 */
    sem_id = semget(ftok(".", 'a'), 1, 0666 | IPC_CREAT);
    init_sem(sem_id, 0);
    result = fork();

    if (result == -1)
        perror("Fork failed\n");
    else if (result == 0)
    {
        sleep(DELAY_TIME);
        printf("The child progress output\n");
        printf("I am the son\n");
        sem_v(sem_id);
    }
    else
    {
        sem_p(sem_id);
        printf("The father process out put\n");
        printf("I am the father\n");
        del_sem(sem_id);
    }
    exit(0);
}

3. POSIX 有名信号量

基本概念

  • 用于任何关系的进程之间的同步

  • 有名信号量把信号量的值 保存在文件 中。这决定了它的用途非常广:

    • 线程
    • 相关进程
    • 不相关进程
  • POSIX 的两类信号量

    ipc1

sem_open()

目标打开一个已存在的有名信号量,或创建并初始化一个有名信号量。该调用完成信号量的创建、初始化和权限的设置。
头文件sys/sempahore.h
函数原型sem_t* sem_open (const char *pathname, int oflag, mode_t mode, int value);
参数说明pathname: 文件路径名,一般有名信号量文件都是在/dev/shm 目录下的
oflag: O_CREAT 或 O_CREAT | O_EXCL 两个取值
mode_t: 控制新的有名信号量的访问权限
value: 指定信号量的初始化值
返回值成功:set_t*;失败:SEM_FAILED,
补充可通过 sem_close(sem_t)进行关闭,然后使用sem_unlink (const char * name )将有名信号量删除
在编译使用 sempahore.h 的函数时,需要使用 -lrt 或者 -lpthread 链接库文件
// P操作
// sem >0 ,那么它减 1 并立即返回
// sem ==0 ,则在此调用处阻塞直到 sem >0 时为止,此时立即减 1 ,然后返回
int sem_wait(sem_t * sem);

// V操作
// 把指定的信号量sem的值加1,唤醒正在等待该信号量的任意进程
int sem_post (sem_t *sem);

例子

注意,以下代码运行一次后,会在/dev/shm下创建对应的有名信号文件,想要再次运行,需要删除对应文件,或者改SEM_NAMESEM_NAME2.

// posix.c
#include <stdio.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <fcntl.h>
#include <stdlib.h>
#define SHMSZ 27
char SEM_NAME[] = "process1";
char SEM_NAME2[] = "process2";

int main()
{
    char ch;
    int shmid, fd;
    key_t key;
    sem_t *mutex, *mutex2;

    // 创建和初始化信号量
    mutex = sem_open(SEM_NAME, O_CREAT, 0777, 0);	// 该信号量初始值为0
    mutex2 = sem_open(SEM_NAME2, O_CREAT, 0777, 1);	// 该信号量初始值为1
    if (mutex == SEM_FAILED || mutex2 == SEM_FAILED)
    {
        perror("unable to execute semaphore");
        sem_close(mutex);
        sem_close(mutex2);
        exit(-1);
    }

    fd = open("a.txt", O_CREAT | O_RDWR | O_TRUNC | O_APPEND, 0777);
    if (fd == -1)
    {
        perror("Open failed");
        sem_close(mutex);
        // sem_close(mutex2);
        exit(-1);
    }
    while (1)
    {
        sem_wait(mutex); // P操作,开始时mutex为0,故阻塞,直到posix2.c中sem_post(mutex)
        char s[] = "ABC\n";
        write(fd, s, sizeof(s) - 1);
        close(fd);

        fd = open("a.txt", O_WRONLY | O_APPEND);
        sleep(1);
        sem_post(mutex2); // V操作
    }

    sem_close(mutex);
    sem_unlink(SEM_NAME);
    close(fd);
    exit(0);
}
// posix2.c
#include <stdio.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <fcntl.h>
#include <stdlib.h>
#define SHMSZ 27
char SEM_NAME[] = "process1";
char SEM_NAME2[] = "process2";

int main()
{
    char ch;
    int shmid, fd;
    key_t key;
    sem_t *mutex, *mutex2;

    // 创建和初始化信号量
    key = 19753;
    mutex = sem_open(SEM_NAME, O_CREAT, 0777, 0);	// 该信号量初始值为0
    mutex2 = sem_open(SEM_NAME2, O_CREAT, 0777, 1);	// 该信号量初始值为1
    if (mutex == SEM_FAILED || mutex2 == SEM_FAILED)
    {
        perror("unable to execute semaphore");
        sem_close(mutex);
        sem_close(mutex2);
        exit(-1);
    }

    fd = open("a.txt", O_CREAT | O_RDWR | O_TRUNC | O_APPEND, 0777);
    if (fd == -1)
    {
        perror("Open failed");
        sem_close(mutex);
        sem_close(mutex2);
        exit(-1);
    }
    while (1)
    {
        sem_wait(mutex2); // P操作,开始时mutex2为1,故能继续运行
        char s[] = "DEF\n";
        write(fd, s, sizeof(s) - 1);
        close(fd);

        fd = open("a.txt", O_WRONLY | O_APPEND);
        sleep(1);
        sem_post(mutex); // V 操作
    }

    sem_close(mutex);
    sem_unlink(SEM_NAME);
    close(fd);
    exit(0);
}
  • 运行:

    image-20221120181338899
    image-20221120181357250

  • 结果:不断地写入DEF\nABC\n,至于为何DEF在前,这就跟信号量有关,请看注释。

4. 共享内存

基本概念

  • 共享内存是 GNU/Linux 现在可用的、最快速的进程间通信机制

  • 与消息队列和管道通信机制相比,一个进程要向队列管道中写入数据时 ,引起数据从用户地址空间向内核地址空间的一次复制,进行消息读取时也要进行 一次复制,共享内存的优点是完全省去了这些操作

    image-20221120181046983

  • 共享内存的使用

    • 创建共享内存。从内存中获得一段共享内存区域,这里用到的函数是 shmget
    • 映射共享内存。创建的共享内存映射到具体的进程空间中,使用函数 shmat
    • 撤销映射。使用完共享内存就需要撤销,用到的函数是shmdt

shmget()

目标IPC 对象创建或打开函数
头文件sys/types.h sys/ipc.h sys/shm.h
函数原型int shmget(key_t key, int size, int shmflg)
参数key:共享内存的键值,多个进程可以通过它访问同一个共享内存,其中有个特殊值 IPC_PRIVATE ,用于创建当前进程的私有共享内存。如果要想在key 标识的共享内存不存在时,创建它的话,可以与 IPC_CREAT 做或操作。共享内存的权限标志与文件的读写权限一样。
size: 共享内存区大小
shmflg: 权限位
返回值成功: 共享内存段标识符;失败:-1

shmat()

目标IPC 控制函数
头文件sys/types.h sys/ipc.h sys/shm.h
函数原型char *shmat (int shmid, const void *shmaddr, int shmflg)
参数shmid::要映射的共享内存区标识符
shmaddr: :将共享内存映射到指定地址(若为 0 则表示系统自动分配地址并把该段共享内存映射到调用进程的地址空间)
shmflg: SHM_RDONLY 共享内存只读; 默认0,共享内存可读写
返回值成功: 被映射的段地址;失败:-1

shmdt()

目标IPC 操作函数
头文件sys/types.h sys/ipc.h sys/shm.h
函数原型int shmdt (const void *shmaddr)
参数shmaddr: :被映射的共享内存段地址
返回值成功: 0;失败:-1

shmctl()

目标IPC 控制函数
头文件sys/types.h sys/ipc.h sys/shm.h
函数原型int shmctl (int shmid , int cmd , struct shmid_ds *buf)
参数shmid:共享内存区标识符
cmd: 表示对共享内存的属性执行的相关命令,主要有:
- IPC_STAT: 表示得到共享内存的状态,把共享内存的shmid_ds 结构复制到 buf 中;
- IPC_SET :改变共享内存的状态,把 buf 所指的 shmid_ds 结构中的 uid 、 gid 、 mode 复制到共享内存的 shmid_ds 结构内
- IPC_RMID :删除该共享内存
buf::共享内存管理结构体
返回值成功: 0;失败:-1

例子1

由sleep控制的子进程先写入共享内存,父进程再读共享内存。

// shm.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>		
#define SIZE 1024

int main()
{
    // 共享内存段标识符
    int shmid;
    char *shmaddr;
    char buff[30];
    // 获取共享内存属性信息
    int shmstatus;
    int pid;

    shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT | 0600);
    if (shmid < 0)
    {
        perror("getshm ipc_id error");
        return -1;
    }

    pid = fork();
    if (pid == 0)
    {
        shmaddr = (char *)shmat(shmid, NULL, 0);
        if ((int)shmaddr == -1)
        {
            perror("shmat addr error");
            return -1;
        }
        strcpy(shmaddr, "Hello World!\n");
        shmdt(shmaddr);
        return 0;
    }
    else if (pid > 0)
    {
        sleep(5);
        shmaddr = (char *)shmat(shmid, NULL, 0);
        if ((int)shmaddr == -1)
        {
            perror("shmat addr error");
            return -1;
        }
        strcpy(buff, shmaddr);
        printf("I have got from shared memory: %s\n", buff);
        shmdt(shmaddr);
        shmctl(shmid, IPC_RMID, NULL);
    }
    else
    {
        perror("fork error");
        shmctl(shmid, IPC_RMID, NULL);
    }
    return 0;
}

例子2

由信号量控制的父进程先写入共享内存,子进程再读共享内存。

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>	
#include "semlib.h"		// 为第二节头文件,编译时带上stdlib.c
#define BUFFER_SIZE 2048
#define STRLEN 40
int main()
{
    pid_t pid;
    int sem_id, shmid;
    char *shm_addr = NULL;
    char buff[STRLEN];
    int i = 0;

    sem_id = semget(ftok(".", 'a'), 1, 0666 | IPC_CREAT);
    init_sem(sem_id, 0);
    if ((shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666)) < 0)
    {
        perror("shmget");
        return -1;
    }

    pid = fork();
    if (pid == -1)
    {
        perror("fork");
        return -1;
    }
    else if (pid == 0)
    {
        if ((shm_addr = shmat(shmid, 0, 0)) == (char *)-1)
        {
            perror("shmat");
            return -1;
        }

        while (i < 3)
        {
            printf("Child process is waiting for data:\n");
            sem_p(sem_id);
            strcpy(buff, shm_addr);

            printf("Child get data from shared-memory:%s\n", buff);
            sem_v(sem_id);
            i++;
        }
        del_sem(sem_id);
        if ((shmdt(shm_addr)) < 0)
        {
            perror("shmdt");
            return -1;
        }
        if (shmctl(shmid, IPC_RMID, NULL) == -1)
        {
            perror("child process delete shared memory");
            return -1;
        }
    }
    else
    {
        if ((shm_addr = shmat(shmid, 0, 0)) == (char *)-1)
        {
            perror("shmat");
            return -1;
        }
        while (i < 3)
        {
            printf("Please input something:\n");
            int j;
            fgets(buff, BUFFER_SIZE, stdin);
            strncpy(shm_addr, buff, strlen(buff) + 1);
            sem_v(sem_id);
            i++;
            sem_p(sem_id);
        }
        if ((shmdt(shm_addr)) < 0)
        {
            perror("Parent:shmdt");
            exit(1);
        }
        waitpid(pid, NULL, 0);
    }
    return 0;
}

内存映射

Linux 提供了内存映射函数 mmap 它把普通文件内容映射到一段虚拟内存上,通过对这段内存的读取和修改 实现对文件的读取和修改。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用 read() ,write()等操作。

mmap()
目标将磁盘文件映射到进程的某段内存空间中
头文件sys/mman.h
函数原型void *mmap (void *addr , size_t length, int prot , int flags, int fd , off_t offset);
参数addr: 指定映射的起始地址 通常设为 NULL, 由系统指定
length: 将文件的多大长度映射到内存
prot: 映射区的保护方式 可以是
• PROT_EXEC 映射区可被执行
• PROT_READ 映射区可被读取
• PROT_WRITE 映射区可被写入
• PROT_NONE 映射区不能存取
flags: 映射区的特性可以是
• MAP_SHARED 对映射区域的写入数据会复制回文件 且允许其他映射该文件的进程共享
• MAP_PRIVATE 对映射区域的写入操作会产生一个映射的复制 (copy on write), 对此区域所做的修改不会写回原文件
fd: 由 open 返回的文件描述符代表要映射的文件
offset: 以文件开始处的偏移量 必须是分页大小的整数倍 通常为 0 表示从文件头开始映射
返回值成功: 文件映射到进程空间的地址;失败:-1
munmap()
目标解除进程地址空间中的映射关系
头文件sys/mman.h
函数原型void *munmap (void *addr , size_t len)
参数addr :调用mmpa时返回的地址
len: 映射区的大小
返回值成功: 0;失败:-1
msync()
目标实现磁盘上文件内容与共享内存区的内容一致
头文件sys/mman.h
函数原型void *msync (void *addr , size_t len , int flags)
参数addr :调用mmpa时返回的地址
len: 映射区的大小
flags: 映射区的特性,可以是:
• MS_SYNC 要求回写完成后才返回
• MS_ASYNC 发出回写请求后立即返回
• MS_INVALIDATE 使用回写的内容更新该文件的其它映射
返回值成功: 0;失败:-1

5. 消息队列

基本概念

  • 消息队列就是一个消息的链表即将消息看作一个记录,并且这个记录具有特定的格式以及特定的优先级
  • 对消息队列有写权限的进程可以按照一定的规则添加新消息到 队列的末尾
  • 对消息队列有读权限的进程则可以从消息队列中读取消息
  • 发送方不必等待接收方去接收该消息就可不断发送数据,而接收方若未收到消息也无需等待
  • 这种方式实现了发送方和接收方之间的 松耦合 ,发送方和接收方只需负责自己的发送和接收功能,无需等待另外一方,从而为应用程序提供了灵活性。

ipc3

  • 特点

    • 首先,它提供有 格式字节流 ,有利于减少开发人员的工作量
    • 其次,消息具有类型,在实际应用中,可为不同类型的消息分配不同的优先级
  • 消息队列的使用

    1. 创建消息队列
    2. 发送数据到消息队列
    3. 从消息队列中读取数据
    4. 删除队列

msgget()

目标创建一个消息队列或取得一个已经存在的消息队列
头文件sys/msg.h
函数原型int msgget (key_t key, int msgflg)
参数key:消息队列的键值
msgflg:创建消息队列的创建方式或权限 。 创建方式有:
• IPC_CREAT 如果内存中不存在指定消息队列 则创建一个消息队列否则取得该消息队列;
•IPC_EXCL ,消息队列不存在时,新的消息队列才会被创建否则会产生错误
返回值成功: 返回消息队列的标示符;失败:-1

msgsnd()

目标往队列中发送一个消息
头文件sys/msg.h
函数原型int msgsnd (int msgid,struct msgbuf *msgp ,int msgsz, int msgflg)
参数msgid:消息标识 id 也就是 msgget 函数的返回值
msgp:指向消息缓冲区的指针,该结构体为
struct mymesg {
long mtype //消息类型
char mtext[512] // 消息文本
}
msgsz:消息文本的大小 不包含消息类型
msgflg:可以设置为 0 ,或者 IPC_NOWAIT 。如为
IPC_NOWAIT,则当消息队列已满,则此消息将不写入消息队列,将返回到调用进程,如果没有指明(即为 0 ),调用进程会被挂起,直到消息写入消息队列为止。
返回值成功:0;失败:-1

msgrcv()

目标往队列中发送一个消息
头文件sys/msg.h
函数原型int msgrcv (int msgid, struct msgbuf *msgp,int msgsz, long mtype,int msgflg)
参数msgid:消息标识 id 也就是 msgget 函数的返回值
msgp:指向消息缓冲区的指针
msgsz:消息文本的大小
mtyp:从消息队列中读取的消息的类型。如果为0,则会读取驻留消息队列时间最长的那一条消息,不论它是什么类型;
msgflg:为 0 时表示该进程将一直阻塞,直到有消息可读;还可设为IPC_NOWAIT ,表示如果没有消息可读时立刻返回 -1 ,否则进程被挂起。
返回值成功:0;失败:-1

msgctl()

目标消息队列控制系统调用
头文件sys/msg.h
函数原型int msgctl (int msgid, int cmd ,struct msgqid_ds *buf)
参数msgid:消息标识 id 也就是 msgget 函数的返回值
cmd:消息队列的处理命令 主要包括如下几种类型:
• IPC_RMID :从系统内核中删除消息队列 相当于命令ipcrm -q id
• IPC_STAT :获取消息队列的详细消息 包含权限 、 各种时间 、id 等;
• IPC_SET :设置消息队列的信息
• buf :存放消息队列状态的结构体指针 。
返回值成功:0;失败:-1

例子

msgsnd.c创建消息队列并发送3种不同类型(1,2,3)的消息,msgrcv.c通过命令行参数获取类型,然后输出对应类型的消息。

// msg.h
#ifndef _MSG_H_
#define _MSG_H_

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/stat.h>
#define MAX_TEXT 512
#define MSG_KEY 335
struct my_msg_st
{
    long my_msg_type;
    char text[MAX_TEXT];
};

#endif
// msgsnd.c
#include "msg.h"

int main()
{
    int index = 1;
    struct my_msg_st some_data;
    int msgid;
    char buffer[BUFSIZ];

    msgid = msgget((key_t)MSG_KEY, IPC_CREAT | S_IRUSR | S_IWUSR);
    if (msgid == -1)
    {
        perror("create message queue failed");
        return -1;
    }

    srand((int)time(0));
    while (index < 5)
    {
        printf("[%d]Enter some text: less than %d\n", msgid, MAX_TEXT);
        fgets(buffer, BUFSIZ, stdin);
        strcpy(some_data.text, buffer);

        some_data.my_msg_type = rand() % 3 + 1;
        printf("my msg_type=%ld\n", some_data.my_msg_type);

        if (msgsnd(msgid, (void *)&some_data, sizeof(some_data), 0) == -1)
        {
            fprintf(stderr, "msgsnd failed\n");
            exit(-1);
        }
        index++;
    }
    exit(0);
}
// msgrcv.c
#include "msg.h"

int main(int argc, char **argv)
{
    int msgid;
    int type;
    struct my_msg_st *my_data = (struct my_msg_st *)malloc(sizeof(struct my_msg_st));
    if (argc < 2)
    {
        printf("USAGE msgexample msgtype n");
        return 1;
    }
    type = atoi(argv[1]);
    if (type < 0 || type > 3)
    {
        printf("msgtype shoube be one of 1,2,3");
        return -1;
    }
    msgid = msgget((key_t)MSG_KEY, IPC_CREAT | S_IRUSR | S_IWUSR);
    if (msgid == -1)
    {
        perror("create message queue failed");
        return -1;
    }
    while (1)
    {
        if (msgrcv(msgid, (void *)my_data, sizeof(struct my_msg_st), (long)type, IPC_NOWAIT) != -1)
        {
            printf("The message type is %ld\n", my_data->my_msg_type);
            printf("The message content is %s\n", my_data->text);
        }
        else if (ENOMSG == errno)
        {
            printf("There is no any message matched to message type\n");
            break;
        }
    }
}
  • 结果:

    image-20221120192801927

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GaspardR

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值