Linux进程间通信之消息队列

Linux提供了两种消息队列机制,POSIX Messages以及System V Message Queues。

POSIX Messages

打开或创建消息队列mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr)

参数名说明
name消息队列名,任意取。必须从跟目录开始,且只能包含一个‘/’ ,例如:/example;错误:/example/anything
oflag消息队列的操作方式:O_RDONLY只读,O_WRONLY只写,O_RDWR可读可写,O_CREAT创建。更多请参考man 3 mq_open
mode消息队列权限
attr消息队列属性,下面mq_getattr( )会讲到
返回值说明
非负整数成功
-1失败
关闭消息队列int mq_close(mqd_t mqdes)
参数说明
mqdes消息队列描述符,mq_open()的返回值
返回值
0成功关闭
-1关闭失败,原因可能为消息队列不存在,或者关闭未打开的消息队列,具体看errno的值
删除消息队列int mq_unlink(const char *name)
参数说明
:–
name消息队列的标识名,与mq_open()第一个参数一样
返回值说明
0删除成功
-1删除失败,并设置errno,通过errno可以知道失败原因

发送消息
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);
注意:打开消息队列的时候,可以选择阻塞或非阻塞方式。在消息队列为满的情况下,以阻塞的方式往消息队列写入数据会等待,直到消息队列有空余位置才往里写;非阻塞方式写会报错立即返回,设置errnoEAGAIN

