高级编程中消息队列的具体使用方法

每个内核中的IPC结构(消息队列、信号量或共享存储段)都用一个非负整数的标识符加以引用,为了对一个消息队列发送或取消息,只需要知道其队列标识符。

键是在用户空间的,标识符是在内核空间的,一个标识符映射一个键。

 

用户    内核

        标识符

1 ==> 65536

2 ==> 98305

 

调用get函数(msgget()semget()shmget())时必须指定键表示引用哪一个IPC对象。

int msgget(key_t key, int msgflg);

int semget(key_t key, int nsems, int semflg);

int shmget(key_t key, size_t size, int shmflg);

 

#include  

struct  ipc_perm

{

    uid_t      uid;拥有者的有效用户ID

    gid_t   gid;拥有者的有效组ID

    uid_t      cuid;创建者的有效用户ID

    gid_t   cgid;创建者的有效组ID

    mode_t  mode;访问权限

};

 

使用IPC对象时应注意的问题:

1)进程终止了IPC对象不会被删除,直到

        1、某个进程删除它为止  

        2、某个进程执行ipcrm删除它为止

        3、重新启动操作系统。

2)这些IPC结构在文件系统中没有名字,于是就不得不增加新的命令ipcsipcrm

消息队列:

消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。

msgget()用于创建一个消息队列或打开一个现存的队列。

msgsnd()将新消息添加到队列尾端。每个消息包含一个正长整型类型字段,一个非负长度以及实际数据字节,所有这些都在将消息添加到队列时,传递给msgsnd()

msgrcv()用于从队列中取消息。我们不一定要以先进先出次序取消息,也可以按消息的类型字段取消息。

   

每个队列都有一个msqid_ds结构与其相关联:

struct  msqid_ds

{

    struct  ipc_perm       msg_perm;//这个又是个结构体用来标识消息队列的权限

    msgqnum_t      msg_qnum;消息队列中消息的数量

    msglen_t           msg_qbytes;消息队列的长度

    pid_t              msg_lspid;最后一个发送消息的进程ID

    pid_t              msg_lrpid;最后一个接收消息的进程ID

    time_t             msg_stime;最后一次发送消息的时间

    time_t             msg_rtime;最后一次接受消息的时间

    time_t             msg_ctime;最后一个修改时间

};

此结构规定了队列的状态,类似与struct  stat

Linux可发送最长消息的字节数:8192字节;

一个特定队列的最大字节数:16384字节;

系统中最大消息队列数:取决于操作系统;

 

 

int     msgget(key_t   key,int    flag)

flag:IPC_CREAT,IPC_EXCL,权限组合

功能:打开一个现存队列或创建一个新队列。

若执行成功,msgget()返回消息队列ID,此后,该值就可被用于其他三个消息队列函数。

 

创建1号消息队列,一定要在flag中指定IPC_CREAT表示创建消息队列,IPC_EXCL表示如果消息队列存在则出错返回EEXIST:

if(msgq_id=msgget(1,IPC_CREAT|IPC_EXCL|0777)<0)

    check_error(“msgget”);

如果已经创建消息队列,如何获得消息队列的标识符?

int msgq_id;

msgq_id=msgget(1,0);

 

int     msgctl(int msqid,int cmd,struct  msqid_ds  *buf);

cmd参数 说明对由msqid指定的队列要执行的命令:

IPC_STAT   取此队列的msqid_ds结构,并将它存放在buf指向的结构中;

 

IPC_SET 按由buf指向结构中的值,设置与此队列相关结构中的下列四个字段:msg_perm.uidmsg_per.gidmsg_perm.modemsg_qbytes。此命令只能由下列两种进程执行:一种是超级用户进程;一种是其有效用户ID等于msg_perm.cuidmsg_perm.uid的进程。

 

IPC_RMID   从系统中删除该消息队列以及仍在该队列中的所有数据。此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuidmsg_perm.uid;另一种是超级用户进程。

例如:msgctl(1,IPC_RMID,NULL);

 

 

int msgsnd(int  msqid,const void *ptr,size_t  nbytes,int  flag);

成功返回0,失败返回-1

 

消息结构体,这个结构体需要自己定义:

struct  mymesg

{

    long        mtype; //消息类型

    char       mtext[512]; //具体的消息

};

 

参数flag 

可以指定为IPC_NOWAIT,这类似于文件I/O的非阻塞标志,若消息队列已满,或队列中的字节总数等于系统限制值,则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN

如果没有指定IPC_NOWAIT(通常设为0),则进程阻塞直到下述情况出现为止:

1、有空间可以容纳要发送的消息;

2、从系统中删除了此队列,此时,msgsnd()返回EIDRM表示该消息队列被删除了。

3、捕捉到一个信号,并从信号处理程序返回,此时,msgsnd()返回EINTR表示被信号中断了。

 

msgsnd.cmsgrcv.c ==> 讲解消息队列的基本使用方法。

方法:

msgsnd.c向消息队列中发送消息,msgrcv.c从消息队列中取出消息。

msgsnd.c

#include   “include.h”

 

//定义消息结构体

typedef struct

{

    long    mtype;  //消息类型

    char    mtext[512]; //具体的消息

}Msg;

 

int main()

