UNP卷二 chapter6 System V 消息队列

1、System V 消息队列

既然是消息队列,就与管道有所区别,队列可以先写入,再读取消息。而管道必须是先有读取的等待,再对管道进行写入

对于系统中的每个消息队列,内核维护一个定义在<sys/msg.h>头文件中的信息结构,如下所示:

struct ipc_perm {
	uid_t	uid;
	gid_t	gid;
	uid_t	cuid;
	gid_t	cgid;
	mode_t	mode;
	ulong_t	seq;
	key_t	key;
};

struct msqid_ds {
	struct ipc_perm	msg_perm;/* read_write perms */
	struct msg	*msg_first;/* ptr to first message on queue */
	struct msg	*msg_last;/* ptr to last message on queue */
	msglen_t	msg_cbytes;/* current # bytes on queue */
	msgqnum_t	msg_qnum;/* current # of messages on queue */
	msglen_t	msg_qbytes;/* max # of bytes allowed on queue */
	pid_t		msg_lspid;/* pid of last msgsnd() */
	pid_t		msg_lrpid;/* pid of last msgrcv() */
	time_t		msg_stime;/* time of last msgsnd() */
	time_t		msg_rtime;/* time of last msgrcv() */
	time_t		msg_ctime;/* time of last msgctl() */
};

内核中某个特定的消息队列可画为一个消息链表,如下图所示,


2、msgget、msgsnd、msgrcv、msgctl函数

msgget函数

用于创建一个新的消息队列或访问一个已存在的消息队列。

#include<sys/msg.h>
int msgget(key_t key, int oflag);//返回:若成功则为非负标识符,若出错则为-1

上述函数的返回值是一个整数标识符,可用来指代对应队列。此队列是基于指定的key产生的,而key既可以是ftok的返回值,也可以是常值IPC_PRIVATE。

oflag是相应读写权限值的组合。它还可以与IPC_CREAT或IPC_CREAT | IPC_EXCL按位或。

msgcreate程序

#include	"unpipc.h"

int
main(int argc, char **argv)
{
	int		c, oflag, mqid;

	oflag = SVMSG_MODE | IPC_CREAT;
	while ( (c = Getopt(argc, argv, "e")) != -1) {
		switch (c) {
		case 'e':
			oflag |= IPC_EXCL;//指定选项e,排它性创建
			break;
		}
	}
	if (optind != argc - 1)
		err_quit("usage: msgcreate [ -e ] <pathname>");

	mqid = Msgget(Ftok(argv[optind], 0), oflag);//返回一个key_t(IPC键),此ftok函数在书上P20
	exit(0);
}

msgsnd函数

使用msgget打开一个消息队列后,使用msgsnd往其上放置一个消息。

#include<sys/msg.h>
int msgsnd(int msqid, const void *ptr, size_t length, int flag);//返回:若成功则为0,若出错则为-1
其中msqid是由msgget返回的标识符。ptr是一个结构指针,该结构具有如下的 模板(只作为参考的模版),其定义在<sys/msg.h>中。举例如下,
struct msgbuf {
	long mtype;/*message type, must be > 0 */
	char mtext[1];/* message data */
};
//上述msgbuf{}为一个参考模版,自己举例定义类似结构如下
#define MY_DATA 8

typedef struct my_msgbuf {
	long	mtype;/* message type*/
	int16_t	mshort;/* start of message data */
	char	mchar[MY_DATA];
}Message;

msgsnd的length参数以字节为单位指定待发送消息的长度。即位于长整数消息类型之后的用户自定义数据的长度。此值可以为0.

flag参数既可以是0,也可以是IPC_NOWAIT。IPC_NOWAIT标志使得msgsnd调用非阻塞:如果没有存放新消息的可用空间,该函数立即返回。此条件可能发生的情形包括:

i、在指定的队列中已有太多的字节(对应该队列的maqid_ds结构中的msg_qbytes值);

ii、在系统范围存在太多的消息。

