进程间通信(二)---消息队列

19 篇文章 0 订阅
8 篇文章 0 订阅

前面我们讲了进程间通信的其中一种方式,进程间通信(一)—管道,现在我们来讲一下另外一种方式就是消息队列

1: 消息队列提供了从一个进程向另外一个进程发送一块数据的方法。
2:每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
3:消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数也是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)。

IPC对象数据结构

struct ipc_perm
{
    __kernel_key_t  key;//消息队列的标识符
    __kernel_uid_t  uid;
    __kernel_gid_t  gid;
    __kernel_uid_t  cuid;
    __kernel_gid_t  cgid;
    __kernel_mode_t mode; //权限
    unsigned short  seq;
};

消息队列结构

struct msqid_ds
{
    struct ipc_perm msg_perm;
    //first message on queue,unused
    struct msg *msg_first;
    //last message in queue,unused
    struct msg *msg_last;
    //last msgsnd time      
    __kernel_time_t msg_stime;
    //last msgrcv time
    __kernel_time_t msg_rtime;
    //last change time  
    __kernel_time_t msg_ctime;
    //Reuse junk fields for 32 bit
    unsigned long  msg_lcbytes; 
    //ditto
    unsigned long  msg_lqbytes;
    //current number of bytes on queue
    unsigned short msg_cbytes;
    //number of messages in queue   
    unsigned short msg_qnum;
    //max number of bytes on queue  
    unsigned short msg_qbytes;
    //pid of last msgsnd
    __kernel_ipc_pid_t msg_lspid;
    //last receive pid  
    __kernel_ipc_pid_t msg_lrpid;
};

先要使用消息队列我们就必须先来认识一下相关的消息队列函数

msgget函数
创建一个消息队列

int msgget(key_t key,int msgflg);
//参数:key表示某个消息队列的名字
//msgflg有9个权限标志构成,他们的用法和创建文件时使用的mode模式标志是一样的
//返回值:成功返回一个非负整数,即该消息队列的标识码,失败返回-1

msgflg设置值:IPC_CREAT和IPC_EXCL
1:同时设置,要创建的消息队列已经存在则出错返回
2:同时设置,要创建的消息队列不存在则创建新的消息队列成功返回
3:单独设置IPC_CREAT,要创建的消息队列不存在则创建返回
4:单独设置IPC_CREAT,要创建的消息队列已存在则获取该消息队列
5:key_t ftok(const char *pathname,int proj_id);//获取队列标识符的函数

msgctl函数
控制消息队列

int msgctl(int msgid,int cmd,struct msqid_ds *buf);
//参数:msgid由msgget函数返回的消息队列标识码
//cmd:是将要采取的动作(有三个可取值)
//成功返回0,失败返回-1

cmd的取值
IPC_STAT:把msqid_ds结构中的数据设置为消息队列的当前关联值
IPC_SET:在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值
IPC_RMID:删除消息队列

msgsnd函数
把一条消息添加到消息队列中

int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);
//参数:msgid由msgget函数返回的消息队列标识码
//msgp:是一个指针,指针指向准备发送的消息
//msgsz:是msgp指向的消息长度
//msgflg:控制着当前消息队列满或到达西永上限时将要发生的事情
//msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误。
//返回值:成功返回0,失败返回-1

1:消息队列在两方面受到制约:首先它必须小于系统规定的上限值,其次,它必须以一个long int长整数开始,接收者函数将利用这个长整数确定消息的类型
2:消息结构参考形式如下:
struct msgbuf
{
long mtype;
char mtext[1];
}

msgrcv函数
从一个消息队列接收消息

