进程间的通信——消息队列篇

进程间的通信——共享内存https://blog.csdn.net/q496958148/article/details/79953349
进程间的通信——信号量https://blog.csdn.net/q496958148/article/details/79977093
进程间的通信——管 道https://blog.csdn.net/q496958148/article/etails/79948367

什么是消息队列?

消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的。也就是说进程的退出,如果不自主去释放资源,消息队列是会悄无声息的存在的。消息队列与管道不同的是,消息队列是基于消息的, 而管道是基于字节流的,且消息队列的读取不一定是先入先出。消息队列与命名管道有一 样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限

进程通信我们有了管道,为什么还要引入消息队列呢?

因为根据管道的特性,我们知道其在一定程度上存在或多或少的局限性,首先匿名管道以及命名管道是随进程的,进程的退出,意味着管道生命周期的结束;其次,管道传送数据时以无格式字节流的形式传送,这有时会给程序的开发带来不便;再者,担当数据传送媒介的管道,其缓冲区的大小也有较大的限制。所以作为IPC方式下的另一种应用于进程间通信的消息队列方式便应运而生了。
IPC对象数据结构:

//内核为每个IPC对象维护了一个数据结构
struct ipc_perm
{
    __kernel_key_t  key;//key值是消息队列的一个验证码(我理解为暗号,首先一个进程要向消息队列放入一个东西,得先生成一个key值,这个key值是函数计算得来的,输入值确定会固定得到一个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;
    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 */
};

和消息队列有关的函数:

  • 生成key值
key_t ftok(const char *pathname, int proj_id);
  • 用来创建或者访问一个消息队列
int msgget(key_t key, int msgflg);
//key 上面解释过了,相当于一个暗号由ftok函数创建
//msgflg: IPC_CREAT:创建新的消息队列。 IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。 IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞。
//返回值: 调用成功返回队列标识符,否则返回-1.
  • 消息队列的控制函数
 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//msqid:有msgget返回的标识码
//cmd:将要采取的动作,IPC_STAT把msqid_ds结构的数据设置为消息队列的当前关联值, IPC_SET在权限允许的情况下,吧消息队列的当前关联值设置为msqid_ds数据结构中给出的值,IPC_RMID删除消息队列
  • 插入一条消息到消息队列
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//msqid:由msgget函数返回的消息队列标识码
//msgp:指向准备发送消息的指针
//msgsz:是msgp的长度
//msgflg:控制这当前消息队列是否满或者达到系统上限
//返回值成功为0;失败为-1
  • 从消息队列接收一个消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//msqid:由msgget函数返回的消息队列标识码
//msgp:指向准备接收消息的指针
//msgsz:是msgp的长度
//msgtyp:它可以实现接收优先级的简单形式
//msgflg:控制这当前消息队列是否满或者达到系统上限
//返回值成功为实际接收到接收缓存区的字符个数;失败为-1

说明:
msgtyp等于0 则返回队列的最早的一个消息。
msgtyp大于0,则返回其类型为mtype的第一个消息。
msgtyp小于0,则返回其类型小于或等于mtype参数的绝对值的最小的一个消息。
msgflg:这个参数依然是是控制函数行为的标志,取值可以是:0,表示忽略;IPC_NOWAIT,如果消息队列为空,则一个ENOMSG,并将控制权交回调用函数的进程。如果不指定这个参数,那么进程将被阻塞直到函数可以从队列中得到符合条件的消息为止。如果一个client 正在等待消息的时候队列被删除,EIDRM 就会被返回。如果进程在阻塞等待过程中收到了系统的中断信号,EINTR 就会被返回*。MSG*_NOERROR,如果函数取得的消息长度大于msgsz,将只返回msgsz 长度的信息,剩下的部分被丢弃了。如果不指定这个参数,E2BIG 将被返回,而消息则留在队列中不被取出。当消息从队列内取出后,相应的消息就从队列中删除了。

实例:
功能是实现俩个窗口之间的相互通信:
最终的成果图
这里写图片描述

具体实现如下:

这里写图片描述

这里写图片描述

msgges.h:

#pragma once

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

//这俩个宏定义是生成key值
#define PATH "."
#define NUM 0x6666

//
#define SERVER_TYPE 1
#define CLIENT_TYPE 2

//消息队列中消息的结构体
struct msgbuf{
    long mtype;//上面进行宏定义的SERVER_TYPE与CLINENT_TYPE就是这个
    char mtext[1024];
};

int createMsgQueue();

int getMsgQueut();

int destroyMsgQueut(int msgid);

int sendMsg(int msgid,int who,char *msg);

int recvMsg(int msgid,int recvType,char out[]);

msgges.c:

#include "msgges.h"

//创建一个消息队列,如果已经有了,返回错误
int createMsgQueue()
{
    key_t key = ftok(PATH,NUM);//生成key值(暗号)
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    int msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);
    if(msgid < 0)
    {
        perror("msgget");
        return -1;
    }
    return msgid;
}

//创建一个消息队列,如果已经有消息队列了,返回已经有的消息队列msgid
int getMsgQueue()
{
    key_t key = ftok(PATH,NUM);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    int msgid = msgget(key, IPC_CREAT);
    if(msgid < 0)
    {
        perror("msgget");
        return -1;
    }
    return msgid;
}

//删除消息队列
int destroyMsgQueue(int msgid)
{
    printf("111\n");
    if(msgctl(msgid,IPC_RMID,NULL) < 0)
    {
        perror("msgctl");
        return -1;
    }
    return 0;
}

//往消息队列里发数据
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;
}

//从消息队列里取数据
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;
}

server.c:

#include "msgges.h"

int main()
{
    int msgid = createMsgQueue();
    char buf[1024];
    while(1)
    {
        buf[0] = 0;
        recvMsg(msgid,CLIENT_TYPE,buf);
        printf("小明:%s\n",buf);

        printf("你:");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf));
        if(s > 0)
        {
            buf[s-1] = 0;
            sendMsg(msgid,SERVER_TYPE,buf);
        }
    }
    destroyMsgQueue(msgid);
    return 0;
}

client.c:

#include "msgges.h"

int main()
{
    int msgid = getMsgQueue();
    char buf[1024];

    while(1)
    {
        buf[0] = 0;
        printf("你:");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf));
        if(s > 0)
        {
            buf[s-1] = 0;
            sendMsg(msgid,CLIENT_TYPE,buf);
        }

        recvMsg(msgid,SERVER_TYPE,buf);
        printf("张三:%s\n",buf);
    }
    return 0;
}

Makefile:

.PHONY:all
all:client server


client:client.c msgges.c
    gcc $^ -o $@;
server:server.c msgges.c
    gcc $^ -o $@;


.PHONY:clean
clean:
    rm -f client server

最后如果你出现了下面的错误:
这里写图片描述
解决办法如下:
这里写图片描述
造成这个原因是,你只要编译通过,并且运行了 你再用ctrl+c 终止掉进程,进程不会走这个函数的destroyMsgQueue(msgid); 一旦你发出ctrl+c信号,操作系统就会马上杀死该进程。这样就会造成IPC没有及时被回收。想要解决这个问题我们可以将ctrl+c 的执行函数改为destroyMsgQueue(msgid); 并且执行完后退出即可。

阅读更多
上一篇进程间的通信——管道篇
下一篇进程间的通信——共享内存
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