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

本文详细介绍了UNIX内核中的消息队列这一进程间通信机制。消息队列作为一种先进先出的数据结构,提供了消息分类、打包等功能,并通过具体示例展示了如何在实际项目中创建、发送和接收消息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


   十二 消息队列

  消息队列是 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, mode msg_qbytes.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值