1概述
System V消息队列使用消息队列标识符标识。具有足够特权(参看IPC权限)的任何进程都可以往一个给定队列放置一个消息,或从一个给定队列读出一个消息。
对于系统中的每个消息队列,内核维护一个定义在<sys/msg.h>头文件中的信息结构。
struct msqid_ds { struct ipc_perm msg_perm; struct msg *msg_first; /* first message on queue,unused */ struct msg *msg_last; /* last message in queue,unused */ __kernel_time_t msg_stime; /* last msgsnd time */ __kernel_time_t msg_rtime; /* last msgrcv time */ __kernel_time_t msg_ctime; /* last change time */ unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */ unsigned long msg_lqbytes; /* ditto */ unsigned short msg_cbytes; /* current number of bytes on queue */ unsigned short msg_qnum; /* number of messages in queue */ unsigned short msg_qbytes; /* max number of bytes on queue */ __kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */ __kernel_ipc_pid_t msg_lrpid; /* last receive pid */ }; |
假设一个消息队列有三个消息,消息长度分别为1字节、2字节和3字节,消息类型分别为100、200和300,该消息队列中的消息链表可如图1所示。
2函数
头文件 | #include <sys/msg.h> |
函数 | int msgget(key_t key, int oflag); |
参数 | key:键值,由ftok生成或IPC_PRIVATE oflag:读写权限与打开或创建一个消息队列标志的或值 创建或打开:IPC_CREAT或IPC_CREAT | IPC_EXCL 读写权限:MSG_R、MSG_W等等 |
返回值 | 成功返回非负标识符,出错返回-1 |
功能 | 创建一个新的消息队列或打开一个已存在的消息队列 |
说明 | 当创建一个新消息队列时,msqid_ds结构的如下成员被初始化: 1)msg_perm中的uid和cuid被设置成当前进程的有效用户ID,gid和cgid被设置成当前进程的有效组ID 2)oflag中的读写权限存放在msg_perm.mode中 3)msg_qnum、msg_lspid、msg_lrpid、msg_stime和msg_rtime被设置为0 4)msg_ctime被设置为当前时间 5)msg_qbytes被设置成系统限制值 |
头文件 | #include <sys/msg.h> |
函数 | int msgsnd(int msgqid, const void *ptr, size_t length, int flag); |
参数 | msgqid:由msgget消息队列标识符 ptr:自定义结构指针,包含消息类型和消息数据。 若nbytes为0,则无消息数据;若发送的最长消息是512字节,则可定义下列结构: struct msgbuf{ long mtype; /*消息类型,必须为正整数*/ char mtext[256]; /*消息数据*/ }; length:消息数据字节大小,由自定义结构msgbuf可知该为256 flag:标志位,可以为0,也可以为IPC_NOWAIT、MSG_EXCEPT、MSG_NOERROR。 对发送消息来说,比较有意义的flag是IPC_NOWAIT。在消息队列满时,若指定IPC_NOWAIT则使得msgsnd立即出错返回EAGAIN;若没有指定,则进程阻塞直到下述情况出现为止: a)有空间可以容纳要发送的消息。 b)从系统中删除了此队列。 c)捕捉到一个信号,并从信号处理程序返回。 |
返回值 | 成功返回0,出错返回-1 |
功能 | 往消息队列添加一个消息 |
头文件 | #include <sys/msg.h> |
函数 | ssize_t msgrcv(int msqid, void *ptr, size_t length, long type, int flag); |
参数 | msqid:msgget返回的消息队列标识符 ptr:自定义结构指针 length:消息数据字节大小,由自定义结构msgbuf可知该为256 type:消息类型,type值非0用于以非先进先出次序读消息。 type == 0 返回队列中的第一个消息 type > 0 返回队列中消息类型为type的第一个消息 type < 0 返回队列中消息类型值小于或等于type绝对值的消息。 flag:标志位,可以为0,也可以为IPC_NOWAIT、MSG_EXCEPT、MSG_NOERROR。 |
返回值 | 成功返回读取缓冲区中数据的字节数,出错返回-1 |
功能 | 从消息队列中读取一个消息 |
头文件 | #include <sys/msg.h> |
函数 | int msgctl(int msqid, int cmd, struct msqid_ds *buff); |
参数 | msqid:消息队列标识符 cmd:msgctl提供3个命令, IPC_RMID删除消息队列 IPC_SET 设置msqid_ds结构的4个成员:msg_byte和msg_perm中的udi、gid、mode IPC_STAT 获取消息队列的msqid_ds结构 |
返回值 | 成功返回0,出错返回-1 |
功能 | 提供在一个消息队列上的各种控制操作 |
3应用
示例1:每个应用一个队列
示例2:每个客户一个队列
//头文件msg.h
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#define PATHNAME "/tmp/msgserv"
#define MSG_R 0400
#define MSG_W 0200
#define BUFSIZE 256
#define MAXLINE 100
#define SVMSG_MODE (MSG_R | MSG_W | MSG_R>>3 | MSG_W>>6)
/*自定义消息结构*/
typedef struct msgbuf{
long mtype; /*消息类型*/
char mtext[BUFSIZE]; /*消息内容*/
}msgbuf_t;
typedef struct clibuf{
int msgqid; /*消息队列标识符*/
char pathname[MAXLINE]; /*路径名*/
}clibuf_t;
//客户端client.c
#include "msg.h"
int main(int argc, char *argv[])
{
int ret;
int readid;
int writeid;
key_t key;
ssize_t n;
msgbuf_t msg;
clibuf_t cli;
char pathname[MAXLINE];
if (argc < 2)
{
printf("usage: ./a.out <pathname>\n");
exit(1);
}
key = ftok(PATHNAME, 0);
if (key == -1)
{
printf("ftok error..\n");
exit(1);
}
/*打开一个已经存在的服务器消息队列*/
writeid = msgget(key, 0);
if (writeid == -1)
{
printf("msgget error: %s\n", strerror(errno));
exit(1);
}
/*创建客户专属的私有消息队列*/
readid = msgget(IPC_PRIVATE, IPC_CREAT | SVMSG_MODE);
if (readid == -1)
{
printf("msgget error:%s\n", strerror(errno));
exit(1);
}
/*填充私有队列标识符和路径名*/
cli.msgqid = readid;
snprintf(pathname, sizeof(pathname), "%s", argv[1]);
n = strlen(pathname);
if (pathname[n-1] == '\n')/*去除标准输入的路径名的换行符*/
n--;
pathname[n] = '\0';
snprintf(cli.pathname, n+1, "%s", pathname);
/*向服务器发送标识符和路径名消息*/
msg.mtype = 1;
memcpy(msg.mtext, (char *)&cli, sizeof(cli));
ret = msgsnd(writeid, &msg, BUFSIZE, IPC_NOWAIT);
if (ret == -1)
{
msgctl(readid, IPC_RMID, NULL);
printf("smsgsnd error: writeid=%d, ret=%d, %s\n", writeid, ret, strerror(errno));
exit(1);
}
/*从服务器读出路径名内容并显示到显示器*/
while ((n = msgrcv(readid, &msg, BUFSIZE, 2, IPC_NOWAIT)) > 0)/*容易出错的是将第三个参数设置为sizeof(msg)*/
{
write(STDOUT_FILENO, msg.mtext, n);
memset(msg.mtext, 0x0, BUFSIZE);
}
/*删除客户专属的私有消息队列*/
ret = msgctl(readid, IPC_RMID, NULL);
if (ret == -1)
{
printf("client delete priv_msg error:%s..\n", strerror(errno));
exit(1);
}
exit(0);
}
//服务器server.c
#include "msg.h"
static void sig_chld(int signo)
{
int stat;
pid_t pid;
while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
;
return ;
}
int main()
{
int ret;
int fd;
int msgqid;
int privqid;
key_t key;
ssize_t n;
pid_t pid;
FILE *fp;
msgbuf_t msg;
clibuf_t *cli;
char pathname[MAXLINE];
char buf[BUFSIZE];
/*确保服务器消息文件存在*/
fd = open(PATHNAME, O_RDONLY | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH);
if (fd == -1)
{
printf("server open %s error\n", PATHNAME);
exit(1);
}
close(fd);
key = ftok(PATHNAME, 0);
if (key == -1)
{
printf("server ftok error..\n");
exit(1);
}
/*创建服务器消息队列*/
msgqid = msgget(key, IPC_CREAT | SVMSG_MODE);
if (msgqid == -1)
{
printf("msgget error..\n");
exit(1);
}
if (signal(SIGCHLD, sig_chld) == SIG_ERR)
{
printf("signal error..\n");
exit(1);
}
int i;
/*并发处理每个客户的请求*/
for (;;)
{
/*读取客户的私有消息队列标识符和路径名*/
ret = msgrcv(msgqid, &msg, BUFSIZE, 1, 0);
if (ret == -1)
{
printf("msgrcv priqid and pathname error: msgqid=%d, ret=%d, %s\n", msgqid, ret, strerror(errno));
continue;
}
/*解析出客户私有消息队列标识符和路径名*/
if (msg.mtype == 1)
{
cli = (clibuf_t *)msg.mtext;
privqid = cli->msgqid;
snprintf(pathname, sizeof(pathname), "%s", cli->pathname);
}
else
{
continue;
}
/*生成子进程处理客户的请求*/
pid = fork();
if (pid < 0)
{
printf("fork error\n");
}
else if (pid == 0)
{
/*打开路径名*/
fd = open(pathname, O_RDONLY | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH);
if (fd == -1)
{
printf("open error: %s", strerror(errno));
sprintf(msg.mtext, "open '%s' error: %s\n", pathname, strerror(errno));
msgsnd(privqid, &msg, sizeof(msg), IPC_NOWAIT);
exit(1);
}
/*读取路径名的内容并发送给客户*/
while ((n = read(fd, (void *)buf, BUFSIZE)) > 0)
{
msg.mtype = 2;
memset(msg.mtext,0x0, BUFSIZE);
memcpy(msg.mtext, buf, n);
ret = msgsnd(privqid, &msg, BUFSIZE, IPC_NOWAIT);/*容易出错的是将第三个参数设置为sizeof(msg)*/
if (ret == -1)
{
printf("msgsnd error: id=%d, ret=%d, %s\n", privqid, ret, strerror(errno));
break;
}
}
exit(0);
}
}
exit(0);
}