一、消息队列
消息对类是一个存放在内核中的消息链表,每个消息队列由消息队列标识符标识。与管道不同的是消息队列存放在内核中,只有在内核重启(即操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正删除。
操作消息队列时,需要一些数据结构,熟悉这些数据结构是掌握消息队列的关键。
1.消息缓冲队列结构
2.msqid_ds内核数据结构
3.ipc_perm内核数据结构
二、创建消息队列
消息队列是随着内核的存在而存在的,每个消息队列在系统范围内对应唯一的键值。要获得一个消息队列的描述符 ,只需提供该消息队列的键值即可,该键值通常由函数ftok返回。该函数定义在头文件sys/ipc.h中:
key_t ftok(const char *pathname,int proj_id);
#include<sys/types.h>
#include<sys/ipc.h>
ftok函数根据pathname和proj_id这两个参数生成唯一的键值。该函数执行成功会返回一个键值,失败会返回-1。
注意:pathname在系统中一定要存在且进程有权访问,参数proj_id的取值范围为1~255。
ftok()返回的键值可以提供给函数msgget。msgget()根据这个键值创建一个新的消息队列或者访问一个已经存在的消息队列。
msgget定义在头文件sys/msg.h中:
msgget的参数key即为ftok函数的返回值。msgflg是一个标志参数,以下是msgflg的可能取值:
int msgget(key_t key,int msgflg);
1.IPC_CREAT:
如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;
如果存在这样的消息队列,返回该消息队列的描述符。
2.IPC_EXCL:(单独存在没有意义)
和IPC_CREAT一起使用,如果对应键值的消息队列已经存在,则出错,返回-1。
示例代码:
获取消息队列的键值:
#include<sys/ipc.h>
#include<sys/types.h>
#include<stdio.h>
int main()
{
int i;
for(i=1;i<=5;i++)
{
printf("key[%d]=%ul \n",i,ftok(".",i));
}
return 0;
}
运行结果:
pc@ubuntu:~/linux_lan/pipe/10.4$ ./ftok
key[1]=16803014l
key[2]=33580230l
key[3]=50357446l
key[4]=67134662l
key[5]=83911878l
三、写消息队列
创建了一个消息队列后,就可以对队列进行读写了。函数msgsnd用于向消息队列发送(写)数据。
该函数定义在头文件sys/msg.h中:
参数:
int msgsnd(int msqid.struct msgbuf *msgp,size_t msgsz,int magflg);
msqid:函数向msqid标识的消息队列发送一个消息;
msgp:指向发送的消息;
msgsz:要发送的消息的大小,不包含消息类型占用的4个字节;
magflg:操作标志位。可以设置为0或者IPC_NOWAIT。如果设置为0,则当消息队列已满的时候,msgsnd将会阻塞,直到消息可以写进消息队列;
如果设置为IPC_NOWAIT,当消息队列已满的时候,msgsnd函数将不等待立即返回。
返回:
msgsnd函数成功返回0,失败返回-1。
示例代码:
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
#define BUF_SIZE 256
#define PROJ_ID 32 //id是子序号 1~255
#define PATH_NAME "." //当前目录
int main()
{
struct mymsgbuf
{
long msgtype;
char ctrlstring[BUF_SIZE];
}msgbuffer;
int qid;
int msglen;
key_t msgkey;//ftok函数返回的键值
/*获取键值*/
if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1)
{
perror("ftok error!\n");
exit(1);
}
/*创建消息队列*/
if((qid=msgget(msgkey,IPC_CREAT|0666))==-1)
{
perror("msgget error!\n");
exit(1);
}
/*填充消息结构,发送到消息队列*/
msgbuffer.msgtype=3;//消息类型
strcpy(msgbuffer.ctrlstring,"Hello,message queue");
msglen=sizeof(struct mymsgbuf)-4;//不包含消息类型占用的4字节
if(msgsnd(qid,&msgbuffer,msglen,0)==-1)
{
perror("msgget error!\n");
exit(1);
}
exit(0);
}
运行结果:
key msqid owner perms used-bytes messages
0x200064c6 0 pc 666 260 1
运行两次:
key msqid owner perms used-bytes messages
0x200064c6 0 pc 666 520 2
分析:
运行一次,通过ipcs可以看到有一条messages,运行两次即为两条。
四、读消息队列
消息队列中放入数据后,其他进程就可以读取其中的消息了。读取消息的系统调用为msgrcv(),
该函数定义在头文件sys/msg.h中,原型如下:
参数:
int msgrcv(int msqid,struct msgbuf *msgp,size_t msgsz,long int msgtyp,int msgflg);
1.msqid:消息队列描述符;
2.msgp:读取的消息存储到msgp指向的消息结构中;
3.msgsz:消息缓冲区的大小;
4.msgtyp:请求读取的消息类型;
5.msgflg:操作标志位。
返回:调用msgrcv函数的时候,成功会返回读出消息的实际字节数,否则返回-1。
示例代码:
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
int main()
{
/*用户自定义消息缓冲区*/
struct mymsgbuf
{
long msgtype;
char ctrlstring[BUF_SIZE];
}msgbuffer;
int qid;
int msglen;
key_t msgkey;
/*获取键值*/
if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1)
{
perror("ftok error!\n");
exit(1);
}
/*获取消息队列标志符*/
if((qid=msgget(msgkey,IPC_CREAT|0666))==-1)
{
perror("msgget error!\n");
exit(1);
}
msglen=sizeof(struct mymsgbuf)-4;
if(msgrcv(qid,&msgbuffer,msglen,3,0)==-1)
{
perror("msgrcv error!\n");
exit(1);
}
printf("Get message: %s\n",msgbuffer.ctrlstring);
exit(0);
}
运行结果:
pc@ubuntu:~/linux_lan/pipe/10.4$ ./rcvmsg
Get message: Hello,message queue
运行ipcs查看:发现messags减少为1
再次运行后:
pc@ubuntu:~/linux_lan/pipe/10.4$ ./rcvmsg
Get message: Hello,message queue
运行ipcs查看:messages为0