ssize_t msgrcv(int msgid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
//参数:msgid由msgget函数返回的消息队列标识码
//msgp:是一个指向准备接收的消息
//msgsz:由msgp指向的消息长度,这个长度不含保存消息类型的那个long int 长整形
//msgtyp:他可以实现接收优先级的简单形式
//msgflg:控制着队列中没有相应类型的消息可供接收收时将要发生的事情
//返回值:成功返回实际放到接收缓冲区离去的字符个数

msgtyp=0返回队列第一条信息
msgtyp<0返回队列第一条类型小于等于msgtyp绝对值的消息,并且是满足条件的消息类型最小的消息
msgtyp>0返回队列第一条类型等于msgtyp的消息
msgflg=IPCNOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
msgflg=MSG_NOERRNO,消息大小超过msgsz时被截断
msgtype>0qiemsgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息。

下面我们以代码实例演示两个进程间的通信,实现两个进程间的对话效果。
Makefile文件

.PHONY:all

all:process1.c process2.c

process1:process1.c
    gcc $^ -o $@

process2:process2.c
    gcc $^ -o $@

.PHONY:clean

clean:
    rm -f process1 process2

com.h文件

#pragma once
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/msg.h>
#include<string.h>
#define PATHNAME "."
#define PROJ_ID 0X6666
#define process1_type 1
#define process2_type 2
struct msgbuf
{
    long mtype;
    char mtext[1024];
};
//创建消息队列函数
int createMsgQueue();
//获取消息队列函数
int getMsgQueue();
//销毁消息队列函数
int destroyQueue(int msgid);
//发送消息函数
int sendMsg(int msgid,int who,char *msg);
//接收消息函数
int recvMsg(int msgid,int recvType,char out[]);

com.c文件

#include"com.h"
//success > 0 ,failed == -1
static int comMsgQueue(int flags)
{
    //获取队列标识符赋值给key
    key_t key = ftok(PATHNAME,PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    //创建消息队列
    int msgid = msgget(key,flags);
    if(msgid < 0)
    {
        perror("msgget");
    }
    //成功会返回该队列的标识符
    return msgid;
}
//创建消息队列
int createMsgQueue()
{
    return comMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}
int getMsgQueue()
{
    return comMsgQueue(IPC_CREAT);
}
//封装msgctl函数
int destroyMsgQueue(int msgid)
{
    if(msgctl(msgid,IPC_RMID,NULL) < 0)
    {
        perror("msgctl");
        return -1;
    }
    return 0
}
//封装msgsnd函数
int sendMsg(int msgid,int who,char *msg)
{
    struct msgbuf buf;
    buf.mtype = who;
    strcpy(buf.mtext,msg);
    if(msgsnd(msgid,(void*)&buf,sizeof(buf.mtext),0) < 0)
    {
        perror("msgsnd");
        return -1;
    }
    return 0;
}
//封装msgrcv函数
int recvMsg(int msgid,int recvType,char out[])
{
    struct msgbuf buf;
    if(msgrcv(msgid,(void*)&buf,sizeof(buf.mtext),recvType,0) < 0)
    {
        perror("msgrcv");
        return -1;
    }
    strcpy(out,buf.mtext);
    return 0;
}

process1.c文件

#include"com.h"

int main()
{
    int msgid = createMsgQueue();
    char buf[1024];
    while(1)
    {
        buf[0] = 0;
        recvMsg(msgid,process2_type,buf);
        printf("process2 say %s\n",buf);

        printf("Please Enter# ");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf));
        if(s > 0)
        {
            buf[s-1] = 0;
            sendMsg(msgid,process1_type,buf);
            printf("send done,wait recv...\n");
        }
    }
    destroyMsgQueue(msgid);
    return 0;
}

process2.c文件

#include"com.h"
int main()
{
    int msgid = getMsgQueue();
    char buf[1024];
    while(1)
    {
        buf[0] = 0;
        printf("Please Enter# ");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf));
        if(s > 0)
        {
            buf[s-1] = 0;
            sendMsg(msgid,process2_type,buf);
            printf("send done,wait recv...\n");
        }
        recvMsg(msgid,process1_type,buf);
        printf("process1 say %s\n",buf);
    }
    return 0;
}

运行结果如下:
这里写图片描述
需要注意的是,异常终止process1和process2,再次运行时就会报错
这里写图片描述
这时我们可以使用ipcs 和ipcrm命令

ipcs:显示IPC资源
ipcrm:手动删除IPC资源

当我们在命令行上执行ipcs -q 命令时,我们可以看到当前的消息队列的相关信息。使用ipcrm -q [msgid]就可以删除对应id的消息队列。结果如下图:
这里写图片描述
再次运行程序就可以正常运行了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值