Linux内核入门(十一)——进程间通信:消息队列

时间:2024-10-23 09:22:31

Linux内核入门(十一)——进程间通信:消息队列

  • 前言
  • 什么是消息队列
  • 创建一个消息队列
  • 实现进程间通信

前言

上回书说到,共享内存的一些知识,我们再来看一个新东西:消息队列,其实它用起来和共享内存有点像,甚至调用的函数都差不多,参数啥的都能对应上。

其实,共享内存、消息队列、信号量这三个通信方式的IPC通信里的“御三家”,至于管道和信号为啥不算,俺也不知道。

什么是消息队列

消息队列本质上是一个链表,很像之前管道里讲的:管道的本质是队列,就很奇怪,说是消息队列,本质却不是一个队列,而是叫作链式队列,和之前的管道队列是有区别的!

那有了管道还需要这个消息队列干嘛呢,当然是因为消息队列更强大嘛。
如下图所示:为消息队列在内存中分布的示意图
在这里插入图片描述
我们可以看到:这是一个链表的形式,一个结点指向下一个结点,直到最后一个。我们仔细看看每个结点里都有什么:第一个是指针,链表必须要有的东西,略过。第二个叫type,这个type有值为100,为200……什么意思,100是一个代号,表明该结点存放数据的“种类”,type不同的结点是不能看作相同类型的数据的,即使他们都是整型且数据一模一样。有了这个type,我们就可以迅速查到所有类型为100的结点,其他类型的结点不受影响。
往下看:length表明当前节点数据段的长度,data当然就是结点里存放的数据啦。

创建一个消息队列

创建消息队列的函数原型:

int msgget(key_t key, int msgflg);
  • 1

是不是和共享内存的

int shmget(key_t key, size_t size, int shmflg);
  • 1

很像?

其实他们的参数含义也是一样的:
key:可以设key值为IPC_PRIVATE,无亲缘关系进程间通信我们使用函数key_t ftok(const char *pathname, int proj_id);获取一个唯一的key值

msgflg:指明共享内存的权限,就像open函数中的mode参数一样,key值对应得共享内存不存在时需要使用IPC_CREAT作与操作。

消息队列不需要像共享内存那样需要将内存连接到进程地址空间才能使用,所以方便不少。

删除消息队列函数原型:

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • 1

msqid:是msgget()函数返回的消息队列标识符。
cmd:是要采取的操作,它可以取下面的三个值 :

IPC_STAT
IPC_SET
IPC_RMID:删除消息队列
  • 1
  • 2
  • 3

buf:是一个结构指针,删除消息队列时设为NULL。

写入和读取函数:

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
  • 1
  • 2
  • 3

msgsnd是发送函数,msgrcv是接收函数
具体使用方法如下表:转载自博客:链接: linux 消息队列.
在这里插入图片描述
在这里插入图片描述

这里我们需要自己创建一个消息结构体,结构体必须包括“消息类型”和“消息正文”

掌握创建,删除,读写的方法,基本就可以使用这个通信方式
下面我们来使用消息队列实现:两个无亲缘关系进程间通信。

实现进程间通信

发送进程源码:

#include <>
#include <sys/>
#include <>
#include <>

struct msgnode
{
    long type;
    char buffer[124];
    char ID[4];
};


int main(void)
{
    int msgid;
    struct msgnode sendbuf;
    int ret;
    key_t key;

    sendbuf.type = 100;
    key = ftok("./",0);

    msgid = msgget(key,IPC_CREAT | 0777);
    if(msgid<0){
        printf("creat message queue failure.\n");
        return -1;
    }
    printf("creat message queue sucess,id is %d.\n",msgid);

    system("ipcs -q");

    while(1){
        fgets(sendbuf.buffer,124,stdin);
        msgsnd(msgid,(void*)&sendbuf,strlen(sendbuf.buffer),0);
    }

    msgctl(msgid,IPC_RMID,NULL);
    system("ipcs -q");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

接收进程源码:

#include <>
#include <sys/>
#include <>
#include <>

struct msgnode
{
    long type;
    char buffer[124];
    char ID[4];
};


int main(void)
{
    int msgid;
    struct msgnode rcvbuf;
    int ret;
    key_t key;

    rcvbuf.type = 100;
    key = ftok("./",0);

    msgid = msgget(key,IPC_CREAT | 0777);
    if(msgid<0){
        printf("creat message queue failure.\n");
        return -1;
    }
    printf("creat message queue sucess,id is %d.\n",msgid);

    system("ipcs -q");

    while(1){
        memset(rcvbuf.buffer,0,124);
        ret = msgrcv(msgid,(void*)&rcvbuf,128,rcvbuf.type,0);
        printf("message is %s",rcvbuf.buffer);
        printf("length is %d\n",ret);
    }
    
    msgctl(msgid,IPC_RMID,NULL);
    system("ipcs -q");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

消息队列如图管道通信一样,读取数据后,数据在队列中将不复存在。
我们以阻塞方式,不断向消息队列写入数据,另一个进程接收到后,我们将其打印出来,等待再次接收到type类型的数据。