《UNIX网络编程 卷2》 笔记: Posix消息队列(2)

Posix消息队列允许异步事件通知,以告知何时有一个消息放入到了某个空消息队列中。有两种通知的方式可供进程选择:

    1. 产生一个信号

    2. 创建一个线程处理

这种通知是通过mq_notify函数建立的。

int mq_notify(mqd_t mqdes, const struct sigevent *sevp);
下面的例子演示进程怎样注册为接收一个信号通知。

#include "unpipc.h"

mqd_t mqd;
void *buff;
struct mq_attr attr;
struct sigevent sigev;

static void sig_usr1(int signo);

int main(int argc, char **argv)
{
	if (argc != 2)
		err_quit("usage: mqnotifysig1 <name>");
	
	mqd = Mq_open(argv[1], O_RDONLY);
	Mq_getattr(mqd, &attr);
	buff = Malloc(attr.mq_msgsize);

	/*注册信号处理函数*/
	Signal(SIGUSR1, sig_usr1);
	/*产生一个SIGUSR1 信号通知*/
	sigev.sigev_notify = SIGEV_SIGNAL;
	sigev.sigev_signo = SIGUSR1;
	/*将进程注册为接收该消息队列的异步事件通知*/
	Mq_notify(mqd, &sigev);

	for ( ; ; )
		pause();
	exit(0);
}

static void sig_usr1(int signo)
{
	ssize_t n;

	/*重新注册*/
	Mq_notify(mqd, &sigev);
	n = Mq_receive(mqd, buff, attr.mq_msgsize, NULL);
	printf("SIGUSR1 received read %ld bytes\n", (long)n);
	return;
}
当有一个消息放到空队列时,进程就会接收到一个指定的信号,信号处理函数从队列中获取消息。

上面的程序有个隐含的错误是在信号处理函数中调用了mq_notify、mq_receive、printf等非异步信号安全函数。

异步信号安全函数是POSIX定义的可以在信号处理函数中安全调用的函数,下表所示的都是异步信号安全函数:


mq_notify、mq_receive和printf函数都不在表中,所以上面的程序是不正确的。

为了避免在信号处理函数中调用非异步信号安全函数,我们可以只让信号处理函数处理一个标志,然后进程检查标志以确定何时收到一个消息。改进后的代码如下:

#include "unpipc.h"

volatile sig_atomic_t mqflag;
static void sig_usr1(int);

int main(int argc, char **argv)
{
	mqd_t mqd;
	void *buff;
	ssize_t n;
	sigset_t zeromask, newmask, oldmask;
	struct mq_attr attr;
	struct sigevent sigev;

	if (argc != 2)
		err_quit("usage: mqnotifysig2 <name>");

	mqd = Mq_open(argv[1], O_RDONLY);
	Mq_getattr(mqd, &attr);
	buff = Malloc(attr.mq_msgsize);

	Sigemptyset(&zeromask);
	Sigemptyset(&newmask);
	Sigemptyset(&oldmask);
	Sigaddset(&newmask, SIGUSR1);
	/*注册信号处理函数*/
	Signal(SIGUSR1, sig_usr1);
	/*将进程注册为接收该消息队列的异步事件通知*/
	sigev.sigev_notify = SIGEV_SIGNAL;
	sigev.sigev_signo = SIGUSR1;
	Mq_notify(mqd, &sigev);

	for ( ; ; ) {
		/*阻塞SIGUSR1信号*/
		Sigprocmask(SIG_BLOCK, &newmask, &oldmask);
		while (mqflag == 0)
			/*该函数原子性地将进程投入睡眠,并把它的信号
			掩码复位成zeromask,直到某个信号发生,直到该信号
			的信号处理函数返回之后才返回,并将信号掩码恢复
			成调用该函数之前的信号掩码*/
			sigsuspend(&zeromask);
		mqflag = 0;
		/*重新注册*/
		Mq_notify(mqd, &sigev);
		/*接收消息*/
		n = Mq_receive(mqd, buff, attr.mq_msgsize, NULL);
		printf("read %ld bytes\n", (long)n);
		/*解阻塞SIGUSR1信号*/
		Sigprocmask(SIG_UNBLOCK, &newmask, NULL);
	}
	exit(0);
}

static void
sig_usr1(int signo)
{
	mqflag = 1;
	return;
}
主程序for循环先屏蔽SIGUSR1信号,然后在变量mqflag为0时调用sigsuspend函数等待信号发生。当一个消息被放置到空队列时,进程接收到SIGUSR1信号,mqflag被置1,sigsuspend函数返回。然后mqflag被复位,消息从队列中被取出。

值得注意的是为什么要屏蔽SIGUSR1信号!因为信号处理函数和进程共享变量mqflag,如果不屏蔽该信号,那么可能程序刚执行完while(mqflag == 0)语句之后和执行sigsuspend函数之间产生了SIGUSR1信号,这样sigsuspend函数就等待不到该信号了,就好像该信号丢失了!for循环最后再解除SIGUSR1信号的屏蔽是为了避免信号打断mq_receive函数的执行。

然而上面的程序也还存在一个问题:通知只是在有一个消息被放置到某个空消息队列时才发出。如果在能够读出第一个消息前有两个消息到达,那么只有一个通知被发出。我们调用mq_receive只读出第一个消息,而其他的消息被我们忽略了。

解决这个问题的办法是当接收到信号时,总是以非阻塞模式读消息队列。代码如下:

#include "unpipc.h"

volatile sig_atomic_t mqflag;
static void sig_usr1(int);

