学习笔记08-学习《精通UNIX下C语言编程及项目实践》

来源:互联网 发布:淘宝联盟手机能提现吗 编辑:程序博客网 时间:2024/06/05 01:51


  十二 消息队列

  消息队列是UNIX内核中的一个先进先出的链表结构. 相对于管道, 消息队列有明显的优势, 原因在于:

  (1) 消息队列是一种先进先出的队列型数据结构, 可以保证先送的货物先到达, 后送的货物后到达, 避免了插队现象.

  (2) 信息队列将输出的信息进行了打包处理, 这样就可以保证以每个消息为单位进行接收了.

  (3) 消息队列还可以对信息进行分类处理, 标记各种类别的信息, 这样就可以根据信息类别分别出列.

  IPC就是进程间通信, 侠义上讲, IPC指消息队列, 信号量和共享内存三种对象. 通过shell命令ipcs可以查询当前系统的IPC对象信息:

[bill@billstone Unix_study]$ ipcs

 

------ Shared Memory Segments --------

key        shmid      owner      perms      bytes      nattch     status

0x00000000 196609     bill      777        393216     2          dest

0x00000000 491522     root      644        106496     2          dest

0x00000000 524291     root      644        110592     2          dest

0x00000000 557060     root      644        110592     2          dest

0x00000000 589829     root      644        110592     2          dest

 

------ Semaphore Arrays --------

key        semid      owner      perms      nsems

 

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages

 

[bill@billstone Unix_study]$

  消息队列简介

  UNIX内核使用结构msqid_ds来管理消息队列:

struct msqid_ds {

        struct ipc_perm msg_perm;

        struct msg *msg_first;          /* first message on queue, unused  */

        struct msg *msg_last;           /* last message in queue, unused */

        __kernel_time_t msg_stime;      /* last msgsnd time */

        __kernel_time_t msg_rtime;      /* last msgrcv time */

        __kernel_time_t msg_ctime;      /* last change time */

        unsigned long  msg_lcbytes;     /* Reuse junk fields for 32 bit */

        unsigned long  msg_lqbytes;     /* ditto */

        unsigned short msg_cbytes;      /* current number of bytes on queue */

        unsigned short msg_qnum;        /* number of messages in queue */

        unsigned short msg_qbytes;      /* max number of bytes on queue */

        __kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */

        __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */

};

  其中msg结构的定义如下, 我们在实际项目中几乎很少使用如下的结构, 一般都需要我们根据实际的需求自行定义.

Struct msg{

    struct msg* msg_next;

    long msg_type;

    long msg_ts;

    short msg_spot;

};

  理论上可以通过结构msqid_ds的成员msg_first, msg_last和结构msg的成员msg_next遍历全部消息队列并完成管理和维护消息队列的功能, 但实际上这三个成员是UNIX内核的直辖数据, 用户无法引用.

  消息队列中消息本身是由消息类型和消息数据组成, 我们常常使用如下结构作为消息模板:

struct msgbuf {

        long mtype;         /* type of message */

        char mtext[1];      /* message text */

};

  根据消息类型的不同, 我们可以在同一个信息队列中定义不同功能的消息.

  使用消息队列

  (1) 消息队列的创建

  UNIX, 采用函数msgget创建消息队列

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

extern int msgget (key_t __key, int __msgflg) __THROW;

  函数msgget创建一个新的消息队列, 或者访问一个已经存在的消息队列. 调用成功时返回消息队列的标志符, 否则返回-1.

  (2) 消息队列的发送和接收

  UNIX中函数msgsnd向消息队列发送消息, 原型如下:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

extern int msgsnd (int __msqid, __const void *__msgp, size_t __msgsz, int __msgflg) __THROW;

  发送消息一般分五个步骤:

 a) 根据自己的需要定义消息结构

struct msgbuf {

        long mtype;         /* type of message */

        char ctext[100];      /* message data */

};

 b) 打开或创建消息队列

msgid = msgget(Key, 0666 | IPC_CREAT);

if(msgid < 0) 打开(或创建)队列失败

 c) 组装消息

 d) 发送消息

int ret;

ret = msgsnd(msgid, (void *)&buf, strlen(buf.ctext), IPC_NOWAIT);

 e) 发送判断

If (ret == -1){

    if (errno == EINTR) 信号中断, 重新发送;

    else 系统错误

}

  下面是一个发送消息的实例: 它循环读取键盘输入, 将输入的字符串信息写入到消息队列(关键字为0x1234).

