10.Linux网络编程-POSIX消息队列

一:posix消息队列
消息队列可以认为是一个消息链表,某个进程往一个消息队列中写入消息之前,不需要另外某个进程在该队列上等待消息的达到,这一点与管道和FIFO相反。Posix消息队列与System V消息队列的区别如下:
1) 对Posix消息队列的读总是返回最高优先级的最早消息,对System V消息队列的读则可以返回任意指定优先级的消息;
2)当往一个空队列放置一个消息时,Posix消息队列允许产生一个信号或启动一个线程,System V消息队列则不提供类似的机制;

#include    <mqueue.h>
typedef int mqd_t;
mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);
@mq_open:函数会通过一个消息队列描述符(类型是mqd_t)建立一个进程与一个消息队列的连接,该函数会创建一个打开的消息队列的描述符, 其他函数就可以通过这个描述符操作消息队列.
@mqd_t:成功时为消息队列描述字,出错时为-1
@name:指向消息队列名称的指针,必须以一个斜杠符打头,并且不能再包含任何其他斜杠符
@oflag:表示想要访问(receive/send)消息队列的方式,O_RDONLY、O_WRONLY、O_RDWR三者之一,按位或上O_CREAT、O_EXCL、O_NONBLOCK
@mode:是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时,才需要提供该参数,表示默认访问权限,S_ISRUSR、S_ISWUSR、S_ISRGRP、S_ISWGRP、S_ISROTH、S_ISWOTH
@mq_attr:也是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时才需要。该参数用于给新队列设定某些属性,如果是空指针,那么就采用默认属性
	struct mq_attr
	{
		long mq_flags;//阻塞标志, 0或O_NONBLOCK
		long mq_maxmsg;//最大消息数
		long mq_msgsize;//每个消息最大大小
		long mq_curmsgs;//当前消息数
	};

#include    <mqueue.h>
int mq_close(mqd_t mqdes);
返回:成功时为0,出错时为-1。
功能:关闭已打开的消息队列。

#include    <mqueue.h>
int mq_unlink(const char *name)
返回:成功时为0,出错时为-1
功能:从系统中删除消息队列。

#include    <mqueue.h>
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, const struct mq_attr *attr, struct mq_attr *attr);
均返回:成功时为0, 出错时为-1

//POSIX消息队列可以通过以下两个函数来进行发送和接收消息
#include    <mqueue.h>
int mq_send(mqd_t mqd, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
ssize_t mq_receive(mqd_t mqd, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);

#ifdef __USE_XOPEN2K
mqd_t mq_timedsend(mqd_t mqdes, const char *msg_ptr,
                      size_t msg_len, unsigned msg_prio,
                      const struct timespec *abs_timeout);
mqd_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,
                      size_t msg_len, unsigned *msg_prio,
                      const struct timespec *abs_timeout);
#endif

@mqd:消息队列描述符;
@msg_ptr:指向消息体缓冲区的指针;
@msg_len:消息体的长度,其中mq_receive的该参数不能小于能写入队列中消息的最大大小,即一定要大于等于该队列的mq_attr结构中@mq_msgsize的大小。如果mq_receive中的msg_len小于该值,就会返回EMSGSIZE错误。POXIS消息队列发送的消息长度可以为0。
@msg_prio:消息的优先级;它是一个小于MQ_PRIO_MAX的数,数值越大,优先级越高。POSIX消息队列在调用mq_receive时总是返回队列中最高优先级的最早消息。如果消息不需要设定优先级,那么可以在mq_send是置msg_prio为0,mq_receive的msg_prio置为NULL。
@timespec:超时时间参数。

二:查找已创建的消息队列
不同于system v利用ipcs查看消息队列,posix消息队列存在于一个虚拟的文件夹中,默认在/dev/mqueue中;我们可以通过man 7 mq_overview命令查看其解释。
如果不存在文件夹,可以对mqueue的队列位置进行重定向,操作命令如下:

