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),刚开始学习网络编程,如有不正确之处请大家多多指正。