10.3 消息队列编程

System V的IPC机制提供了对消息队列进行编程的接口,主要包括消息队列的创建、消息的读写、消息队列的控制及删除。另外,IPC机制还提供了生成IPC键值的函数ftok。

10.3.1 键值生成函数
        键值生成函数ftok提供了一种键值生成方法,即根据文件名和一个整型的变量生成一个惟一的键值,并且保证每次以同样的参数调用ftok返回的键值是相同的。这里的整型变量成为项目ID。通过这种机制,使得编程人员不需要在程序中直接指定长整型的键值,而是通过定义一个文件和一个项目ID的方式即可。函数ftok的原型为:
#include <sys/ipc.h>
key_t ftok(const char *pathname,int prjid);
参数说明:
1)pathname:文件路径名称。可以使用绝对路径或者相对路径。
2)prjid:项目ID。指定相同的文件名称和一个不同的项目ID,可以生成不同的键值。
返回值:
1)若失败,返回-1。可能的原因包括文件不存在或者没有足够的访问权限。
2)若成功,返回其他数值。此时返回值即是可用的键值。
注意事项:
1)选择的文件应该是内容和属性(修改时间等)长期不发生变化的,并且是对进程而言是可读的,如“/etc/profile”等。如果文件内容或者属性经常发生变化,尽管用同样的参数去调用ftok,而返回的键值可能是不同的。
2)可以通过指定同一个文件,而使用不同的项目ID的方式获取不同的键值。

10.3.2 创建消息队列
        一个消息队列实际上是Linux在内核分配的一个数据结构。要使用消息队列进行消息传递,首先要创建一个消息队列。在Linux下,实现创建消息队列功能的是函数msgget。该函数除了具有创建新的消息队列的功能外,还可以返回一个已存在的消息队列的标识符。函数msgget的原型为:
#include <sys/msg.h>
int msgget(key_t key,int msgflg);
参数说明:
1)key:键值。该参数的输入方式有3种,分别是:一、直接指定一个整数;二、用ftok产生一个键值作为输入;三、用IPC_PRIVATE为参数输入,由系统创建一个可用的队列。
2)msgflg:标志和权限信息。通常情况下,在创建一个新的队列时,用参数IPC_CREAT;而在获取一个已存在的消息队列的标识符时,则将该参数设置为0。该参数包括两部分内容,一是标志,二是权限。
返回值:
若失败,返回-1;若成功,返回其他值,该值即是可用的标识符。
注意事项:
1)如果使用IPC_PRIVATE调用msgget,系统都将创建一个新的消息队列,即msgget调用将总是返回成功,并且该消息队列的键值恒为0。由此产生的问题是,在其他进程中不能用键值访问该消息队列,而必须用msgget返回的标识符来访问。所以,进程间必须通过某种机制(子进程继承等)来获取标识符,然后通过该标识符进行消息传递和接收。
2)msgget函数不仅有创建消息队列的功能,而且在消息队列已存在的情况下,还可以返回该消息队列的标识符。要特别注意IPC_CREAT和IPC_EXCL合用时的含义。

10.3.3 发送消息
        消息队列创建成功后,便可以调用消息发送函数向队列中发送消息了。消息的发送由函数msgsnd完成,其原型为:
#include <sys/msg.h>
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);
参数说明:
1)msqid:消息队列的标识符,由msgget函数返回的标识符。
2)msgp:输入参数,消息结构指针,该参数的类型是void*,实际上应该向该参数传递一个“消息结构”的指针。不过在传递参数的时候强制类型转换为void*。
3)msgsz:消息的长度,这里的长度是指真正的消息内容的长度,而不是消息结构的尺寸。
4)msgflg:发送消息可选标志,如果不需要指定任何标志,在这里输入0即可。可以选择的标志只有IPC_NOWAIT。在默认状态下,消息的发送操作是阻塞的。一旦由于该消息队列中存储的消息已达到最大值或者存储的消息条数已达到最大值,此时调用msgsnd将阻塞,直到有进程调用msgrcv从队列中读出消息。如果选择了IPC_NOWAIT标志,表明消息发送的过程是非阻塞的。如果队列已满,则msgsnd将立即返回-1。
返回值:
若成功,返回0;若失败,返回-1。

10.3.4 接收消息
        消息已发送到消息队列中,其他进程如何获取该消息呢?这就需要用到消息接收的操作。利用消息接收操作可以从消息队列中读取指定类型的消息,也可以不指定类型,按照“先进先出”的原则读取队列头部的第一条消息。消息接收的函数是msgrcv,其原型为:
#include <sys/msg.h>
int msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
参数说明:
1)msqid:消息队列的标识符,由msgget函数返回的标识符。
2)msgp:输出参数,消息结构指针,该参数的类型是void*,实际上应该向该参数传递一个“消息结构”的指针。
3)msgsz:要读取的消息长度,在不确定消息长度的情况下,此处可以传递一个稍大的长度,系统将读取相应消息的全部内容,并在msgrcv的返回值中指明消息的正确长度。如果此处传递的要读取的长度比真正的消息内容长度要小,则默认状态下将返回失败。不过,如果指定了MSG_NOERROR可选标志,也可以成功读取。
4)msgflg:接收消息可选标志。
返回值:
若失败,返回-1;若成功,返回实际读取到的消息内容的字节数。