mkdir /dev/mqueue
mount -t mqueue none /dev/mqueue
//创建一个消息队列
//注意编译的时候需要连接rt库
//gcc -o demo demo.cpp -lrt
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int arg, char** argv)
{
	mqd_t mqid = mq_open("/demo", O_CREAT | O_RDWR, 0666, NULL);
	if (mqid == -1)
		ERR_EXIT("mq_open");
    struct mq_attr attr;
    if (mq_getattr(mqid, &attr) == -1)
    	ERR_EXIT("mq_getattr");

    if (attr.mq_flags == 0)
    	printf("mq_flags = 0, ");
    else
    	printf("mq_flags = O_NONBLOCK, ");
    printf("mq_maxmsg = %ld, ", attr.mq_maxmsg);
    printf("mq_msgsize = %ld, ", attr.mq_msgsize);
    printf("mq_curmsgs = %ld\n", attr.mq_curmsgs);
	mq_close(mqid);
	return 0;
}
#查看结果
root@epc:/home/share/project/demo# ./demo 
mq_flags = 0, mq_maxmsg = 10, mq_msgsize = 8192, mq_curmsgs = 0
root@epc:/home/share/project/demo# ls /dev/mqueue
/dev/mqueue/demo
root@epc:/home/share/project/demo# cat /dev/mqueue/demo
QSIZE:0          NOTIFY:0     SIGNO:0     NOTIFY_PID:0     
root@epc:/home/share/project/demo# 

POSIX消息队列大小的限制大小为800KB,该大小是整个消息队列的大小,不仅仅是最大消息数*消息的最大大小;还包括消息队列的额外开销。

#POSIX消息队列大小的限制
root@root~# ulimit -a |grep message
POSIX message queues     (bytes, -q) 819200

#POSIX消息队列默认的最大消息数
mq_maxmsg = 10

#POSIX消息队列消息的最大大小
mq_msgsize = 8192

三:mq_notify

mqd_t mq_notify(mqd_t mqdes, const struct sigevent *notification);
@mq_notify:建立或者删除消息到达通知事件
@mqd_t:成功返回0;失败返回-1
@mqdes:消息队列描述符
@notification:非空表示当消息到达且消息队列先前为空,那么将得到通知;NULL表示撤消已注册的通知
union sigval { /* Data passed with notification */
	int     sival_int;/* Integer value */
	void   *sival_ptr;/* Pointer value */
};

struct sigevent {
	int sigev_notify; /* Notification method */
	int  sigev_signo; /* Notification signal */
	union sigval sigev_value; /* Data passed with notification */
	void (*sigev_notify_function) (union sigval);
	/* Function for thread notification */
	void *sigev_notify_attributes;
	/* Thread function attributes */
};

sigev_notify 的取值有3个:
SIGEV_NONE:消息到达不会发出通知
SIGEV_SIGNAL:以信号方式发送通知,当设置此选项时,sigev_signo 设置信号的编号,且只有当信号为实时信号时才可以通过sigev_value顺带数据,参考这里。
SIGEV_THREAD:以线程方式通知,当设置此选项时,sigev_notify_function 即一个函数指针,sigev_notify_attributes 即线程的属性

mq_notify 函数注意点:
1、任何时刻只能有一个进程可以被注册为接收某个给定队列的通知
2、当有一个消息到达某个先前为空的队列,而且已有一个进程被注册为接收该队列的通知时,只有在没有任何线程阻塞在该队列的mq_receive调用的前提下,通知才会发出。
3、当通知被发送给它的注册进程时,其注册被撤消。进程必须再次调用mq_notify以重新注册(如果需要的话),重新注册要放在从消息队列读出消息之前而不是之后。

四:收发包实例

//创建和查看消息队列属性checkQ.cpp
//编译命令:g++ -o checkQ checkQ.cpp -lrt

#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define ERR_EXIT(m) do{ perror(m); exit(EXIT_FAILURE);}while(0);

int main(int arg, char** argv)
{
	mqd_t mqid = mq_open("/demo", O_CREAT|O_RDWR, 0666, NULL);
	if (mqid < 0)
		ERR_EXIT("mq_open");
	struct mq_attr attr;
	if (mq_getattr(mqid, &attr) == -1)
		ERR_EXIT("mq_open");
	if (attr.mq_flags == 0)
		printf("mq_flags = 0,");
	else
		printf("mq_flags = O_NONBLOCK, ");
	printf("mq_maxmsg = %ld, ", attr.mq_maxmsg);
	printf("mq_msgsize = %ld, ", attr.mq_msgsize);
	printf("mq_curmsgs = %ld\n", attr.mq_curmsgs);
	mq_close(mqid);
	return 0;
}

//发送消息到队列sendQ.cpp
//编译命令:g++ -o sendQ sendQ.cpp -lrt
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define ERR_EXIT(m) do{ perror(m); exit(EXIT_FAILURE);}while(0);

