您的位置:首页 > 其它

IPC之消息队列详解

2015-03-20 10:17 225 查看
一、概述

消息队列是在两个进程之间传递二进制块数据的一种简单有效的方式。每个数据块都有一个特定的类型,接收方可以根据类型来有选择地接收数据,而不一定像管道和命名管道那些必须以先进先出的方式接收数据。



System V消息队列使用消息队列标识符(message queue identifier)标识。具有足够特权的任何进程都可以往一个给定队列放置一个消息(或者多个消息),具有足够特权的任何进程都可以从一个队列读出一个消息。(注意:和Posix消息队列一样,在某个进程往一个消息队列中写入一个消息之前,另外某个进程就可能正在等待该队列上一个消息的到达。也可能是将消息发送到消息队列,没有进程进行消息读取。)

1、对于系统中的每个消息队列,内核维护一个定义在<sys/msg.h>头文件中的信息结构msqid_ds。结构如下:

struct msqid_ds {

struct ipc_perm msg_perm; /* 消息队列的操作权限 */

time_t msg_stime; /* 最后一次调用msgsnd的时间 */

time_t msg_rtime; /* 最后一次调用msgrcv的时间 */

time_t msg_ctime; /* 最后一次被修改的时间*/

unsigned long __msg_cbytes; /* 消息队列中已有的字节数 */

msgqnum_t msg_qnum; /* 消息队列中已有的消息数*/

msglen_t msg_qbytes; /* 消息队列允许的最大字节数 */

pid_t msg_lspid; /* 最后执行msgsnd的进程的PID */

pid_t msg_lrpid; /* 之后执行msgrcv的进程的PID */

};

2、Linux消息队列的API都定义在sys/msg.h中,包括4个系统调用:msgget、msgsnd、msgrcv和msgctl。

3、消息队列主要用到的头文件为:<sys/msg.h>

二、函数分析

1、msgget系统调用

int msgget(key_t key, int msgflg);

功能:msgget系统调用创建一个消息队列,或者获取一个已有的消息队列。

参数:key参数为一个键值,用来标识一个全局唯一的消息队列。(注意:此参数可以使用ipcs命令进行查看)

msgflg:

返回值:成功时返回一个正整数,它是消息队列的标识符。失败时返回-1,并设置errno。其他三个msg函数就用它来指代该队列。它是基于指定的key产生的,而key既可以是ftok的返回值,也可以是常值IPC_PRIVATE。

1.1 当msgget()函数用于初始化一个消息队列时,msq_ds结构的如下成员被初始化:

1)msg_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cgid成员被设置成当前进程的有效组ID;

2)msgflg中的读写权限位存放在msg_perm.mode中;

3)msg_qnum、msg_lspid、msg_lrpid、msg_stime和msg_rtime被置为0;

4)msg_ctime被设置成当前时间;

5)msg_qbtypes被设置成系统限制值。

1.2 当msgget()函数处理一个已经存在的消息队列时

2、msgsnd()系统调用

功能:使用msgget()系统调用打开一个消息队列之后,我们使用msgsnd往其上放置一个消息。

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

返回值:若成功的话就返回0,并将修改内核数据结构msqid_ds的部分字段(为:将msg_qnum加1;将msg_lspid设置为调用进程的PID;将msg_stime设置为当前的时间。);出错返回-1,并设置errno。

参数:msqid是由msgget系统调用返回的标识符。msgp是一个结构指针,该结构具有如下的模板。它定义在<sys/msg.h>中。

struct msgbuf {
long mtype;     /* message type, must be > 0 */
char mtext[1];  /* message data */
};


注意:1、消息类型mtype必须大于0,因为对于msgrcv函数来说,非正的消息类型用作特殊的指示器。

2、msgbuf结构定义中的名字mtext不大确切,消息的数据部分并不局限于文本。任何形式的数据都是允许的,无论是二进制数据还是文本。因为内核根本不解释消息数据的内容。

3、我们使用“模板”的说法描述这个结构,因为msgp所指向的只是一个含有消息类型的长整数,消息本身则紧跟在它之后(如果消息长度大于0字节)。不过大多数应用并不使用msgbuf结构的这个定义。因为其数据量(1个字节)通常是不够的。一个消息中的数据量并不存在编译时限制(其系统限制则通常可由系统管理员修改),因此可不去声明一个数据量很大(比一个给定应用可能支持的数据还要大)的结构,而去定义一个上述的模板。

例如:如果某个应用需要交换由一个16位整数后跟一个12字节字符数组构成的消息,它可以定义自己的结构如下:

#define SIZE     16
typedef  struct  my_msgbuf{
long  int  mtype;/*message  type*/
int    mshort;/*start of message data*/
char  buffer[SIZE];
}Message


msgsnd的msgsz参数以字节为单位指定待发送消息的长度。这是位于长整数消息类型之后的用户自定义数据的长度。在上面的例子中,长度可以传递成sizeof(Message) - sizeof(long)。

msgsnd的msgflg参数既可以是0,也可以是IPC_NOWAIT。IPC_NOWAIT标志使得msgsnd调用非阻塞(nonblocking):如果没有存放新消息的可用空间,该函数就马上返回。这个条件可能发生的情况:

1)在指定的队列中已经有太多的字节(对应该队列的msqid_ds结构中的msg_qbytes值);

2)在系统范围存在太多的消息。

如果这两个条件中有一个存在,并且IPC_NOWAIT标志已经指定,msgsnd就返回一个EAGAIN错误。如果这两个条件中有一个存在,但是IPC_NOWAIT标志未指定,那么调用进程被投入睡眠,直到:

