Linux 进程通信,System V 第三节------> msg queue 消息队列—转

一.简介

ipc_perm Structure

  

 1 struct ipc_perm
 2 {
 3   key_t  key;
 4   ushort uid;   /* owner euid and egid */
 5   ushort gid;
 6   ushort cuid;  /* creator euid and egid */
 7   ushort cgid;
 8   ushort mode;  /* access modes see mode flags below */
 9   ushort seq;   /* slot usage sequence number */ (linux下为 __seq)
10 };

说明:

1. 内核为每一个 system v ipc 对象提供一个 ipc_perm 结构体对象来记录其权限和模式等相关信息。

2. 创建 system v ipc 对象时指定的 mode 不受 umask 的影响。

3. 结构体中的 seq 解释为 /* slot usage sequence number */,整个 system v ipc可以理解为一个对象池,每次创建一个资源时, 分配一个 slot ,seq 标识该 slot 第几次使用(每使用一次加 1 , 溢出时 回复到 0)

关于消息队列的数据结构:

 1 #include<sys/msg.h>
 2    
 3     struct msqid_ds                        //!> msg queue id dscription ( 消息队列状态描述符 )
 4     {
 5        struct ipc_perm     msg_perm;       //!> 读写perms
 6        struct msg  *       msg_first;      //!> 队列中的第一个msg, 对于内存的存储而言的,对用户使用没有意义
 7        struct msg  *       msg_last;       //!> 队列中的最后一个msg ,对于内存的存储而言的,对用户使用没有意义
 8        msglen_t            msg_cbytes;     //!> 当前的队列中的bytes
 9        msgunum_t           msg_qnum;       //!> 当前的队列中的message
10        msglen_t            msg_qbytes;     //!> 队列中最大允许的bytes
11        pid_t               msg_lspid;      //!> 最后一个发送msg的pid
12        pid_t               msg_lrpid;      //!> 最后一个接收msg的pid
13        time_t              msg_stime;      //!> 最后一个msg发送时间
14        time_t              msg_rtime;      //!> 最后一个msg收到时间
15        time_t              msg_ctime;      //!> 最后一个msg控制时间( time of last magctl())   
16     };

消息队列的创建:函数---> msgget():可以创建或者访问一个已经存在的queue

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

int msgget(key_t key, int flag); //!> 返回值就是操作句柄

//!> key 可以是ftok()函数的返回值或者IPC_PRIVATE

//!> flag也是读写权限(一般是:IPC_CREAT或者IPC_CREAT |IPC_EXCL)

注意创建的一个队列后的msqid_ds的初始化参数是:

msg_perm的uid和cuid是当前进程的有效用户id;gid和cgid为当前进程的有效组id

msg_ctime是当前时间

msg_qbytes:为系统限制值

其余的值都是0

关于队列的操作:

>>>>>

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

//!> 参数:msqid:msgget()的返回值;ptr:是一个结构体的指针structmsgbuf { long mtype; char mtext[1] };

//!> flag:可以指定为:IPC_NOWAIT,。。。

>>>>>

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

//!> 参数:ptr;接收到的消息的储存的位置

//!> type:希望从队列获取什么样内容的消息=0:返回第一个消息;>0 : 返回类型为type的第一个消息;<0:返回类型值小于或等于type参数的绝对值的消息中类型值中最小的第一个消息

>>>>>

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

可以完成的操作:

IPC_RMID:

删除指定队列的所有数据,只能由两种进程执行,第一个:

用户有效ID为 msg_perm.cuid或者msg_perm.uid

第二个:超级用户权限进程

IPC_SET:

按照buff值设置msg_perm.uid, msg_perm.gid, msg_perm.mode,

msg_qbytes四个字段,执行进程同上面

IPC_STAT:

取队列中msqid_ds结构值放到buff中

二.

ftok()函数简介:

系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。

ftok函数原型如下:

#include <sys/ipc.h>

key_t ftok(const char *pathname, int id)

fname就时你指定的文件名,id是子序号。

说明:

1. 内核通过 key_t 类型(通常为32为整形)来标识一个 system v ipc 对象。

2. key_t 类型 变量的值由以下 3 部分(各取部分)拼接而成:

○ pathname 所指定文件的 stat 结构的 st_dev 成员

○ pathname 所指定文件的 stat 结构的 st_ino 成员

○ id 最低 8 位(id 参数只用到最低 8 位)

3. pathname指定的路径名必须存在,否则返回 -1

在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。

查询文件索引节点号的方法是: ls -i

当删除重建文件后,索引节点号由操作系统根据当时文件系统的使用情况分配,因此与原来不同,所以得到的索引节点号也不同。

如果要确保key_t值不变,要目确保ftok的文件不被删除,要么不用ftok,指定一个固定的key_t值。

同一段程序,用于保证两个不同用户下的两组相同程序获得互不干扰的IPC键值。

由于etc/config.ini(假定)为应用系统的关键配置文件,因此不存在被轻易删除的问题——即使被删,也会很快被发现并重建(此时应用系统也将被重起)。

ftok()的设计目的也在于此.

三.

 1 #include <stdlib.h>
 2 #include<string.h>  
 3 #include <stdio.h>
 4 #include <errno.h>
 5 #include <sys/types.h>
 6 #include <sys/ipc.h>
 7 #include <sys/msg.h>
 8 #include <sys/stat.h>
 9 