10.3.5 控制消息队列
        消息队列创建成功后,内核不仅创建了消息队列结构用于存储消息,并且创建了消息队列的控制结构(msqid_ds)。该结构中指定了该消息队列的权限信息、发送接收消息的进程等信息。IPC机制提供了专门的函数用于对该结构进行控制,这个函数就是msgctl,其原型为:
#include <sys/msg.h>
int msgctl(int msqid,int cmd,struct msqid_ds *buff);
参数说明:
1)msqid:消息队列的标识符,由msgget函数返回的标识符。
2)cmd:控制命令,可取值分别为:0(IPC_RMID)、1(IPC_SET)、2(IPC_STAT)、3(IPC_INFO)。
3)buff:输入或输出参数,指向msqid_ds结构的指针。在cmd参数为IPC_SET时,该参数用作输入;在cmd参数为IPC_STAT时,该参数用作输出。
返回值:
若成功,返回0;若失败,返回-1。

例10-1:编程实现消息队列的创建,消息的发送、接收及消息队列的控制。本例将编写4个程序(createmsq,sendmsg,recvmsg和ctrlmsq),分别用于创建消息队列、发送消息、接收消息和控制消息队列。
createmsq代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
void main()
{
//键值
key_t key;
//消息队列标识符
int msqid;
//生成键值
if((key=ftok("/etc/profile",1))==-1)
{
perror("ftok");
return;
}
//创建消息队列
if((msqid=msgget(key,IPC_CREAT|IPC_EXCL|0666))==-1)
{
//如果创建失败并且队列不存在
if(errno!=EEXIST)
{
perror("msgget");
return;
}
//如果创建失败但队列已经存在,则继续获取队列标识符
if((msqid=msgget(key,0))==-1)
{
perror("msgget");
return;
}
}
//创建成功,输出信息
printf("ok: msqid=%d\n",msqid);
}

sendmsg代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//自定义消息参数类型
typedef struct
{
long type;         //消息类型
char name[20]; //消息内容
int id; //消息内容
}MSG;
void main(int argc,char *argv[])
{
//键值
key_t key;
//消息队列标识符
int msqid;
//消息变量
MSG msg;
//检测命令行参数
if(argc!=3)
{
printf("usage: sendmsg name id\n");
return;
}
//设置消息内容
memset(&msg,0,sizeof(MSG));
msg.type=1;
sscanf(argv[1],"%s",msg.name);
sscanf(argv[2],"%d",&msg.id);
//生成键值
if((key=ftok("/etc/profile",1))==-1)
{
perror("ftok");
return;
}
//获取消息队列标识符
if((msqid=msgget(key,0))==-1)
{
perror("msgget");
return;
}
//发送消息
if(msgsnd(msqid,&msg,sizeof(MSG)-4,0)<0) //阻塞模式发送
{
perror("msgsnd");
return;
}
//发送成功,输出信息
printf("ok: send message successful!\n");
}

recvmsg代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//自定义消息参数类型
typedef struct
{
long type;         //消息类型
char name[20]; //消息内容
int id; //消息内容
}MSG;
void main()
{
//键值
key_t key;
//消息队列标识符
int msqid;
//消息变量
MSG msg;
//生成键值
if((key=ftok("/etc/profile",1))==-1)
{
perror("ftok");
return;
}
//获取消息队列标识符
if((msqid=msgget(key,0))==-1)
{
perror("msgget");
return;
}
//接收消息
memset(&msg,0,sizeof(MSG));
if(msgrcv(msqid,&msg,sizeof(MSG)-4,1,0)<0)
{
perror("msgrcv");
return;
}
//接收消息成功,输出信息
printf("ok: recveive message: name=%s,id=%d\n",msg.name,msg.id);
}

ctrlmsg代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//自定义消息参数类型
typedef struct
{
long type; //消息类型
char name[20]; //消息内容
int id; //消息内容
}MSG;
void main()
{
//键值
key_t key;
//消息队列标识符
int msqid;
//消息变量
MSG msg;
//消息队列控制结构变量
struct msqid_ds msqds;
//生成键值
if((key=ftok("/etc/profile",1))==-1)
{
perror("ftok");
return;
}
//获取消息队列标识符
if((msqid=msgget(key,0))==-1)
{
perror("msgget");
return;
}
//获取消息队列状态
memset(&msqds,0,sizeof(msqds));
if(msgctl(msqid,IPC_STAT,&msqds)<0)
{
perror("msgctl IPC_STAT");
return;
}
//获取消息队列状态成功,输出相关信息
printf("max msq size=%d bytes\n",msqds.msg_qbytes); //输出该消息队列允许存储的最大长度
printf("current msg count=%d\n",msqds.msg_qnum); //输出当前该队列的消息数目
printf("current msg size=%d bytes\n",msqds.__msg_cbytes);  //输出当前该队列中的消息总长度
}

说明:
1)编译链接分别生成可执行程序:createmsq,sendmsg,recvmsg及ctrlmsq。
2)创建消息队列,执行命令: ./createmsq
3)发送消息,执行命令: ./sendmsg Jim 123
4)接收消息,执行命令: ./recvmsg
5)获取消息队列状态信息,执行命令: ./ctrlmsq
6)注意观察控制台的输出结果。
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值