1)具备存放新消息的空间;

2)由msqid标志的消息队列从系统中删除(这种情况下返回一个EIDRM错误);

3)调用线程被某个捕获的信号所中断(这种情况下返回一个EINTR错误)。

3、msgrcv()系统调用

功能:使用msgrcv()函数从某个消息队列中读出一个消息。

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

返回值:若成功则为读入到缓冲区中数据的字节数(即所接收消息中数据的字节数,它不包括也通过msgp参数返回的长整型消息类型所需的几个字节),若出错则为-1.

参数:msgp参数指定所接收消息的存放位置。跟msgsnd函数一样,该指针指向紧挨在真正的消息数据之前返回的长整数类型字段。

msgsz指定了由msgp指向的缓冲区中数据部分的大小。这是该函数能返回的最大数据量。该长度不包括长整型类型字段。

msgtyp指定希望从所给定的队列中读出什么样的消息:

1)如果msgtyp为0,那就返回该队列中的第一个消息。既然每个消息队列都是作为一个FIFO(先进先出)链表维护的,因此msgtyp为0指定返回该队列中最早的消息;

2)如果msgtyp大于0,那就返回其类型值为msgtyp的第一个消息;

3)如果msgtyp小于0,那就返回其类型值小于或等于msgtyp参数的绝对值的消息中类型值最小的第一个消息。

msgrcv的msgflg参数指定所求类型的消息不在所指定的队列中该做何处理。在没有消息可得的情况下,如果设置了msgflg中的IPC_NOWAIT位,msgrcv函数就立即返回一个ENOMSG错误。否则,调用者被阻塞到下列某个事件发生为止:

1)有一个所请求类型的消息可获取;

2)由msqid标识的消息队列被从系统中删除(这种情况下返回一个EIDRM错误);

3)调用线程被某个捕获的信号所中断(这种情况下返回一个EINTR错误)。

注意:msgflg参数中另有一位可以指定:MSG_NOERROR。当所接收消息的真正数据部分大于msgsz参数时,如果设置了该位,msgrcv函数就只是截短数据部分,而不是返回错误。否则,msgrcv返回一个E2BIG错误。

4、msgctl()系统调用

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

功能:msgctl()函数提供在一个消息队列上的各种控制操作。

返回值:若成功则返回0,若出错则返回-1.

4.1 msgctl()函数提供3个命令:

1)IPC_RMID 从系统中删除由msqid指定的消息队列。当前在该队列上的任何消息都被丢弃。对于该命令而言。msgctl()函数的第三个参数被忽略;

2)IPC_SET 给所指定的消息队列设置其msqid_ds结构的以下4个成员:msg_perm.uid、msg_perm.gid、msg_perm.mode和msg_perm.qbytes。它们的值来自由buf参数指向的结构中的相应成员;

3)IPC_STAT (通过buf参数)给调用者返回与所指定消息队列对应的当前msqid_ds结构。

三、实例

四、总结以及注意事项

1、ftok

key_t ftok(const char *pathname, int proj_id);

功能:函数ftok()把一个已经存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键。

返回值:成功时返回key_t类型的key值(IPC键值),失败则返回-1。

参数:pathname就是我们指定的文件名(该文件必须是存在且可以访问的),proj_id是子序号,虽然为int,但是只有8个比特被使用(0-255);

1.1

该函数假定对于使用System V IPC的某个给定应用来说,客户和服务器同意对该应用有一定意义的pathname。它可以是服务器守护程序的路径名、服务器使用的某个公共数据文件的路径名或者系统上的某个其他路径名。如果客户和服务器之间只需单个IPC通道,那么可以使用譬如说值为1的proj_id。如果需要多个IPC通道,譬如说从客户到服务器一个通道,从服务器到客户又一个通道,那么作为一个例子,一个通道可使用值为1的proj_id,另一个通道可以使用值为2的proj_id。客户和服务器一旦在pathname和proj_id上达成一致,双方就都能调用ftok函数把pathname和id转换成同一个IPC键。

ftok函数的典型实现调用stat函数,然后组合以下三个值:

1)pathname所在的文件系统的信息(stat结构的st_dev成员);

2)该文件在本文件系统内的索引节点号(stat结构的st_ino成员);

3)id的低序8位(不能为0)。(因为索引节点绝不会是0,因此大多数实现把IPC_PRIVATE定义为0)

这三个值的组合通常会产生一个32位键。不能保证两个不同的路径名与同一个id的组合产生不同的键,因为上面所列三个条目(文件系统标识符、索引节点、id)中的信息位数可能大于一个整数的信息位数。

1.2 关于pathname路径

如果pathname不存在,或者对于调用进程不可访问,ftok就返回-1。注意,路径名用于产生IPC键的文件不能是在服务器存活期间由服务器反复创建并删除的文件,因为该文件每次创建时由系统赋予的索引节点号很可能不一样,于是对于下一个调用者来说,由ftok返回的键也可能不同。

注意:

三种类型的System V IPC使用key_t值作为它们的名字。头文件<sys/types.h>把key_t这个数据类型定义为一个整数,它通常是一个至少32位的整数。这些整数值通常是由ftok函数赋予的。

2、消息类型问题

3、各个宏的含义

1)IPC_NOWAIT

2)MSG_EXCEPT

3)MSG_NOERRNO

4)IPC_PRIVATE:

五、参考

参考:Linux高性能服务器编程



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: