IPC进程间通信——消息队列

消息队列:提供了一种从一个进程向另外一个进程发送数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立的接收含有不同的类型的数据结构,我们可以通过发送信息来避免命名管道的同步和阻塞问题,但是消息队列和命名管道一样,每个数据块都有一个最大的长度限制。

消息队列与管道的区别:

  1. 消息队列基于消息,管道基于字节流
  2. 消息队列的读取不一定是先入先出的
  3. 消息队列的生命周期是伴随内核,如果你没有删除他,那么关机前他一直存在
一条消息的最大值:msgmax
一条消息队列的总消息大小:msgmnb
系统能创建多少个消息队列:msgni

内核为每个IPC对象都维护一个数据结构

struct ipc_perm{//消息队列,共享内存,信号量共有的

    key_t          __key;      //唯一标识符
    uid_t          uid;         /* Effective UID of owner */
    gid_t          gid;         /* Effective GID of owner */
    uid_t          cuid;        /* Effective UID of creator */
    gid_t          cgid;        /* Effective GID of creator */
    unsigned short mode;        /* Permissions */
    unsigned short __seq;       /* Sequence number */
};
struct msqid_ds {//私有
    struct ipc_perm msg_perm;     /* Ownership and permissions */
    time_t          msg_stime;    /* Time of last msgsnd(2) */
    time_t          msg_rtime;    /* Time of last msgrcv(2) */
    time_t          msg_ctime;    /* Time of last change */
    unsigned long   __msg_cbytes; /* Current number of bytes in queue (non-standard) */
    msgqnum_t       msg_qnum;     /* Current number of messages in queue */
    msglen_t        msg_qbytes;   /* Maximum number of bytes allowed in queue */
    pid_t           msg_lspid;    /* PID of last msgsnd(2) */
    pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
};
消息队列基本命令

查看消息队列:ipcs -q
广泛

我们可以看到这里有一条消息队列

删除消息队列:ipcrm -q +key
这里写图片d描述
当我们执行删除消息队列指令之后,再次查看消息队列,刚才看到的那一条消息队列已经没有了。

基本函数

创建消息队列/打开消息队列
//创建消息队列或打开消息队列
int msgget(key_t key//相当于文件名
            int msgflg)
            //消息队列的权限  创建的话写IPC_CREAT,权限|后面
            //即IPC_CREAT|0644
            //若打开消息队列写曾经创建时候的权限或者0(内核自动查找)
  返回值:id//相当于文件描述符子进程也可以用

参数:

  • key:

    1. 1234写死
    2. IPC_PRIVATE
    3. int ftok(char *path,int pro_id) //path是路径,必须存在,id的低八位必须非0。
  • msgflg
    1.消息队列的权限,创建的话写IPC_CREAT|权限,如IPC_CREAT|0644

    1. 若打开消息队列写曾经创建时候的权限或者0(内核自动查找)
  • 返回值:id

代码实现:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>

int main()
{
    int id=msgget(ftok(".",'a'),IPC_CREAT|0644);//. 路径绝对存在
    if(id==-1)
        perror("magget"),exit(1);
    printf("msgget ok\n");
}
//我们在这里第一个参数用的是ftok函数

这里写图片描述
我们执行该程序,在使用查看消息的指令,可以看到我们创建了一个消息队列。

向消息队列中发送数据
int msgsnd(int msqid, //打开的消息队列
        const void *msgp, //指向准备发送的消息地址
        size_t msgsz,//消息大小,不包含channel字段
        int msgflg);//一般填0,IPC_NOWAIT,如果消息队列满
        //立马返回,error=EAGAIN

msgflg:用来致命核心程序在队列没有数据的情况的下所采取的行动.如果msgflg和常数IPC_NOWAIT合用,在msgsnd()执行时若是消息队列已满,则
msgsnd()不会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码ENOMSG。当msgflg为0时,
msgrev()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理方式

 msgp:指向消息缓冲区的指针,此地址用来暂时存储发送接收消息,是一个用户可定义的通用结构,结构如下     
//结构体自己定义        
 struct msgbuf {
        long mtype;      //消息类型即通道号必须>0
        char mtext[1];   //消息的数据占多大都没问题但是不能超过系统上限
           };

代码实现:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>

struct msgbuf
{
    long channel;
    char text[1024];
};
int main(int argc,char *argv[])
{
    struct msgbuf mb;
    if(argc!=3)
    {
        printf("usage:%s channel msg\n",argv[0]);
        exit(0);
    }
    int id=msgget(ftok(".",'a'),0);
    if(id==-1)
        perror("msgget"),exit(1);

    mb.channel = atol(argv[1]);//转换为整数
    strcpy(mb.text,argv[2]);

   if(msgsnd(id,&mb,strlen(mb.text),0)==-1)
        perror("msgsnd"),exit(0);
}

运行结果+分析:
这里写图片广泛描述

从消息队列中读取数据
//从消息队列中读取数据
ssize_t msgrcv(int msqid,//打开的消息队列
            void *msgp,
            size_t msgsz,//放消息的大小,不包含类型大小,是真正放数据的大小
            long msgtyp,
            //>0:从指定的通道收取数据
            //=0:返回消息队列中的第一条
            //<0:读取<=|type|,并且读最小的类型
            int msgflg);//一般写0,IPC_NOWAIT如果消息队列为空,不等待,立马返回

代码实现:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>

struct msgbuf
{
    long channel;
    char text[1024];
};
int main(int argc,char *argv[])
{
    struct msgbuf mb={};//清空
    long type;
    if(argc!=2)
    {
        printf("usage:%s channel\n",argv[0]);
        exit(0);
    }
    int id=msgget(ftok(".",'a'),0);
    if(id==-1)
        perror("msgget"),exit(1);
    type=atol(argv[1]);
    if(msgrcv(id,&mb,1024,type,0)==-1)
        perror("msgrcv"),exit(1);
    printf("%s\n",mb.text);
}

运行需要打开两个终端,
一个终端下,向消息队列中发送数据
这里写图片描述

另外一个终端收取消息
这里写图片是描述

收取消息的终端如果运行:./rcv 0:那么就按顺序读取
这里写图片描述

收取消息的终端如果运行:./rcv + 负数:读取小于等于他的绝对值且最小的消息
这里写图片描述

删除消息队列,设置消息队列属性
int msgctl(int msqid//创建的消息队列的ID
,int cmd,//IPC_RMID(删除的话写它)
        struct msqid_ds *buf);//删除的话不用,所以填0

参数:msgctl系统调用对msgqid标识的消息队列执行cmd操作系统定义了3种cmd操作:IPC_STAT,IPC_SET,IPC_RMID

IPC_STAT:该命令用来获取消息队列对应的msqid_ds数据结构,并将其保存到buf指定的地址空间.

IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf中.

IPC_RMID:从内核中删除msqid标识的消息队列.

代码实现:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>

int main()
{
    int id=msgget(ftok(".",'a'),0);//获取消息队列
    if(id==-1)
        perror("msgget"),exit(1);
    msgctl(id,IPC_RMID,0);
}

运行结果:
这里写图片描述
我们可以看到,先查看消息队列 ,我们可以看到有一条消息队列,我们执行该程序之后,再次查看消息队列,该消息队列已经删除了

我们通常用该函数用来删除消息队列

双向通信——客户端/服务器端

我们可以使用消息队列来实现双向通信(客户端服务器端发送接收消息):

以下是代码实现:

//客户端
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<unistd.h>
#include<string.h>
struct mymsgbuf
{
    long type;
    char buf[1024];
};
int main()
{
   int id= msgget(1234,0);
   if(id==-1)
    perror("msgget"),exit(1);
    struct mymsgbuf mb,rcvbuf;//定义mb缓存
    int pid =getpid();
    while(1)
    {
        memset(&mb,0x00,sizeof(mb));//清空缓存
        *(int*)mb.buf=pid; 
        mb.type=1;
        //read from keyboard
        fgets(mb.buf+sizeof(int),sizeof(mb.buf)-sizeof(int),stdin);//从键盘读取一行
        //send to servrer
        msgsnd(id,&mb,strlen(mb.buf+sizeof(int))+sizeof(int),0);
        //get msg from server
        memset(&rcvbuf,0x00,sizeof(rcvbuf));
        msgrcv(id,&rcvbuf,1024,pid,0);//从服务器中接收数据
        printf("rcv=%s\n",rcvbuf.buf+sizeof(int));//显示屏幕
    }
}


//服务器端
//在那个终端发送消息就在哪个终端接收消息
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<unistd.h>
#include<string.h>

struct mymsgbuf
{
      long type;
      char buf[1024];
};
int main(void)
{
    int id = msgget(1234, IPC_CREAT|0644);
    if(id == -1) perror("msgget"),exit(1);
    struct mymsgbuf mb;//缓存区
    while(1)
    {
        memset(&mb, 0x00, sizeof(mb));
        msgrcv(id,&mb,1024,1,0);
        mb.type = *(int*)mb.buf;
        printf("%s\n",mb.buf+sizeof(int));
        msgsnd(id, &mb,strlen(mb.buf+sizeof(int))+sizeof(int),0);
    }
}

下面是代码的运行(我们要打开两个终端哦!):
客户端:我们在客户端发送消息
这里写图片描述

服务器端:在服务器端显示消息
这里写图片描述

消息 队列特点:
1.消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。

2.同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。

3.接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

4.可以实现双向通信。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值