10 #define MSG_FILE       "server.c"                                       //!> 仅仅是创建的path而已
11 #define BUFFER       255
12 #define PERM           S_IRUSR | S_IWUSR
13 
14 typedef struct dataType
15 {
16    long   mID;
17    char    buffer[BUFFER + 1];
18 }dataType;
19 
20 struct msgtype
21 {
22    long       mtype;
23    dataType    mdata;
24 };
25 
26 int main()
27 {
28     structmsgtype   msg;
29    key_t               key;                                               //!> 每个queue都有一个自己的KEY作为标志
30    int                   msgid;                                               //!>
31   
32     if( ( key =ftok( MSG_FILE, 'a' ) ) == -1)                           //!> when fail
33    {                                                                       //!>注意'a'相当于是一个标志码而已  
34        fprintf( stderr, "Create Key error...: %s \n", strerror( errno ));
35        exit( EXIT_FAILURE );
36     }
37   
38     if( ( msg IPC_CREAT | IPC_EXCL ) ) == -1)//!> 创建 msg  queue
39     {
40        fprintf( stderr, "Create Msg error...: %s \n", strerror( errno ));
41        exit( EXIT_FAILURE );
42     }
43   
44    printf("\nMsg id = %d \n", msgid);
45 
46                                                                                    //!>注意此处的flag都是简单处理为0  
47     while( 1)                                                                 //!> 接收
48     {
49        msgrcv( msgid, &msg, sizeof( struct msgtype ), 1, 0);              
50        //!> 我们可以知道type=1,所以可以知道在client中的msg标志就是1
51        //!> ( 必须的,我们等一下可以使用进程ID的处理 )
52        fprintf( stderr, "Server receive: %s \n", msg.mdata.buffer);           //!>
53        msg.mtype = msg.mdata.mID;
54        //!> 注意此处的ID必须要换成data中客户端进程的ID( 不然客户端无法识别!!!!!!!)
55        sprintf( msg.mdata.buffer, " %d  客户端接收的回馈!",(int)msg.mtype );  
56                                                                                           //!> 注意server的回馈msg已经改变
57        msgsnd( msgid, &msg, sizeof( struct msgtype ), 0);               //!> 回发送给client
58     }
59   
60     exit( 0);  
61 }
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <errno.h>
 5 #include <sys/types.h>
 6 #include <sys/ipc.h>
 7 #include <sys/msg.h>
 8 #include <sys/stat.h>
 9 #include <stdio.h>
10 
11 #define MSG_FILE       "server.c"
12 #define BUFFER       255
13 #define PERM           S_IRUSR | S_IWUSR
14 
15 typedef struct dataType
16 {
17    long   mID;
18    char    buffer[BUFFER + 1];
19 }dataType;
20 
21 struct msgtype
22 {
23    long       mtype;
24    dataType    mdata;
25 };
26 
27 int main( int argc, char ** argv)                              
28 {
29     structmsgtype    msg;
30    key_t               key;
31    int                    msgid;
32   
33     if( argc !=2)                                                               //!> 需要输入一个字符串作为参数
34     {
35        fprintf( stderr, "Usage: %s string .\n", argv[0] );
36        exit( EXIT_FAILURE );
37     }
38   
39     if( ( key =ftok( MSG_FILE, 'a' ) ) == -1)                               //!> 获得这个Key
40     {
41        fprintf( stderr, "Create Key error...: %s \n", strerror( errno ));
42        exit( EXIT_FAILURE );
43     }
44   
45     if( ( msgid= msgget( key, PERM ) ) == -1)                           //!>获得queue的ID  
46     {
47        fprintf( stderr, "Create Msg error...:%s \n", strerror( errno ));
48        exit( EXIT_FAILURE );
49     }
50   
51     msg.mtype =1;                                                                       //!>注意此处的type需要与server中匹配  
52    msg.mdata.mID =getpid();                                                     //!> my ID
53     strncpy(msg.mdata.buffer, argv[1], BUFFER);                       //!> 获得命令行参数而已
54   
55     msgsnd(msgid, &msg, sizeof( struct msgtype ), 0);               //!> 发送给server
56     memset(&msg, '\0', sizeof( struct msgtype ));                      
57     msgrcv(msgid, &msg, sizeof( struct msgtype ), getpid(), 0);      
58    //!>获得server的反馈( 注意type是自己的ID,所以就是server的发送msg中带的 )
59     fprintf(stderr, "Client receive: %s \n", msg.mdata.buffer);      //!> 注意只接受自己ID的msg!!!
60   
61     exit(EXIT_SUCCESS );
62 }

2.运行

./s& //!> server 后台

./cILOVEYOU //!> 参数是字符串

if是多客户操作:for i in 1 2 3 4 5; do ./c sss &done //!> 注意 sss 是参数而已

3.注意:

<1>:

client对于server的发送的标志是一样的才是可以的!

因为server不可以识别多个未知的client,但是对于server的反馈而言可以根据client的ID

也就是client的接受可以根据自己的进程ID来进行不同的接受( 在网络环境下可以根据Socket套接字接受)

<2>:

注意对于msg queue 而言:必须有一个这样的数据结构:

struct msgtype

{

long mtype;

dataType mdata; //!> 注意此处的不一定只是char*,可以是自己定义的

}; //!> 但是整体的格式必须的,主要是long这个标志是必需的!!!

typedef struct dataType

{

long mID;

char buffer[BUFFER + 1];

}dataType;

注意:对于cs模式而言,server和client必须是打开同一个queue才是OK的,所以需要有相同的操作就是:

if( ( key = ftok( MSG_FILE, 'a' ) ) == -1) //!> 获得相同的Key

...

获得了相同的KEY后,对于msgget的操作(也就是打开文件,自己随便...)