上述条件有一个存在,且IPC_NOWAIT标志已指定,msgsnd就返回一个EAGAIN错误。若未指定IPC_NOWAIT,那么调用线程被投入睡眠直至:

i、具备存放新消息的空间;

ii、由msqid标识的消息队列从系统中删除(返回一个EIDRM错误);

iii、调用线程被某个捕获的信号所中断(返回一个EINTR错误)。

msgsnd程序
#include	"unpipc.h"

int
main(int argc, char **argv)
{
	int		mqid;
	size_t	len;
	long	type;
	struct msgbuf	*ptr;

	if (argc != 4)
		err_quit("usage: msgsnd <pathname> <#bytes> <type>");
	len = atoi(argv[2]);//长度
	type = atoi(argv[3]);//类型

	mqid = Msgget(Ftok(argv[1], 0), MSG_W);//MSG_W表示以写权限创建或打开该队列

	ptr = Calloc(sizeof(long) + len, sizeof(char));
	ptr->mtype = type;

	Msgsnd(mqid, ptr, len, 0);//此处ptr隐式存在一个强制类型转换

	exit(0);
}

msgrcv函数

使用msgrcv函数从某个消息队列中读出一个消息。

#include<sys/msg.h>
ssize_t msgrcv(int msqid, void* ptr, size_t length, long type, int flag);
										//返回:若成功则为读入缓冲区中数据的字节数,若出错则为-1

ptr参数指定所接收消息的存放位置。具体指向参见下图,


length指定由ptr指向的缓冲区中数据部分的大小。这是该函数能返回的最大数据量。该长充不包括长整数类型字段。

type指定希望从所给定的队列中读出什么样的消息:

i、type=0,返回该队列中的第一个消息。既然每个消息队列都是作为一个FIFO链表维护,因此type为0指定返回该队列中最早的消息;

ii、type>0,返回其类型值为type的第一个消息;

iii、type<0,返回其类型值小于或等于type参数的绝对值的消息中类型值最小的第一个消息。

msgrcv程序

#include	"unpipc.h"

#define	MAXMSG	(8192 + sizeof(long))

int
main(int argc, char **argv)
{
	int		c, flag, mqid;
	long	type;
	ssize_t	n;
	struct msgbuf	*buff;

	type = flag = 0;
	while ( (c = Getopt(argc, argv, "nt:")) != -1) {
		switch (c) {
		case 'n':
			flag |= IPC_NOWAIT;//参见上述说明
			break;

		case 't':
			type = atol(optarg);
			break;
		}
	}
	if (optind != argc - 1)
		err_quit("usage: msgrcv [ -n ] [ -t type ] <pathname>");

	mqid = Msgget(Ftok(argv[optind], 0), MSG_R);

	buff = Malloc(MAXMSG);

	n = Msgrcv(mqid, buff, MAXMSG, type, flag);
	printf("read %d bytes, type = %ld\n", n, buff->mtype);

	exit(0);
}

msgctl函数

msgctl函数提供在一个消息队列上的各种控制操作,如IPC_RMID、IPC_SET、IPC_STAT。

#include<sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds* buff);//返回:若成功则为0,若出错则为-1

msgctl函数提供3个命令

IPC_RMID 删除由msqid指定的消息队列。

IPC_SET 给指定消息队列设置msqid_ds结构的以下4个成员:msg_perm.uid、msg_perm.gid、msg_perm.mode、msg_perm.qbytes。其值来自由buff参数指向的结构中的相应成员。

IPC_STAT (通过buff参数)给调用者返回与所指定消息队列对应的当前msqid_ds结构。

msgrmid程序

#include	"unpipc.h"

int
main(int argc, char **argv)
{
	int		mqid;

	if (argc != 2)
		err_quit("usage: msgrmid <pathname>");

	mqid = Msgget(Ftok(argv[1], 0), 0);
	Msgctl(mqid, IPC_RMID, NULL);

	exit(0);
}

