Linux 进程通信之 消息队列
在之前自学linux时,曾简单总结过Linux 进程通信(http://blog.csdn.net/ky_heart/article/details/52733354),现在,已经系统的学习了,决定详细的总结一下。
一.消息队列基本概念
消息队列就是一个消息的链表。每个消息队列都有一个队列头,用结构struct msg_queue来描述。队列头中包含了该消息队列的大量信息,包括消息队列键值、用户ID、组ID、消息队列中消息数目等等,甚至记录了最近对消息队列读写进程的ID。读者可以访问这些信息,也可以设置其中的某些信息。
可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。
二. 操作消息队列
1、 打开或创建消息队列
消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,只需提供该消息队列的键值即可;
注:消息队列描述字是由在系统范围内唯一的键值生成的,而键值可以看作对应系统内的一条路经。
2、 读写操作
调用函数struct msgbuf{ long mtype; char mtext[1]; };
mtype成员代表消息类型,从消息队列中读取消息的一个重要依据就是消息的类型;mtext是消息内容,当然长度不一定为1。
发送消息:预置msgbuf缓冲区——>写入消息类型和内容——>调用相应的发送函数;
读取消息:分配msgbuf缓冲区——>把消息读入该缓冲区。
3、 获得或设置消息队列属性
消息队列的信息基本上都保存在消息队列头中,因此,可以分配一个类似于消息队列头的结构(struct msqid_ds),来返回消息队列的属性;同样可以设置该数据结构。
三.消息队列API
1. 文件名到键值
key_t ftok (char*pathname, char proj);
函数作用:设定键值
返回值:与路径pathname相对应的一个键值
该函数不直接对消息队列操作,但在调用ipc(MSGGET,…)或msgget()来获得消息队列描述字前,往往要调用该函数。
典型的调用代码是:
key=ftok(path_ptr, ‘a’); ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0); …
2. int msgget(key_t key, int msgflg)
函数作用:创建一个新的消息队列
在以下两种情况下,该调用将创建一个新的消息队列:
如果没有消息队列与健值key相对应,并且msgflg中包含了IPC_CREAT标志位;
key参数为IPC_PRIVATE;
函数参数: key:键值,由ftok获得;msgflg参数是一些标志位。
参数msgflg(权限)可以为以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果。
调用返回:成功返回消息队列描述字,否则返回-1。
注:参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列,只意味着即将创建新的消息队列。
3. int msgrcv(int msgid, struct msgbuf *msgp, intmsgsz, long msgtyp, int msgflg);
函数作用:从msgid代表的消息队列中读取一个消息,并把消息存储在msgp指向的msgbuf结构中。
函数参数:msqid:消息队列描述字;
msgp:消息返回后存储的地址,
msgsz指定msgbuf的mtext成员的长度(即消息内容的长度)
msgtyp:请求读取的消息类型;
msgflg(读消息标志)可以为以下几个常值的或:
IPC_NOWAIT 如果没有满足条件的消息,调用立即返回,此时,errno=ENOMSG
IPC_EXCEPT 与msgtyp>0配合使用,返回队列中第一个类型不为msgtyp的消息
IPC_NOERROR 如果队列中满足条件的消息内容大于所请求的msgsz字节,则把该消息截断,截断部分将丢失。
调用返回:成功返回读出消息的实际字节数,否则返回-1。
4. intmsgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
函数作用:向msgid代表的消息队列发送一个消息,即将发送的消息存储在msgp指向的msgbuf结构中,消息的大小由msgsz指定。
对发送消息来说,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。
调用返回:成功返回0,否则返回-1。
5. intmsgctl(int msqid, int cmd, struct msqid_ds *buf);
函数作用:由msqid标识的消息队列执行cmd操作,共有三种cmd操作:IPC_STAT、IPC_SET、IPC_RMID。
- IPC_STAT:该命令用来获取消息队列信息,返回的信息存贮在buf指向的msqid结构中;
- IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf指向的msqid结构中;可设置属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同时,也影响msg_ctime成员。
- IPC_RMID:删除msqid标识的消息队列;
调用返回:成功返回0,否则返回-1。
四.消息队列的限制
1. 每个消息队列的容量(所能容纳的字节数)都有限制,该值因系统不同而不同。
2. 每个消息队列所能容纳的最大消息数:在redhad 8.0中,该限制是受消息队列容量制约的:消息个数要小于消息队列的容量(字节数)。
五.消息队列的应用
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MAX_TEXT 512
struct my_msg_st
{
long int my_msg_type;
char some_text[BUFSIZ];
};
int main(void)
{
int running = 1;
int msgid;
struct my_msg_st some_data;
long int msg_to_receive = 1;
char buffer[BUFSIZ];
//创建消息队列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error:%d\n", errno);
exit(EXIT_FAILURE);
}
//循环从消息队列中接收消息
while(running)
{
//读取消息
if(msgrcv(msgid, (void *)&some_data, BUFSIZ, msg_to_receive, 0) == -1)
{
fprintf(stderr, "msgrcv failed with error:%d\n", errno);
exit(EXIT_FAILURE);
}
printf("2 wrote:%s\n", some_data.some_text);
printf("Enter some text:");
fgets(buffer, BUFSIZ, stdin);
some_data.my_msg_type = 3;
strcpy(some_data.some_text, buffer);
//添加消息
if(msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1)
{
fprintf(stderr, "msgsnd failed\n");
exit(EXIT_FAILURE);
}
if(strncmp(some_data.some_text, "end", 3) == 0)
{
running = 0;
}
sleep(2);
if(strncmp(buffer, "end", 3) == 0)
{
running = 0;
}
}
#if 0
//从系统内核中移走消息队列
if(msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
#endif
exit(EXIT_SUCCESS);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#define MAX_TEXT 512
struct my_msg_st
{
long int my_msg_type;
char some_text[MAX_TEXT];
};
int main(void)
{
int running = 1;
int msgid;
struct my_msg_st some_data;
long int msg_to_receive = 3;
char buffer[BUFSIZ];
//创建消息队列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error:%d\n", errno);
exit(EXIT_FAILURE);
}
//循环向消息队列中添加消息
while(running)
{
printf("Enter some text:");
fgets(buffer, BUFSIZ, stdin);
some_data.my_msg_type = 1;
strcpy(some_data.some_text, buffer);
//添加消息
if(msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1)
{
fprintf(stderr, "msgsnd failed\n");
exit(EXIT_FAILURE);
}
if(msgrcv(msgid, (void *)&some_data, BUFSIZ, msg_to_receive, 0) == -1)
{
fprintf(stderr, "msgrcv failed with error:%d\n", errno);
exit(EXIT_FAILURE);
}
printf("1 wrote:%s\n", some_data.some_text);
if(strncmp(some_data.some_text, "end", 3) == 0)
{
running = 0;
}
sleep(2);
if(strncmp(buffer, "end", 3) == 0)
{
running = 0;
}
}
exit(EXIT_SUCCESS);
}