参数说明
mqdes同上
msg_ptr需要发送的消息。不一定是字节流,可以定义你需要的数据结构,发送的时候强制转换为(const char *)
msg_len发送的消息大小。内核对其大小边界作了限定(跟内核版本有关),详细请man mq_overiew(7)
msg_prio消息优先级,值越大优先级越高。
abs_timeout阻塞的超时时间。根据前面的描述,阻塞调用mq_send()会一直等待直到队列有空间为止,但是mq_timedsend()可以设置等待时间,超时就返回,不会无限等待。
返回值说明
0发送成功
-1发送失败,并设置errno,通过errno可以知道失败原因
获取消息
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio, const struct timespec *abs_timeout);
参数说明
:–
mqdes同上
msg_ptr从队列取出的消息。不一定是字节流,可以强制转换为你需要的数据结构
msg_len需要接受的消息长度。这个值必须大于或等于消息队列大小,可以通过msg_getattr()获取消息队列大小`
msg_prio消息优先级,值越大优先级越高。
abs_timeout阻塞调用mq_receive()会一直等待直到队列有消息为止,mq_timedreceive()可以设置等待时间,超时就返回。
返回值说明
正整数接收到的消息长度。
-1接收失败,并设置errno,通过errno可以知道失败原因
获取队列属性int mq_getattr(mqd_t mqdes, const struct mq_attr *attr);
//属性数据结构
struct mq_attr
{
	long mq_flags;       /* Flags: 0 or O_NONBLOCK */
	long mq_maxmsg;      /* Max. # of messages on queue */		//消息最大条数
	long mq_msgsize;     /* Max. message size (bytes) */		//单条消息大小
	long mq_curmsgs;     /* # of messages currently in queue */	//队列中当前消息条数
};
参数说明
mqdes同上
attr从内核中获取当前消息队列的属性
设置队列属性int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);
注意:只能修改mq_attr中mq_flags成员:0阻塞或者O_NONBLOCK非阻塞。
参数说明
:–
mqdes同上
newattr目标属性。要设置的新的属性
oldattr原有属性。设置前先通过mq_getattr()获取
POSIX测试代码

接收进程:mq_read.c

#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#define MQ_PATH "/test"

typedef struct{
    int year;
    char name[16];
}Student;

int main()
{
    mqd_t mqfd = mq_open(MQ_PATH, O_RDONLY, 0666, NULL);
    if(mqfd == (mqd_t) -1)
    {
        perror("mq_open");
        if(errno != EEXIST)
        {
            return -1;
        }
    }

    struct mq_attr attr;
    memset(&attr, 0x00, sizeof(attr));
    mq_getattr(mqfd, &attr);//默认消息条数为10,单条最大8K

    int rc = -1;
    unsigned int prio;
    Student *stu = NULL;
    char *msgbuf = (char *)malloc(attr.mq_msgsize);
    while(1)
    {
        rc = mq_receive(mqfd, msgbuf, attr.mq_msgsize, &prio);
        if(rc > 0)
        {
            stu = (Student *)msgbuf;
            printf("Recv: prio = %d, year = %d, name = %s\n", prio, stu->year, stu->name);
            if(strcmp("quit", stu->name) == 0)
            	break;
        }
    }
    mq_unlink(MQ_PATH);
    return 0;
}

发送进程0:mq_send0.c,优先级为0的进程

#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

#define MQ_PATH "/test"

typedef struct{
    int year;
    char name[16];
}Student;

int main()
{
    mqd_t mqfd = mq_open(MQ_PATH, O_WRONLY|O_CREAT, 0666, NULL);
    if(mqfd == (mqd_t) -1)
    {
        perror("mq_open");
        if(errno != EEXIST)
        {
            return -1;
        }
        else
        {
            mq_unlink(MQ_PATH);
            mqfd = mq_open(MQ_PATH, O_WRONLY|O_CREAT, 0666, NULL);
        }
    }
    
    int rc = -1, count = 0;
    unsigned int prio;
    Student stu = {18, "XiaoMing"};

    while(1)
    {
        rc = mq_send(mqfd, (const char *)&stu, sizeof(stu), 0);
        if(rc == 0)
        {
            count++;
            if(count == 10)
            {
                memset(stu.name, 0x00, sizeof(stu.name));
                strcpy(stu.name, "quit");
                mq_send(mqfd, (const char *)&stu, sizeof(stu), 0);//消息优先级为0
                break;
            }
            printf("send0: year = %d, name = %s\n", stu.year, stu.name);
        }
        sleep(2);
    }

    mq_close(mqfd);
    return 0;
}

发送进程1:mq_send1.c,优先级为1的进程

#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

#define MQ_PATH "/test"

typedef struct{
    int year;
    char name[16];
}Student;

int main()
{
    mqd_t mqfd = mq_open(MQ_PATH, O_WRONLY, 0666, NULL);
    if(mqfd == (mqd_t) -1)
    {
        perror("mq_open");
        if(errno != EEXIST)
        {
            return -1;
        }
    }

    int rc = -1;
    unsigned int prio;
    Student stu;
    stu.year = 18;
    strcpy(stu.name, "XiaoMing");
    int count = 0;
    while(1)
    {
        rc = mq_send(mqfd, (const char *)&stu, sizeof(stu), 1);
        if(rc == 0)
        {
            count++;
            if(count == 10)
            {
                memset(stu.name, 0x00, sizeof(stu.name));
                strcpy(stu.name, "quit");
                mq_send(mqfd, (const char *)&stu, sizeof(stu), 0);
                break;
            }
            printf("send1: year = %d, name = %s\n", stu.year, stu.name);
        }
        sleep(2);
    }

    mq_close(mqfd);
    return 0;
}

运行顺序:先运行send0,因为要创建消息队列,注意下面两种情况:
1.等send0发送了3条之后,再启动send1,sen1发了2条之后,启动read进程,观察发现read先收到send1发送的消息,验证了优先级的问题(这个应该不难理解);
2.先不启动read进程,在send0和send1加起来共发送10条消息时,发现两个send进程都阻塞不发送了,此时启动read进程,两个send进程会继续发送消息。

System V消息队列

Linux中定义了通用的消息数据结构:

struct msgbuf 
{
	long mtype;       /* message type, must be > 0 */
	char mtext[1];    /* message data */
};

消息的数据结构可以由用户自己定义,但是数据结构的第一个成员必须为long整形的消息类型。
操作流程:
1.生成key:key_t ftok(const char *pathname, int proj_id);

参数说明
pathname路径,此路径为真实存在,不存在的话会创建
proj_id消息队列对象标识,任意大于0的数

2.创建消息队列:int msgget(key_t key, int msgflg);成功返回msgid,失败返回-1

参数说明
keyftok()生成
msgflg指定消息操作模式以及权限。格式:`IPC_CREAT
3.接收消息:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数说明
:–
msqid消息队列标识
msgp接收到的消息
msgsz要接收的消息长度
msgtyp消息类型。如果消息队列里面存在多种类型消息,接收进程只接收指定类型消息。
msgflg消息队列的操作模式。
返回值说明
len成功接收len字节数据
-1失败
4.发送消息:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数说明
:–
msqid同上
msgp需要发送的消息,数据结构可以自定义,但结构体第一个成员必须是long int的消息类型,mtype
msgsz要发送的消息长度。
msgflg消息队列的操作模式。
返回值说明
0发送成功
-1失败

5.消息队列销毁:int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数说明
msqid同上
cmd消息队列操作命令。删除使用:IPC_RMID,更多类型请man msgctl
buf删除使用:NULL
返回值说明
0删除成功
-1失败
删除消息队列还可以在shell命令行下操作:
先通过ipcs命令查看消息队列的key或msgid
在这里插入图片描述
然后命令行下输入:
ipcrm --queue-key 0x31010164
或者
ipcrm --queue-id 98304
System V测试代码

这里创建三个接收进程,分别为msg_rcv.c,msg_rcv1.c,msg_rcv2.c,一个发送进程msg_send.c,以及头文件msgq.h,代码如下:
msgq.h

#ifndef __MSGQ_H__
#define __MSGQ_H__

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <error.h>

#define MSG_PATH    "./msg.txt"

typedef struct student_t{
    char name[16];
    int year;
}Student;

typedef struct message_t{
    long mtype;
    Student Info;
}Message;

typedef enum Info_e{
    STUDENT_INFO = 1,
    TEACHER_INFO,
    QUIT,
}Info_type;

#endif

msg_rcv.c

#include "msgq.h"