int main(int argc, char **argv)
{
	mqd_t mqd;
	void *buff;
	ssize_t n;
	sigset_t zeromask, newmask, oldmask;
	struct mq_attr attr;
	struct sigevent sigev;

	if (argc != 2)
		err_quit("usage: mqnotifysig3 <name>");

	/*!!!以非阻塞方式打开消息队列*/
	mqd = Mq_open(argv[1], O_RDONLY | O_NONBLOCK);
	Mq_getattr(mqd, &attr);
	buff = Malloc(attr.mq_msgsize);

	Sigemptyset(&zeromask);
	Sigemptyset(&newmask);
	Sigemptyset(&oldmask);
	Sigaddset(&newmask, SIGUSR1);
	/*注册信号处理函数*/
	Signal(SIGUSR1, sig_usr1);
	/*将进程注册为接收该消息队列的异步事件通知*/
	sigev.sigev_notify = SIGEV_SIGNAL;
	sigev.sigev_signo = SIGUSR1;
	Mq_notify(mqd, &sigev);
	
	for ( ; ; ) {
		/*阻塞SIGUSR1信号*/
		Sigprocmask(SIG_BLOCK, &newmask, &oldmask);
		while (mqflag == 0)
			sigsuspend(&zeromask);
		mqflag = 0;
		/*重新注册*/
		Mq_notify(mqd, &sigev);
		
		/*!!!接收消息,不要使用Mq_receive*/
		while ((n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0)
			printf("read %ld bytes\n", (long)n);
		if (errno != EAGAIN)
			err_sys("mq_receive error");
		/*解阻塞SIGUSR1信号*/
		Sigprocmask(SIG_UNBLOCK, &newmask, NULL);
	}
	
	exit(0);
}

static void
sig_usr1(int signo)
{
	mqflag = 1;
	return;
}
比上个程序更为高效的办法之一是阻塞在某个函数中,仅仅等待该信号的递交,而不是让内核执行一个只为设置一个标志的信号处理程序。sigwait函数提供了这种能力。改进后的代码如下:

#include "unpipc.h"

int main(int argc, char **argv)
{
	int signo;
	mqd_t mqd;
	void *buff;
	ssize_t n;
	sigset_t newmask;
	struct mq_attr attr;
	struct sigevent sigev;

	if (argc != 2)
		err_quit("usage: mqnotifysig4 <name>");

	/*!!!以非阻塞方式打开消息队列*/
	mqd = Mq_open(argv[1], O_RDONLY | O_NONBLOCK);
	Mq_getattr(mqd, &attr);
	buff = Malloc(attr.mq_msgsize);

	Sigemptyset(&newmask);
	Sigaddset(&newmask, SIGUSR1);
	/*阻塞SIGUSR1信号*/
	Sigprocmask(SIG_BLOCK, &newmask, NULL);
	/*将进程注册为接收该消息队列的异步事件通知*/
	sigev.sigev_notify = SIGEV_SIGNAL;
	sigev.sigev_signo = SIGUSR1;
	Mq_notify(mqd, &sigev);
	
	for ( ; ; ) {
		/*调用sigwait前,先阻塞某个信号集,sigwait一直阻塞到这些信号中
		有一个或多个待处理,这时它返回其中的一个信号。*/
		Sigwait(&newmask, &signo);
		if (signo == SIGUSR1) {
			/*重新注册*/
			Mq_notify(mqd, &sigev);
			
			/*!!!接收消息,不要使用Mq_receive,因为非阻塞,Mq_receive会
			返回EAGAIN,直接退出程序,而我们要忽略EAGAIN*/
			while ((n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0)
				printf("read %ld bytes\n", (long)n);
			if (errno != EAGAIN)
				err_sys("mq_receive error");
		}
	}
	exit(0);
}

Posix消息队列描述符不是普通的描述符,我们不能在selectpoll函数中使用它们,但我们可以伴随一个管道和mq_notify函数使用它们。代码如下:

#include "unpipc.h"

int pipefd[2];
static void sig_usr1(int);

int main(int argc, char **argv)
{
	int nfds;
	char c;
	fd_set rset;
	mqd_t mqd;
	void *buff;
	ssize_t n;
	struct mq_attr attr;
	struct sigevent sigev;

	if (argc != 2)
		err_quit("usage: mqnotifysig5 <name>");

	/*!!!以非阻塞方式打开消息队列*/
	mqd = Mq_open(argv[1], O_RDONLY | O_NONBLOCK);
	Mq_getattr(mqd, &attr);
	buff = Malloc(attr.mq_msgsize);

	Pipe(pipefd);

	Signal(SIGUSR1, sig_usr1);
	/*将进程注册为接收该消息队列的异步事件通知*/
	sigev.sigev_notify = SIGEV_SIGNAL;
	sigev.sigev_signo = SIGUSR1;
	Mq_notify(mqd, &sigev);

	FD_ZERO(&rset);
	for ( ; ; ) {
		FD_SET(pipefd[0], &rset);
		/*Select函数没有处理被write系统调用中断的情形,不要调用*/
		nfds = select(pipefd[0] + 1, &rset, NULL, NULL, NULL);
		if (FD_ISSET(pipefd[0], &rset)) {
			/*从管道读出1字节数据*/
			Read(pipefd[0], &c, 1);
			
			/*重新注册*/
			Mq_notify(mqd, &sigev);
			
			/*!!!接收消息,不要使用Mq_receive*/
			while ((n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0)
				printf("read %ld bytes\n", (long)n);
			if (errno != EAGAIN)
				err_sys("mq_receive error");
		}
	}
	exit(0);
}

static void
sig_usr1(int signo)
{
	/*写1字节数据到管道,这样select可以监听到管道描述符可读*/
	Write(pipefd[1], "", 1);
	return;
}

select函数监听管道读描述符,当信号产生时,信号处理函数往管道写入1字节数据。这样select函数就可以监听到一个读事件,这个读事件就表明了有消息放入了消息队列。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值