Linux应用编程—9.消息队列
消息队列用于进程之间的通讯,可以在如父子进程、兄弟进程这样的具有亲缘关系的进程之间传递数据,也可以用于具有非亲缘关系的进程之间通讯。消息队列可以传递结构体,所以可以发送任意数据类型。与消息队列有关的函数分别是:创建、发送、接收与删除。
9.1 创建消息队列msgget()
Linux终端下输入:man msgget,如图1所示。
NAME
msgget - get a System V message queue identifier
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
msgget()获取一个System v下的消息队列标识符。函数原型为:int msgget(key_t key, int msgflg);需要包含头文件:sys/msg.h。
DESCRIPTION
The msgget() system call returns the System V message queue identifier associated with the value of the key argument. It may be used either to obtain the identifier of
a previously created message queue (when msgflg is zero and key does not have the value IPC_PRIVATE), or to create a new set.
A new message queue is created if key has the value IPC_PRIVATE or key isn't IPC_PRIVATE, no message queue with the given key key exists, and IPC_CREAT is specified in
msgflg.
If msgflg specifies both IPC_CREAT and IPC_EXCL and a message queue already exists for key, then msgget() fails with errno set to EEXIST. (This is analogous to the ef‐
fect of the combination O_CREAT | O_EXCL for open(2).)
Upon creation, the least significant bits of the argument msgflg define the permissions of the message queue. These permission bits have the same format and semantics
as the permissions specified for the mode argument of open(2). (The execute permissions are not used.)
If a new message queue is created, then its associated data structure msqid_ds (see msgctl(2)) is initialized as follows:
msg_perm.cuid and msg_perm.uid are set to the effective user ID of the calling process.
msg_perm.cgid and msg_perm.gid are set to the effective group ID of the calling process.
The least significant 9 bits of msg_perm.mode are set to the least significant 9 bits of msgflg.
msg_qnum, msg_lspid, msg_lrpid, msg_stime, and msg_rtime are set to 0.
msg_ctime is set to the current time.
msg_qbytes is set to the system limit MSGMNB.
If the message queue already exists the permissions are verified, and a check is made to see if it is marked for destruction.
描述:msgget()系统调用返回与key参数值相关的System v消息队列标识符。既可以用于获取先前创建的消息队列的标识符(当msgflg为零且key没有值IPC_PRIVATE时),也可以用于创建一个新的集合。
如果key的值为:IPC_PRIVATE或者不是IPC_PRIVATE,一个新的消息队列将被创建。不存在给定key值的消息队列,并且IPC_CREAT是特殊的标志位。
如果msgflg同时指定了IPC_CREAT与IPC_EXCL,并且key值的消息队列已经存在,那么msgget()将失败。
RETURN VALUE
If successful, the return value will be the message queue identifier (a nonnegative integer), otherwise -1 with errno indicating the error.
函数返回值,如果成功的话,返回消息队列标识符,失败则返回-1。
9.2 发送与接收消息队列msgsnd()、msgrcv()
Linux终端下输入:man msgsnd,如图2所示。
NAME
msgrcv, msgsnd - System V message queue operations
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
msgrcv()与msgsnd()用于System v下消息队列操作。发送消息队列函数原型为:Int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);接收消息队列函数原型为:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)。
DESCRIPTION
The msgsnd() and msgrcv() system calls are used to send messages to, and receive messages from, a System V message queue. The calling process must have write permission
on the message queue in order to send a message, and read permission to receive a message.
The msgp argument is a pointer to a caller-defined structure of the following general form:
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
The mtext field is an array (or other structure) whose size is specified by msgsz, a nonnegative integer value. Messages of zero length (i.e., no mtext field) are per‐
mitted. The mtype field must have a strictly positive integer value. This value can be used by the receiving process for message selection (see the description of ms‐
grcv() below).
msgsnd()与msgrcv()函数系统调用被用于发送或者接收System v消息队列中的消息。调用进程对于消息队列必须要有写权限目的是为了发送消息,和读权限为了读取消息。
参数msgp是一个指针,指向如下面类型的结构体。mtext是一个数组或者其它类型,大小是msgsz,是一个无符号整形。mtype参数必须有一个确切的正整数,这个值被用于通过接收进程的消息选择。对于msgsnd()的参数,msgid是消息队列创建时返回的id号,msgsz是发送往消息队列数据的大小,msgflg是标志位,一般默认标志位,填写入0即可。
msgrcv(),ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);msgid是消息队列标识符,创建时会得到;msgp是指向结构体的指针,msgsz是消息队列内数据的大小,与发送往消息队列数据大小一致;msgtyp用于接收消息时消息选择,发送和接收进程往消息队列里发送的数据靠msgtyp联系,如果这个两个不一样,则接收进程获取不到消息队列里的消息;msgflg为标志位,一般默认标志位,写0即可。
9.3 消息队列实践-父子进程间通讯
先创建一个消息队列,然后创建消息队列数据结构体,成员分别是消息队列类型mtype、用来存储字符串的数组mtetx[32],整数型数据num。然后创建父子进程,在父进程中,通过gets()函数获取用户输入字符串;通过scanf()函数获取用户输入的数字,将数据保存在结构体内,调用msgsnd()函数发送,发送后调用waitpid()等待子进程接收消息队列数据、打印并且结束进程,子进程调用msgrcv()函数接收消息队列里的数据。
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <sys/wait.h>
#define MY_TYPE 9527
int main(void)
{
pid_t pid;
int msgid = 0;
typedef struct
{
long mtype;
char mtext[32];
int num;
}msgbuf_TypeDef;
msgbuf_TypeDef msgbuf;
msgid = msgget(IPC_PRIVATE, IPC_CREAT);
if(-1 == msgid)
perror("msgget.");
pid = fork();
if(pid > 0)
{
sleep(5);
msgbuf.mtype = MY_TYPE;
printf("Please enter a string you want to send:\n");
gets(msgbuf.mtext);
printf("Please enter a number you want to send:\n");
scanf("%d", &msgbuf.num);
msgsnd(msgid, &msgbuf, sizeof(msgbuf) - sizeof(msgbuf.mtype), 0);
waitpid(pid, NULL, 0);
}
else if(pid == 0)
{
printf("Child process receiving data from msg:\n");
msgrcv(msgid, &msgbuf, sizeof(msgbuf) - sizeof(msgbuf.mtype), MY_TYPE, 0);
printf("The data is %s, %d.\n", msgbuf.mtext, msgbuf.num);
msgctl(msgid, IPC_RMID, 0);
}
else
perror("fork.");
return 0;
}
运行结果:
原因是,没有启用超级权限模式。代码运行前,输入:su。在root权限下操作。
9.4 非亲缘进程间通讯
程序流程图如下图5所示。
消息队列发送端代码。
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#define MY_TYPE 1314
#define USER_KEY 9527
int main(void)
{
int msgid;
typedef struct msg
{
long mtype;
char mtext[32];
int number;
}msgbuf_TypeDef;
msgbuf_TypeDef msgbuf;
msgid = msgget(USER_KEY, IPC_CREAT);
if(-1 == msgid)
perror("msgget.");
msgbuf.mtype = MY_TYPE;
printf("Please enter a strings you want to send.\n");
gets(msgbuf.mtext);
printf("Please enter a number you want to send.\n");
scanf("%d", &msgbuf.number);
msgsnd(msgid, &msgbuf, sizeof(msgbuf) - sizeof(msgbuf.mtype), 0);
return 0;
}
消息队列接收端代码。
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#define MY_TYPE 1314
#define USER_KEY 9527
int main(void)
{
int msgid;
typedef struct msg
{
long mtype;
char mtext[32];
int number;
}msgbuf_TypeDef;
msgbuf_TypeDef msgbuf;
msgid = msgget(USER_KEY, IPC_CREAT);
if(-1 == msgid)
perror("msgget.");
while(1)
{
printf("Wtatting mesages from another process.\n");
msgrcv(msgid, &msgbuf, sizeof(msgbuf) - sizeof(msgbuf.mtype), MY_TYPE, 0);
printf("datas is %s %d.\n", msgbuf.mtext, msgbuf.number);
}
return 0;
}
消息队列发送端代码运行结果:
root@ubuntu:/home/sgk/Documents/Linux_Program/no_relation_msg# ./send
Please enter a strings you want to send.
Hello world.
Please enter a number you want to send.
12345
root@ubuntu:/home/sgk/Documents/Linux_Program/no_relation_msg#
消息队列接收端代码运行结果:
root@ubuntu:/home/sgk/Documents/Linux_Program/no_relation_msg# ./recv
Wtatting mesages from another process.
datas is Hello world. 12345.
Wtatting mesages from another process.
9.5 总结
消息队列可以用于亲缘关系间的进程通讯,也可以用于具有非亲缘关系间的进程之间通讯。通讯过程分为大致4步骤:1 创建消息队列结构体,使用msgget()创建消息队列;2 使用msgsnd()发送填充过数据的消息队列结构体;3 使用msgrcv()函数接收;4 如果不在使用,则使用msgctl()函数删除创建的消息队列。