int main(void)
{
    key_t key = ftok(MSG_PATH, '1');
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }

    int msgid = msgget(key, O_RDONLY);
    if(msgid < 0)
    {
        perror("msgget");
        return -1;
    }
	
    int rc = -1, count = 0;
    Message msg;
    while(1)
    {
        memset(&msg, 0x00, sizeof(Message));
        if((rc = msgrcv(msgid, &msg, sizeof(Student), STUDENT_INFO, 0)) > 0)//接收学生类型消息
        {
            printf("rcv[%d]--->type[%d], name[%s], year[%d]\n", count++, STUDENT_INFO, msg.Info.name, msg.Info.year);
            if(strcmp("quit", msg.Info.name) == 0)//接收到quit消息,退出。这里不删除消息队列,有专门的进程删除它
            {
                break;
            }
        }
    }
    return 0;
}

msg_rcv1.c

#include "msgq.h"

int main(void)
{
    key_t key = ftok(MSG_PATH, '1');
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }

    int msgid = msgget(key, O_RDONLY);
    if(msgid < 0)
    {
        perror("msgget");
        return -1;
    }

	int rc = -1, count = 0;
    Message msg;
    while(1)
    {
        memset(&msg, 0x00, sizeof(Message));
        if((rc = msgrcv(msgid, &msg, sizeof(Student), TEACHER_INFO, 0)) > 0)//接收老师类型消息
        {
            printf("rcv[%d]--->type[%d], name[%s], year[%d]\n", count++, TEACHER_INFO, msg.Info.name, msg.Info.year);
            if(strcmp("quit", msg.Info.name) == 0)//接收到quit消息,退出。这里不删除消息队列,有专门的进程删除它
            {
                break;
            }
        }
    }
    return 0;
}

msg_rcv2.c

#include "msgq.h"

int main(void)
{
    key_t key = ftok(MSG_PATH, '1');
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }

    int msgid = msgget(key, O_RDONLY);
    if(msgid < 0)
    {
        perror("msgget");
        return -1;
    }

    int rc = -1, count = 0;
    Message msg;
    while(1)
    {
        memset(&msg, 0x00, sizeof(Message));
        if((rc = msgrcv(msgid, &msg, sizeof(Student), QUIT, 0)) > 0)//接收退出类型消息
        {
            printf("rcv[%d]--->type[%d], name[%s], year[%d]\n", count++, STUDENT_INFO, msg.Info.name, msg.Info.year);
            msgctl(msgid, IPC_RMID, NULL);//删除消息队列
            break;
        }
    }
    return 0;
}

msg_send.c

#include "msgq.h"

int main(void)
{
    key_t key = ftok(MSG_PATH, '1');
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }

    int msgid = msgget(key, IPC_CREAT|O_WRONLY|0666);
    if(msgid < 0)
    {
        perror("msgget");
        return -1;
    }

    int rc = -1, count = 0;
    Message msg;
    msg.mtype = STUDENT_INFO;
    msg.Info.year = 18;
    strcpy(msg.Info.name, "Xiaoming");

    while(1)
    {
        count++;
        if(count % 2 != 0)
        {
            msg.mtype = TEACHER_INFO;
            rc = msgsnd(msgid, &msg, sizeof(Student), 0);
        }
        else
        {
            msg.mtype = STUDENT_INFO;
            rc = msgsnd(msgid, &msg, sizeof(Student), 0);
        }
        printf("Send[%d]---->type[%ld],name[%s], year[%d]\n", count, msg.mtype, msg.Info.name, msg.Info.year);

        if(count == 10)
        {
            memset(msg.Info.name, 0x00, sizeof(msg.Info.name));
           	strcpy(msg.Info.name, "quit");
            msg.mtype = STUDENT_INFO;
            rc = msgsnd(msgid, &msg, sizeof(Student), 0);
            printf("Send[%d]---->type[%ld],name[%s], year[%d]\n", count, msg.mtype, "quit", msg.Info.year);

            sleep(1);

            msg.mtype = TEACHER_INFO;
            rc = msgsnd(msgid, &msg, sizeof(Student), 0);
            printf("Send[%d]---->type[%ld],name[%s], year[%d]\n", count, msg.mtype, "quit", msg.Info.year);

            sleep(1);

            msg.mtype = QUIT;
            rc = msgsnd(msgid, &msg, sizeof(Student), 0);
            printf("Send[%d]---->type[%ld],name[%s], year[%d]\n", count, msg.mtype, "quit", msg.Info.year);
            break;
        }
        sleep(2);
    }
    return 0;
}

简易Makefile

all: msg_rcv msg_rcv1 msg_rcv2 msg_send

.PHONY = all

msg_rcv:msg_rcv.o
    cc -o msg_rcv msg_rcv.o 
msg_rcv1:msg_rcv1.o
    cc -o msg_rcv1 msg_rcv1.o 
msg_rcv2:msg_rcv2.o
    cc -o msg_rcv2 msg_rcv2.o 
msg_send:msg_send.o
    cc -o msg_send msg_send.o 

.PHONY = clean
clean:
    @rm -rf msg_rcv msg_rcv1 msg_rcv2 msg_send *.o

现象如下图:
接收学生消息进程
在这里插入图片描述
接收老师消息进程
在这里插入图片描述

接收退出消息进程
在这里插入图片描述
It’s over.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值