{

    int msgq_id;

    //创建1号消息队列

    if((msgq_id=msgget(1,IPC_CREAT|0777))<0) 

        check_error(“msgget”);

 

    Msg msg;

    msg.mtype=1;

    strcpy(msg.mtext,”Hello world.”);

    if(msgsnd(msgq_id,&msg,sizeof(msg.mtext),0)<0)

        check_error(“msgsnd”);

 

    msg.mtype=2;

    strcpy(msg.mtext,”Hello father.”);

    if(msgsnd(msgq_id,&msg,sizeof(msg.mtext),0)<0)

        check_error(“msgsnd”);

 

    msg.mtype=3;

    strcpy(msg.mtext,”Hello mother.”);

    if(msgsnd(msgq_id,&msg,sizeof(msg.mtext),0)<0)

        check_error(“msgsnd”);

 

 

    return  0;

}

 

msgrcv.c

#include   “include.h”

 

typedef struct

{

    long    mtype;

    char    mtext[512];

}Msg;

 

int main()

{

 

    int msgq_id;

    //找到要操作的消息队列,因为在msgsnd.c中已经创建消息队列,故通过下面语句拿到此消息队列的标识符。

    if((msgq_id=msgget(1,0))<0)check_error(“msgget”);

 

    //从该消息队列里面拿出第2个消息

    Msg msg;

    if(msgrcv(msgq_id,&msg,sizeof(msg.mtext),2,0)<0)

        check_error(“msgrcv”);

    printf(“从消息队列里拿到第2个消息是:%s\n”,msg.mtext);

 

return  0;

}

结果: 从消息队列里拿到第2个消息是:Hello father.

 

 

消息队列标识==>标识消息队列ID

消息类型==>标识消息ID

 

ssize_t msgrcv(int  msqid,void *ptr,size_t  nbytes,long  type,int   flag);

成功返回消息的数据部分的长度,出错返回-1.

msgrcv成功执行时,消息队列中的消息取走了就没了。

 

ptr:指向一个消息结构体(一个长整型数,跟随其后的是存放实际消息数据的缓冲区)

 

nbytes:数据缓冲区的长度,若返回的消息大于nbytes,而且在flag中设置了MSG_NOERROR,则该消息被截短(在这种情况下,不通知我们消息截短了,消息的截去部分被丢弃),如果没有设置这一标志,而消息又太长,则出错返回E2BIG(消息仍留在队列中)

 

type==0:获得消息队列中第一个消息

type>0 :获得消息队列中类型为type的第type个消息

type<0 :获得消息队列中小于或等于type绝对值的消息(类型最小的)

type0用于以非先进先出次序读消息。

 

flag       没有设置IPC_NOWAITmsgrcv以阻塞方式取消息,直到以下情况出现为止:

1、有了指定类型的消息,则取走消息,成功返回。

2、从系统中删除了此队列(出错返回-1errno设置为EIDRM

3、捕捉到一个信号并从信号处理程序返回(msgrcv返回-1errno设置为EINTR)。

 

flag       设置了IPC_NOWAITmsgrcv操作不阻塞,如果没有指定类型的消息,则msgrcv返回-1errno设置为ENOMSG

 

练习:

fork_msg.c ==> 讲解消息队列在父子进程间通信。

方法:

父进程:传递2个整数放到消息队列里,并把子进程运算结果从消息队列里取出来

子进程:拿到父进程的2个整数进行相加把结果放回消息列队里

 

思路:

创建1号消息队列拿到它的内核标识符,fork(),父进程向消息队列里放21类消息,每条消息包含了一个整数,再以阻塞方式读取子进程放的2类消息,子进程读取父进程的21类消息,经过计算以后放12类消息放到消息队列里让父进程去取,父进程取出2类消息打印后把消息队列删除掉,并wait()子进程。

fork_msg.c

#include   “include.h”

 

typedef struct

{

    long       type;

    int     num;

}Msg;

 

int main()

{

    int msgq_id=msgget(1,IPC_CREAT|0777);

    pid_t   pid=fork();

    if(pid<0)check_error(“fork”);

    else if(pid>0)

    {

        Msg msg;

        msg.type=1;

        msg.num=10;

        if(msgsnd(msgq_id,&msg,sizeof(msg.num),0)<0)

           check_error(“msgsnd”);

       

        msg.num=20;

        if(msgsnd(msgq_id,&msg,sizeof(msg.num),0)<0)

           check_error(“msgsnd”);

 

        if(msgrcv(msgq_id,&msg,sizeof(msg.num),2,0)<0)

           check_error(“msgrcv”);

        printf(“父进程读到子进程计算的结果是:%d\n”,msg.num);

        if(msgctl(msgq_id,IPC_RMID,0)<0)

           check_error(“删除消息队列“);

        wait(NULL);

    }

    else if(pid==0)

    {

        int num1,num2;

        Msg msg;

        if(msgrcv(msgq_id,&msg,sizeof(msg.num),1,0)<0)

           check_error(“msgrcv”);

        num1=msg.num;

        if(msgrcv(msgq_id,&msg,sizeof(msg.num),1,0)<0)

           check_error(“msgrcv”);

        num2=msg.num;

        msg.num=num1+num2;

        msg.type=2;

        if(msgsnd(msgq_id,&msg,sizeof(msg.num),0)<0)

           check_error(“msgsnd”);

    }

 

return  0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值