3、利用System V消息队列实现复用消息举例

服务器程序
#include	"svmsg.h"
#include	"unpipc.h"

void	server(int, int);

int
main(int argc, char **argv)
{
	int		msqid;

	msqid = Msgget(MQ_KEY1, SVMSG_MODE | IPC_CREAT);//MQ_KEY1值为1234L

	server(msqid, msqid);	/* same queue for both directions */
							//此处允许使用相同标志符,是因为利用type值复用消息
	exit(0);
}



void
server(int readfd, int writefd)
{
	FILE	*fp;
	char	*ptr;
	pid_t	pid;
	ssize_t	n;
	struct mymesg	mesg;

	for (; ; ) {
		/* 4read pathname from IPC channel */
		mesg.mesg_type = 1;
		if ((n = Mesg_recv(readfd, &mesg)) == 0) {//读出类型值为1的消息
			err_msg("pathname missing");
			continue;
		}
		mesg.mesg_data[n] = '\0';	/* null terminate pathname */

		if ((ptr = strchr(mesg.mesg_data, ' ')) == NULL) {
			err_msg("bogus request: %s", mesg.mesg_data);
			continue;
		}

		*ptr++ = 0;			/* null terminate PID, ptr = pathname */
		pid = atol(mesg.mesg_data);//获取PID号
		mesg.mesg_type = pid;	/* for messages back to client *///PID号作为类型值

		if ((fp = fopen(ptr, "r")) == NULL) {
			/* 4error: must tell client */
			snprintf(mesg.mesg_data + n, sizeof(mesg.mesg_data) - n,
				": can't open, %s\n", strerror(errno));
			mesg.mesg_len = strlen(ptr);
			memmove(mesg.mesg_data, ptr, mesg.mesg_len);
			Mesg_send(writefd, &mesg);

		}
		else {
			/* 4fopen succeeded: copy file to IPC channel */
			while (Fgets(mesg.mesg_data, MAXMESGDATA, fp) != NULL) {
				mesg.mesg_len = strlen(mesg.mesg_data);
				Mesg_send(writefd, &mesg);
			}
			Fclose(fp);
		}

		/* 4send a 0-length message to signify the end */
		mesg.mesg_len = 0;
		Mesg_send(writefd, &mesg);
	}
}
客户端程序
#include	"svmsg.h"
#include	"mesg.h"

void	client(int, int);

int
main(int argc, char **argv)
{
	int		msqid;

	/* 4server must create the queue */
	msqid = Msgget(MQ_KEY1, 0);//打开一个已存在的消息队列

	client(msqid, msqid);	/* same queue for both directions */

	exit(0);
}


void
client(int readfd, int writefd)
{
	size_t	len;
	ssize_t	n;
	char	*ptr;
	struct mymesg	mesg;

	/* 4start buffer with pid and a blank */
	snprintf(mesg.mesg_data, MAXMESGDATA, "%ld ", (long)getpid());
	len = strlen(mesg.mesg_data);//一个空格所占的字节数
	ptr = mesg.mesg_data + len;

	/* 4read pathname */
	Fgets(ptr, MAXMESGDATA - len, stdin);
	len = strlen(mesg.mesg_data);
	if (mesg.mesg_data[len - 1] == '\n')
		len--;				/* delete newline from fgets() */
	mesg.mesg_len = len;
	mesg.mesg_type = 1;

	/* 4write PID and pathname to IPC channel */
	Mesg_send(writefd, &mesg);

	/* 4read from IPC, write to standard output */
	mesg.mesg_type = getpid();
	while ((n = Mesg_recv(readfd, &mesg)) > 0)//从消息队列中读取相应进程号的消息
		Write(STDOUT_FILENO, mesg.mesg_data, n);
}

以上知识点来均来自steven先生所著UNP卷二(version2),刚开始学习网络编程,如有不正确之处请大家多多指正。


猜你喜欢

转载自blog.csdn.net/TT_love9527/article/details/80977318