Linux系统编程三:IPC消息队列

公众号:CppCoding

消息队列可以认为是一个消息链表,其含有优先级。在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。

  • 消息队列是具有随内核的持续性。一直存在到内核的重新自举或者显示的删除该对象。
  • 查看POSIX消息队列:

ls /dev/mqueue

  • 命名规则
    只能以 / 开头。

  • 接口
    头文件:mqueue.h
    库:librt.so(-lrt //链接)

  • 结构体

struct mq_attr //消息队列属性

成员 含义 说明
mq_flags 标志 在mq_open时被初始化,在mq_setattr设置,其值为0或者O_NONBLOCK(O_RDONLY,O_WRONLY,O_RDWR
ma_msgsize 队列每个消息长度的最大值 只能在mq_open时被初始化
mq_curmsgs 当前队列消息的个数 在mq_getattr获取
  • o_flags标志:(O_RDONLY,O_WRONLY,O_RDWR
  • mode:权限
  • 函数
操作 函数
创建消息队列 mdq_t mq_open(const char *name,int oflag,mode_t mode,struct mq_attr *attr)
删除消息队列 int mq_unlink (const char *name)
打开消息队列 int mq_open(const char *name,int oflag)
关闭消息队列 int mq_close(mqd_t mqdes)
发送消息 int mq_send(mqd_t mqdes,const char* msg_ptr,size_t msg_len,unsigned msg_prio),最后一个参数是优先级,数字越大优先级越高(注意优先级有上限)
接受信息 int mq_receive(mqd_t mqdes,const char* msg_ptr,size_t msg_len,unsigned *msg_prio),总是返回指定队列最高优先级的最早消息,而且该优先级能随该消息内容及其长度一起返回,其返回值是接受信息的字节数。
设置消息队列属性 int mq_setattr(mqd_t mqdes,struct mq_attr *newattr,struct mq_attr *oldattr)
获取消息队列属性 int mq_getattr(mqd_t mqdes,struct mq_attr *attr)

待发送信息的大小和优先级必须作为命令行参数指定。
mq_receive的len参数的值不能小于加到指定队列的消息的最大消息长度。也就是在打开消息队列后,要进行mq_getattr确定最大消息大小,然后分配那样大小的缓冲区。

mq_setattr只能设置阻塞或者非阻塞。

mq_close功能和关闭一个打开文件的close类似:调用进程可以不再使用该描述符,但是其消息队列并不从系统中删除。
要从系统中删除某个消息队列,必须使用mq_unlink
每个消息队列都有一个保存当前打开着的描述符的引用计数器,使用mq_unlink从系统中删除该名字意味着引用计数减一,若变为0才是真正的拆除该队列。
posix消息队列具备随内核的持续性。也就是说,即使当前没有进程打开某个消息队列,该队列及其上的各个消息也一直存在,直到引用计数为0才删除。

  • 创建消息队列
mqd_t mq_open(const char *name,int oflag,mode_t mode,struct mq_attr *attr)
//attr选项为NULL,是默认属性
//若是不为NULL,在创建之前就要设置好struct mq_attr attr的相关变量
参数 含义
name posix IPC 名字,格式为/somename
oflag 标志
mode 权限
attr 队列属性,阻塞attr.mq_flag=0;非阻塞attr.mq_flag=NONBLOCK
  • 标志
标志 作用
O_CREAT 没有该对象则创建
O_EXCL 如果O_CREAT指定,但name不存在,就返回错误
O_NONBLOCK 以非阻塞的方式打开消息队列
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
  • 权限

自行在man手册查看,此处可以0000形式设置
例如0777:即所有人有所有权限

  • 返回值
返回值 含义
-1 出错
其他 消息队列描述符
  • 示例
  • 创建一个名字为/tem.test的POSIX消息队列
#include <stdio.h> // perror()
#include <mqueue.h> // mq_open()
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH) //0644
int main(){
    struct mq_attr attr;
    attr.mq_maxmsg = 100;
    attr.mq_msgsize = 100;
    mqd_t mqd = mq_open("/tmp.test",O_CREAT,FILE_MODE,&attr);
    if(-1 == mqd){
        perror("mq_open error");
        return 1;
    }
}

编译时需加上 -lrt

  • 删除消息队列

int mq_unlink(const char *name)

  • 参数
参数 含义
name posixIPC名字
  • 返回值
返回值 含义
-1 出错
0 成功
  • 打开消息队列

mqd_t mq_open (const char*name,int oflag)

  • 返回值
返回值 含义
-1 出错
其他 描述符
  • 关闭消息队列

int mq_close(mqd_t mqdes)

参数:mqdes含义是消息队列描述符

返回值 含义
-1 出错
0 成功
  • 发送消息

int mq_send(mqd_t mqdes,const char* msg_ptr,size_t msg_len,unsigned msg_prio)

  • 参数
参数 含义
msg_ptr 消息的指针
msg_len 消息长度,不能大于属性值mq_msgsize的值
msg_prio 优先级,小于MQ_PRIO_MAX,数值越大,优先级越高
#include <stdio.h> // perror()
#include <mqueue.h> // mq_open() mq_send() mq_close()
int main(int argc,char* argv[]){
    mqd_t mqd = mq_open("/tmp.test",O_WRONLY); // 可以设置O_NONBLOCK
    if(-1 == mqd){
        perror("mq_open error");
        return 1;
    }
    const char* msg = "HelloWorld";
    if(-1 == mq_send(mqd,msg,sizeof(msg),1)){
        perror("mq_send error");
        mq_close(mqd);
        return 1;
    }
    mq_close(mqd);
    return 0;
}
  • 注意
    消息在队列中将按照优先级大小顺序来排列消息
    消息队列已满,mq_send()函数将阻塞,直到有可用空间再次允许放置消息。
    如果O_NONBLOCK被指定,mq_send()那么将不会阻塞,而是返回EAGAIN错误。
  • 接收消息

ssize_t mq_receive(mqd_t mqdes,char *msg_ptr,size_t msg_len,unsigned *msg_prio)

  • 参数
参数 含义
msg_ptr 消息指针
msg_len 消息长度,不能大于属性值mq_msgsize的值
msg_prio 优先级
返回值 含义
-1 出错
正数 收到消息的长度
  • 注意

POSIX消息队列在调用mq_receive()时总是返回队列中最高优先级的最早消息。
如果队列空,mq_receive()函数将阻塞,直到消息队列中有新的消息。
如果O_NONBLOCK被指定,mq_receive()那么将不会阻塞,而是返回EAGAIN错误。

  • 设置消息队列属性

int mq_setattr(mqd_t mqdes,strcut mq_attr* newattr,struct mq_attr* oldattr)

  • 参数
参数 含义
mqdes 消息队列描述符
newattr 新属性,只能设置mq_flag:O:NONBLOCK
oldattr 旧属性
返回值 含义
-1 出错
0 成功
#include <stdio.h> // perror()
#include <string.h> // bzero()
#include <mqueue.h> // mq_open() mq_setattr() mq_close()
int main(){
    mqd_t mqd = mq_open("/tmp.test",O_RDWR);
    if(-1 == mqd){
        perror("mq_open error");
        return 1;
    }
    struct mq_attr new_attr;
    bzero(&new_attr,sizeof(new_attr));
 
    new_attr.mq_flags = O_NONBLOCK;
    struct mq_attr attr;
    if(-1 == mq_setattr(mqd,&new_attr,&attr)){
        perror("mq_setattr error");
        mq_close(mqd);
        return 1;
    }
    printf("flag:%ld,Max msg:%ld,Max msgsize:%ld,Cur msgnun:%ld\n",attr.mq_flags,attr.mq_maxmsg,attr.mq_msgsize,attr.mq_curmsgs);
    mq_close(mqd);
}
  • 获取消息队列属性

int mq_getattr(mqd_t mqdes,struct mq_attr * attr)

参数 含义
mqdes 消息队列描述符
attr 属性
返回值 含义
-1 出错
0 成功
#include <stdio.h> // perror()
#include <mqueue.h> // mq_open() mq_getattr() mq_close()
int main(){
    mqd_t mqd = mq_open("/tmp.test",O_RDONLY);
    if(-1 == mqd){
        perror("mq_open error");
        mq_close(mqd);
        return 1;
    }
    struct mq_attr attr;
    mq_getattr(mqd,&attr);
    printf("flag:%ld,Max msg:%ld,Max msgsize:%ld,Cur msgnun:%ld\n",attr.mq_flags,attr.mq_maxmsg,attr.mq_msgsize,attr.mq_curmsgs);
   mq_close(mqd);
    return 0;
}
  • 注意

mq_setattr()可以设置的属性只有mq_flags,用来设置或清除消息队列的非阻塞标志。newattr结构的其他属性被忽略。

mq_maxmsg和mq_msgsize属性只能在创建消息队列时通过mq_open()来设置。

mq_open()只会设置该两个属性,忽略另外两个属性。

mq_curmsgs属性只能被获取而不能被设置。

mq_notify函数

异步事件通知,以告知何时有一个消息放置在某个空的消息队列。这种通知有两种方式:
1.产生一个信号
2.创建一个线程来执行一个指定的函数

这种通知通过调用mq_notify建立

#include <mqueue.h>
int mq_notify(mqd_t mqdes,const struct sigevent *notification);
//该函数为指定队列建立或删除异步事件通知
#include <signal.h>
union sigval{
	int sival_int;
	void *sival_ptr;
};

struct sigevent{
	int sigev_notify;
	int sigev_signo;
	union sigval sigev_value;
	void (*sigev_notify_function)(union sigval);
	pthread_attr_t *sigev_notify_attributes;
};

1.如果notification参数非空,那么当前进程希望在有一个消息到达所指定的先前为空的队列得到通知。“该进程被注册为接受该队列的通知”
2.如果notification为空指针,且当前进程目前被注册为接受所指定队列的通知,那么已存在的注册将被注销。
3.任意时刻只有一个进程被注册为接收某个队列的通知。
4,.当该通知被发送给他的注册进程时,其注册即被撤销。该进程必须再次调用mq_notify重新注册。

Linux系统编程三:IPC消息队列_第1张图片

Linux系统编程三:IPC消息队列_第2张图片

Posix信号:异步信号安全函数

Linux系统编程三:IPC消息队列_第3张图片
避免从信号处理程序中调用任何函数的方法之一是:让处理程序设置一个全局标志,由某个线程检查该标志以确定何时接收到一个消息。
Linux系统编程三:IPC消息队列_第4张图片Linux系统编程三:IPC消息队列_第5张图片

问题:
在第一个消息被读出之前有两个消息到达。通知只是在有一个消息被放置在某个空队列上时才发出。如果在能够读出第一个消息前有两个消息到达,那么只有一个消息发出:我们读出第一个消息,并调用sigsuspend等待另一个消息,而对应他的通知永远也不会发出。另一个放置在队列等待的消息,被忽略。

  • 使用非阻塞的mq_receive读取消息队列

Linux系统编程三:IPC消息队列_第6张图片Linux系统编程三:IPC消息队列_第7张图片

  • 使用sigwait代替信号处理程序的信号通知

上一个程序通过调用sigsuspend阻塞,以等待某个消息到达。当有一个消息被放置在空的消息队列中,该信号产生,主线程被阻止,信号处理程序执行并设置mqflag变量,主程序在执行,发现mq_flag非零,于是读出该消息。更为简易的方式是阻塞在某个函数,仅仅等待该辛哈的递交,而不是让内核执行一个只为设置一个标志的信号处理程序。

#include <signal.h>
int sigwait(const sigset_t *set,int *sig);

Linux系统编程三:IPC消息队列_第8张图片文章中涉及的signal函数,getopt函数,sigemptyset等函数会另开一个新章节细加说明。

你可能感兴趣的:(Linux系统编程)