Linux进程间的通信方式:运用消息队列通信

一、消息队列的概念:

背景:Unix早期通信的机制之一信号,它能够传送的信息量有限,而无名管道和有名管道则是典型地随进程持续IPC,只能传送无格式的字节流,这无疑给应用程序开发带来不便。

消息队列:消息队列是一个消息的链表,可以把消息看作一个记录,且具有特定的格式及特定的优先级。对消息队列有读权限的进程可以从消息队列中读走消息,而对消息队列有写权限的则可以向消息队列中写入消息数据。

 

二、消息队列的两大分类:

目前,消息队列主要分为两类:POSIX消息队列和系统V消息队列,目前系统V消息队列被大量使用。

系统V消息队列是随内核持续的,只有在内核重起或者认为的删除一个消息队列时,该消息队列才会真正的被删除。每个消息队列都有一个队列头,其中包含了队列的大量信息如:消息队列的键值、用户ID、组ID、消息队列中的消息数目等。

 

三、键值

键值的概念:消息队列的内核持续性要求每个消息队列在系统范围内都有对应的唯一的键值,要获得一个消息队列的描述字,必须提供该消息队列的键值,这在后面创建消息队列时将会提及。

键值的获取:对于键值的获取,我们通常通过ftok()函数来获得

函数原型:key_t  ftok(char *pathname,char proj);

函数的作用:返回文件名对应的键值

参数:

pathname:文件名

Proj:项目名(不为0即可)

 

四、消息队列的使用步骤:

(1)创建消息队列:

对于创建消息队列是使用msgget函数来实现的,下面是函数的介绍:

函数的原型:int msgget(key_t key,int msgflg);

头文件:

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

参数:

Key: 键值,可以设定数值,也可以设置为IPC_PRIVATE,也可以由ftok()获得。

msgflg:

IPC_CREAT:无消息队列时创建消息队列

IPC_EXCL:只有在消息队列不存在时创建消息队列,否则报错

IPC_NOWAIT:读写消息队列无法得到满足时不等待,之间返回

返回值:执行成功时,返回与键值key对应的消息队列描述字;执行出错,返回-1

 

(2)从消息队列中读取数据

从消息队列读取消息数据,通常使用msgrcv函数

函数的原型:int msgrcv(int msqid, void *msgp, size_t msgsz, long int msgtyp, int msgflg);

头文件:

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

参数:

msqid:消息队列的ID(描述字)

msgp:消息读取后存放的内存空间首地址,是消息结构类型的指针

msgsz:消息数据的长度

msgtyp:请求读取的消息类型(正整数)

msgflg:接收标志

IPC_NOWAIT:读不到数据时直接返回,不等待

   0:一直等待到成功读取数据

返回值:执行成功,返回0;执行出错,返回-1

 

 

3)发送数据到消息队列:

实现数据的发送,使用的是msgsnd函数

函数的原型:int msgsnd(int msqid, struct msgbuf *msgp, int msgsz,int msgflg);

头文件:

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

参数:

msqid:已打开的消息队列ID(描述字)

msgp:指向消息结构的指针,结构如下:

struct msgbuf

{

long mtype; //消息类型,大于0

char mtext[1]; //消息数据的首地址

};

msgsz:消息数据的长度

msgflg:发送标志

   IPC_NOWAIT:写不进数据时直接返回,不等待

   0:一直等待到成功写入数据

返回值:执行成功,返回0;执行出错,返回-1

 

(4)控制消息队列

通常在使用完消息队列后用msgctl函数删除消息队列

函数的原型:int msgctl(int msqid, int cmd, struct msqid_ds *buf);

头文件:

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

参数:

msqid:消息队列的ID(描述字)

cmd:相应的命令

IPC_STAT:获取消息队列信息,返回信息存在buf指向的msqid结构中

IPC_SET:设置消息队列的属性

IPC_RMID:删除消息队列

buf:消息队列的结构类型变量

返回值:执行成功,返回0;执行出错,返回-1


五、通过上面对消息队列使用的描述,这里我们作一个 实践,以两个不相关的进程来进行消息队列的通信。对于msg2.c创建消息队列,读取消息队列的消息数据,另一个文件msg1.c打开消息队列,向消息队列中写入消息数据。在两个文件中都定义了如下的相同的数据结构,每个消息都是这样的数据结构:

struct msg

{
    long type;
    char message[MAX_SIZE];
};

对于发送消息来说,预置了msg缓冲区并向其中写入消息类型和消息数据,再调用发送消息函数msgsnd即可;对于接收消息来说,将从消息队列中读取到的数据放置到预置的msg缓冲区中并打印到输出设备中显示。
具体实现如下:

发送消息进程:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <stdlib.h>

#define MAX_SIZE 512

struct msg //消息的数据结构
{
    long type; //消息类型
    char message[MAX_SIZE]; //消息数据
};

int main(void)
{
    int msgid;
    int running = 1;

    struct msg temp;

    char buf[MAX_SIZE]; //输入数据存放区

    msgid = msgget((key_t)1234,0666|IPC_CREAT); //创建消息队列
    if(msgid == -1)
    {
        printf("队列创建失败!\n");
        exit(1);
    }

    while(running)
    {
        printf("enter some text:");

        fgets(buf,MAX_SIZE,stdin);
        strcpy(temp.message,buf); //将输入的数据存放到消息结构的message[]中存储

        temp.type = 1; //定义消息类型

        if((msgsnd(msgid,&temp,MAX_SIZE,0)) == -1) //向消息序列写数据
        {
            printf("写入数据失败\n");
            exit(1);
        }

        if((strncmp(buf,"end",3)) == 0) //输入为end退出
        {
            running = 0;
        }

    }

    return 0;
}
接收消息进程

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <stdlib.h>

#define MAX_SIZE 512

struct msg //定义消息结构
{
    long type; //数据类型
    char message[MAX_SIZE];//消息数据
};

int main()
{
    int msgid;
    int running = 1;

    struct msg temp;

    long int msg_rcv = 1;

    char buf[MAX_SIZE];

    msgid = msgget((key_t)1234,0666|IPC_CREAT); //打开消息队列
    if(msgid == -1)
    {
        printf("队列创建失败\n");
        exit(1);
    }

    while(running)
    {
        if(msgrcv(msgid,(void*)&temp,MAX_SIZE,msg_rcv,0) == -1) //读取消息队列数据
        {
            printf("队列数据读取失败\n");
            exit(1);
        }

        printf("读到数据为:%s",temp.message);

        if(strncmp(temp.message,"end",3) == 0) //输入为end退出
        {
            running = 0;
        }
    }

    if(msgctl(msgid,IPC_RMID,0) == -1) //删除消息队列
    {
        printf(" 删除队列失败\n");
        exit(1);
    }
    else
    {
        printf("删除队列成功\n");
        exit(0);
    }

}

执行效果:

输入数据

读取数据

 注意:这里数据读输入与读取的消息类型必须相同,否则将无法读取到写入的消息数据

六、总结

消息队列与管道相比,具有更大的灵活性。它可以提供有格式的字节流,有利于减少开发人员的工作量;其次,消息队列的消息具有类型,可作为优先级使用;同时消息队列可以在没有关系的多个进程间复用;其随着内核持续,生命力更久。

 

      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值