[bill@billstone Unix_study]$ cat msg1.c

#include <sys/msg.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <stdio.h>

#include <sys/errno.h>

 

extern int errno;

 

struct mymsgbuf{

        long mtype;

        char ctext[100];

};

 

int main(void)

{

        struct mymsgbuf buf;

        int msgid;

 

        if((msgid = msgget(0x1234, 0666 | IPC_CREAT)) < 0){

                fprintf(stderr, "open msg %X failed/n", 0x1234);

                exit(1);

        }

        while(1){

                printf("Please input: ");

                memset(&buf, 0, sizeof(buf));

                fgets(buf.ctext, sizeof(buf.ctext), stdin);

                buf.mtype = getpid();

                while((msgsnd(msgid, (void *)&buf, strlen(buf.ctext), IPC_NOWAIT)) < 0){

                        if(errno == EINTR)

                                continue;

                        exit(2);

                }

                if(!strncmp(buf.ctext, "exit", 4))

                        break;

        }

 

        return 0;

}

[bill@billstone Unix_study]$

  运行结果如下:

[bill@billstone Unix_study]$ make msg1

cc     msg1.c   -o msg1

[bill@billstone Unix_study]$ ipcs -q

 

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages

 

[bill@billstone Unix_study]$ ./msg1

Please input: Hello world!

Please input: Nice to meet you!

Please input: bye!

Please input: exit

[bill@billstone Unix_study]$ ipcs -q

 

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages

0x00001234 98304      bill       666        41           4

 

[bill@billstone Unix_study]$

  UNIX中函数msgrcv从消息队列中接收消息, 原型如下:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

extern int msgrcv (int __msqid, void *__msgp, size_t __msgsz, long int __msgtyp, int __msgflg) __THROW;

  与函数msgsnd不同,这里参数msgsz指的是缓冲区的最大容量,包括消息类型占用的部分.

  这里配合上面的例子设计接收消息的实例: 以阻塞方式不断地从消息队列(关键字为0x1234)中读取消息, 并打印接收到的消息类型, 长度和数据等信息, 当接收到数据内容为'exit'时程序结束.

[bill@billstone Unix_study]$ cat msg2.c

#include <sys/msg.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <stdio.h>

#include <sys/errno.h>

 

extern int errno;

 

struct mymsgbuf{

        long mtype;

        char ctext[100];

};

 

int main(void)

{

        struct mymsgbuf buf;

        int msgid, ret;

 

        if((msgid = msgget(0x1234, 0666 | IPC_CREAT)) < 0){

                fprintf(stderr, "open msg %X failed/n", 0x1234);

                exit(1);

        }

        while(1){

                memset(&buf, 0, sizeof(buf));

                while((ret = msgrcv(msgid, (void *)&buf, sizeof(buf), 0, 0)) < 0){

                        if(errno == EINTR)

                                continue;

                        fprintf(stderr, "Error no: %d", errno);

                        exit(2);

                }

                fprintf(stderr, "Msg: Type=%d, Len=%d, Text:%s", buf.mtype, ret, buf.ctext);

                if(!strncmp(buf.ctext, "exit", 4))

                        break;

        }

 

        return 0;

}

[bill@billstone Unix_study]$

  运行结果如下:

[bill@billstone Unix_study]$ make msg2

cc     msg2.c   -o msg2

[bill@billstone Unix_study]$ ipcs -q

 

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages

0x00001234 98304      bill       666        41           4

 

[bill@billstone Unix_study]$ ./msg2

Msg: Type=15666, Len=13, Text:Hello world!

Msg: Type=15666, Len=18, Text:Nice to meet you!

Msg: Type=15666, Len=5, Text:bye!

Msg: Type=15666, Len=5, Text:exit

[bill@billstone Unix_study]$ ipcs -q

 

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages

0x00001234 98304      bill       666        0            0

 

[bill@billstone Unix_study]$

  从上面可以看到, 采用消息队列通信比采用管道通信具有更多的灵活性.

  系统调用msgctl对消息队列进行各种控制, 包括查询消息队列数据结构, 改变消息队列访问权限, 改变消息队列属主信息和删除消息队列等, 原型如下:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

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

  根据cmd参数对msqid消息队列操作:

  a) IPC_RMID: 删除消息队列

  b) IPC_STAT: 读取消息队列

  c) IPC_SET: 重置消息队列结构msqid_ds中的成员uid, gid, modemsg_qbytes.

原创粉丝点击