学习笔记之消息队列

      我们比较常用的IPC,比如管道,FIFO等,这些都称为经典的进程间的通信,用的也比较多,但是进程间的通信绝对不会这有这两个,我想在这篇文章中较深的谈谈另一种进程间的通信——消息队列!

      可能很多有过编程经验的人到现在都没有使用过它,因为这个IPC在使用的时候又要使用一些其他的系统库例程的调用,而一些其他的IPC也可以实现消息队列的功能,所以,在使用的时候可能比较少,但是一样的东西被造出来肯定是有它一定的道理,没有谁愿意去花心思去把一堆废品抱回家里,所以笔者在这篇文章中较深的讲解消息队列,以及它的一些优点,和一些必要的系统库例程!

     

      XSI说明的IPC还有信号量和共享存储空间,在这么不作讨论。首先我们来思考一个问题,如何来确定一个IPC结构呢?大家应该都知道,在进行文件I/O的时候,可以通过内核返回的文件描述符进行I/O操作,那是不是也应该有一个类似文件描述符的东东来标识一个IPC结构呢?非常正确,当程序在调用msgget(创建一个消息队列,后面会介绍到的)的时候会返回一个IPC结构的标识符,用来标识这个消息队列。那我们再来思考一个问题,所谓的进程间的通信应该是多个进程之间的信息的传输,那其他需要使用该消息队列的进程那怎么办呢?这个时候有一个方法就是一个进程在创建消息队列后返回的队列ID(IPC结构标识符)保存在一个文件里面,然后需要向消息队列中传输消息的进程可以从这个文件中读取这个标识符,这虽然是一个方法,但是每一个需要访问该消息队列的进程都需要去打开那个文件然后读取标识符是不是很麻烦呢?那么是不是还有其他的方法呢?答案是肯定的,其实每一个IPC结构的一个对象都有一个键(key_t)与之相关联,通过访问这个键就可以访问对应的IPC结构对象,在创建一个消息队列之前首先与其他的进程共同约定一个键,然后使用这个指定的键来创建一个消息队列,这样在访问消息队列的时候就可以通过这个共同约定的键进行访问!这样有引来了一个新的问题,怎样让多个进程约定一个键呢?

       这里有两个方法,分别讲述一下,第一种是首先在一个文件中创建一个键,然后把这个文件包含到需要使用消息队列的进程中,这个方法有一个缺点就是有可能这个指定的键已经与一个IPC结构的对象相关联了,那么再使用此键去创建消息队列的时候就会报错。第二种方法就是几个需要使用消息队列的进程之间共同约定一个文件和一个工作ID,然后再调用int ftok(const char *path,int id)生成一个键,这样产生的键可以保证其唯一性,然后就可以用这个生成的键创建消息队列了。

       接下来就具体讨论一些有关消息队列的系统库例程,说了这么久还不知道如何具体的创建一个消息队列。

        #include <sys/msg.h>

        int msgget(key_t key,int flag);(成功返回IPC结构标识符,失败返回-1)

这个函数可以实现两个功能,一个就是创建一个消息队列,而另一个就是打开一个现存的消息队列,创建消息队列可以用两种方式实现,一种是指定键值为IPC_PRIVATE时,指定此键的时候总是会创建一个消息队列。还有一个方法就是向msgget传一个没有与任何IPC结构对象相关联的键,并且在flag指定为IPC_CREAT时也可以创建一个消息队列。这里应该要注意的就是IPC_PRIVATE这个键,当在msgget中指定这个键时,msgget总是会创建一个消息队列。

       现在谈谈当一个怎么描述一个消息队列呢?谈到这个问题就要说到一个结构了。

       struct msqid_ds{

struct ipc_perm msg_perm;

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

msglen_t msg_qbytes;//一个消息的最大字节数

pid_t msg_lspid;//最后一个向该消息队列发送消息的进程

pid_t msg_lrpid;//最后一个向该消息队列读取消息的进程

time_t stime;//消息队列最后接收消息的时间

time_t rtime;//消息队列最后读取消息的时间

time_t ctime;//消息队列最后改变的时间

...

};

在这里又谈到了一个结构,在这里简单的说说这个结构体的作用。

struct ipc_perm{

uid_t uid;

uid_t cuid;

gid_t gid;

gid_t cgid;

mode_t mode;

...

};

这个结构体一看应该就可以看出是什么,这个结构体描述了消息队列的创建者,所属的用户,当一些准备访问消息队列就会使用这个消息队列进行权限的检查。后面还会讨论到这个结构体的。


好了,我们再来讨论下一个系统函数:

#include <sys/msg.h>

int msgctl(int msqid,int cmd,struct msqid_ds *buf);(成功返回0,失败返回-1)

      这个函数会对指定的消息队列进行一些控制操作,传入不同的cmd进行不同的操作,下面就一一介绍:

      当cmd = IPC_STAT,此时msgctl取指定的消息队列的struct msqid_ds然后保存第三个参数指定的msqid_ds的结构中。

      当cmd = IPC_SET,此时会根据第三个参数指定的msqid_ds的结构对指定的消息队列进行设置,在设置的时候会检查进程的有效用户ID是否是否具有相应的设置的权限。

      当cmd = IPC_RMID,指定此标志是对指定的消息队列删除,如果删除之后还有其他的进程继续访问该消息队列,这时会放回给EIDRM的错误给访问进程。

       总之,使用此函数可以对指定的消息队列进行一些控制操作。

 

       接下来就具体的讨论如何访问一个已经创建的消息队列:

       #include <sys/msg.h>

       int msgsnd(int msqid,const void *ptr,size_t nbytes,int flag); (成功返回0,失败返回-1)    

      当一个进程调用msgsnd时,向指定的进程发送消息,发送的消息总是放在消息队列的尾部。

      现在聊一下当调用msgsnd指定IPC_NOWAIT的标志时,还记得O_NONBLOCK这个标志吗?这个标志和O_NONBLOCK很像,就是在向消息队列发送消息的时候不会阻塞,如果消息不能立刻被消息队列接收,那么此次的调用就会出错,如果没有指定这个标志呢?那又将会怎样呢?当消息队列不能立刻接收消息时,那么此次的调用就会阻塞,在这个时候就会有三种情况会发生了,第一,会阻塞到消息队列可以接收这个消息,第二,在阻塞期间,这个消息队列被其他的进程删除了,这个时候,此次的函数调用就会返回出错,并且设置errno为EIDRM,第三,一个信号的发生将此次函数调用中断了,此时,此次调用也回返回出错,并且设置errno为EINTR,至于你是否设置IPC_NOWAIT这个标志就要靠自己权衡利弊了。


      最后就是从消息队列中读取消息了,这个其实没有什么太多好说了,因为和msgsnd许多都差不多,还是首先看看这个函数吧!

      #include <sys/msg.h>

int msgrcv(int msqid,void *ptr,size_t bytes,int type,int flag)

在调用msgrcv从消息队列中取消息的时候,并不一定要从消息队列的起始部分开始取,可以根据消息的类型从消息队列中取出消息.

当type == 0时,取消息队列的第一个消息;

当type > 0 时,取指定类型的消息;

当type < 0 时,取其绝对值之内的类型的消息;

好了,讲到这里觉得词穷了,希望这篇文章能够帮到对消息队列还有所不懂的人...                        

                                                                                            continue...... 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值