typedef struct stu{
	char name[32];
	int age;
}STU;

int main(int argc, char** argv)
{
	if (argc != 2)
		ERR_EXIT("Usage: %s <prio>\n");

	mqd_t mqid = mq_open("/demo", O_WRONLY);
	if (mqid == -1)
		ERR_EXIT("mq_open");

	STU student = {"demo", 20};
	unsigned int prio = atoi(argv[1]);
	mq_send(mqid, (const char*)&student, sizeof(STU), prio);
	mq_close(mqid);
	return 0;
}

//接受消息队列receiveQ.cpp
//编译命令:g++ -o receiveQ receiveQ.cpp -lrt
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define ERR_EXIT(m) do{ perror(m); exit(EXIT_FAILURE);}while(0);

typedef struct stu{
	char name[32];
	int age;
}STU;

int main(int argc, char** argv)
{
	mqd_t mqid = mq_open("/demo", O_RDONLY);
	if (mqid == -1)
		ERR_EXIT("mq_open");

	struct mq_attr attr;
	if (mq_getattr(mqid, &attr) == -1)
		ERR_EXIT("mq_open");

	STU student;
	unsigned int prio;
	mq_receive(mqid, (char*)&student, attr.mq_msgsize, &prio);
	printf("name=%s, age=%d, prio=%d\n", student.name, student.age, prio);
	mq_close(mqid);
	return 0;
}

//利用mq_notify实现信号触发接收队列notifyQ.cpp
//编译命令:g++ -o notifyQ notifyQ.cpp -lrt
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>

#define ERR_EXIT(m) do{ perror(m); exit(EXIT_FAILURE);}while(0);

typedef struct stu{
	char name[32];
	int age;
}STU;

size_t size;
mqd_t mqid;
struct sigevent sigev;

void handle(int sig)
{
	mq_notify(mqid, &sigev);
	STU student;
	unsigned int prio;
	mq_receive(mqid, (char*)&student, size, &prio);
	printf("name=%s, age=%d, prio=%d\n", student.name, student.age, prio);
}

int main(int argc, char** argv)
{
	mqid = mq_open("/demo", O_RDONLY);
	if (mqid == -1)
		ERR_EXIT("mq_open");

	struct mq_attr attr;
	if (mq_getattr(mqid, &attr) == -1)
		ERR_EXIT("mq_open");
	size = attr.mq_msgsize;
	signal(SIGUSR1, handle);
	sigev.sigev_notify = SIGEV_SIGNAL;
	sigev.sigev_signo = SIGUSR1;
	mq_notify(mqid, &sigev);
	while(1)
		pause();
	mq_close(mqid);
	return 0;
}

先运行./checkQ 用mq_open 创建了一个消息队列并mount 到/dev/mqueue 上,可以查看状态:

simba@ubuntu:/dev/mqueue$ cat /dev/mqueue/demo
QSIZE:0 NOTIFY:0 SIGNO:0 NOTIFY_PID:0

接着运行./notifyQ 再查看状态:

simba@ubuntu:/dev/mqueue$ cat /dev/mqueue/demo
QSIZE:0 NOTIFY:0 SIGNO:10 NOTIFY_PID:3572

准备通知的信号是10号,即SIGUSR1,等待的进程pid即./notifyQ ,此时./notifyQ 进程是阻塞的,正在等待其他进程往消息队列写入消息,现在运行./sendQ

simba@ubuntu:~/Documents/code/linux_programming/UNP/posix$ ./sendQ
Usage: ./mq_send
simba@ubuntu:~/Documents/code/linux_programming/UNP/posix$ ./sendQ 1
mq_open succ
simba@ubuntu:~/Documents/code/linux_programming/UNP/posix$ ./sendQ 1
mq_open succ
simba@ubuntu:~/Documents/code/linux_programming/UNP/posix$

回头看./notifyQ 的输出:

simba@ubuntu:~/Documents/code/linux_programming/UNP/posix$ ./notifyQ
name=demo, age=20, prio=1
name=demo, age=20, prio=1

即./sendQ 发送的两条消息都接收到了,需要注意的是虽然通知是一次性的,但我们在消息处理函数再次注册了通知,故能多次接收到通知,但通知只发生在消息队列由空到不空的时候,如果先运行./sendQ 先往消息队列发送了n条消息,那么执行./notifyQ 是不会接收到通知的,一直阻塞着。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值