Linux进程间通信-消息队列

xiaoxiao2021-02-28  148

首先上篇文章我们说到了Linux下进行进程间通信的一种方法或机制匿名管道和命名管道,那么这里要说的是另外一种与之不同的通信方法,即消息队列,两者之间有相同也有不同的地方,具体的下面就一一介绍。


一、什么是消息队列?

首先它也是一种进行进程间通信的方式,通过一个进程向另外一个进程发送数据块的方式,每个数据块都被认为是有一个类型的,而接受者进程接收的数据块可以有不同的类型。

二、消息队列结构体

cat /usr/include/linux/msg.h

通过这个命令就可以查询到消息队列的数据结构,如下图:

该结构体第一条是一个IPC结构体,是所有IPC机制(消息队列,信号量,共享内存)所共有的,可以看出消息队列的底层是通过单链表来实现的。

三、消息队列相关的函数

在了解了底层的实现机制和它的通信原理后,所要掌握的就是相关的函数的使用了,所以得先搞清楚都有些什么函数,具体来怎么实现:

1、创建新的消息队列或获取已存在的消息队列

int msgget(key_t key,int msgflg);

这里的key是一个键,用来标识某个特定的消息队列。 而msgflg呢,包含两个标识符IPC_CREAT和IPC_EXCL ,IPC_CREAT的意思为,如果IPC不存在,则创建一个IPC资源。而IPC_EXCL则是只有在共享内存不存在的时候,新的共享内存才建立,否则产生错误。一般情况下两者必须同时存在,才能保证所得对象是新建的。

2、消息的读/写

读取消息

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

放入消息

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

msqid就是消息队列的标识码,msgp是指向消息缓冲区的指针,这个位置是用来暂时存储发送和接收的消息,是一个用户可自定义的结构:

struct msgstru { long mtype;//必须大于0 char mtext[用户指定的大小] }

msgsz是消息的大小,msgtyp是读取消息的形态,如果为0.则会读取队列里所有的消息。

msgflg则是表明在队列里没有数据的情况下程序所采取的行动,如果msgflg和常数IPC_NOWAIT合用,则在执行msgsnd()执行时若消息队列已满,则msgsnd()将不会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列为空时,直接返回-1,当msgflg为0时,则当队列呈满或呈空时,采取阻塞等待的处理方式。

3、消息队列的属性

int msgctl(int msgqid,int cmd,struct msqid_ds *buf);

cmd是所要执行的操作,一共定义了三种: IPC_STAT:获取消息队列中对应的msqid_ds结构,将其保存到buf中。 IPC_SET:所要设置的属性,然后保存在buf中。 IPC_RMID:从内核中删除msqid标识的消息队列。

4、ftok函数

key_t ftok(const char *pathname,int proj_id);

通过ftok函数可以将一个已存在路径和一个整数标识转换为一个key_t的值,就是用来标识特定消息的。

四、代码测试,熟悉接口。

接收消息端:msg_receive.c

#include<unistd.h> #include<stdlib.h> #include<stdio.h> #include<string.h> #include<errno.h> #include<sys/msg.h> #define FILEPATH "." #define ID 0 struct msg_info { long int msg_type; char mtext[BUFSIZ]; }; int main() { int msgid = -1; struct msg_info data; long int msgtype = 0; key_t _key = ftok(FILEPATH,ID);//创建键值 if(_key < 0) { printf("Get key id error!\n"); return 1; } msgid = msgget(_key,0666 | IPC_CREAT);//获取信号队列ID if(msgid == -1) { printf("Msgrcv get key errno!\n"); return 1; } while(1) { if(msgrcv(msgid,(void*)&data,BUFSIZ,msgtype,0) == -1) { printf("Msgrcv get key errno!\n"); return 1; } printf("You wrote:%s\n",data.mtext); if(strncmp(data.mtext,"end",3) == 0) break; } if(msgctl(msgid,IPC_RMID,0) == -1) { printf("Msgctl errno!\n"); return 1; } return 0; }

发送消息端:msg_send.c

#include<unistd.h> #include<stdlib.h> #include<stdio.h> #include<string.h> #include<sys/msg.h> #include<errno.h> #define FILEPATH "."将路径定义为当前目录 #define ID 0 #define MAX_MTEXT 1024 struct msg_info { long int msg_type; char mtext[MAX_MTEXT]; }; int main() { struct msg_info data; char buffer[BUFSIZ]; int msgid = -1; key_t _key = ftok(FILEPATH,ID); if(_key < 0) { printf("Get key errno!\n"); return 1; } msgid = msgget(_key,0666 | IPC_CREAT); if(msgid == -1) { printf("Msgget error!\n"); return 1; } while(1) { printf("Enter some message!\n"); fflush(stdout); fgets(buffer,BUFSIZ,stdin); data.msg_type = 1; strcpy(data.mtext,buffer); if(msgsnd(msgid,(void*)&data,MAX_MTEXT,0) == -1) { printf("Msgsnd failed!\n"); return 1; } if(strncmp(buffer,"end",3) == 0) break; sleep(1); } return 0; }

程序的结果如下图:

这样就实现了两个进程间的通信。

五、和管道之间的异同

相同:每个消息它也是有最大长度的限制的,所以两者在字节限制这块是相同的,并且都可以实现两个不相干进程间的信息传递,在管道中我们也通过小例子来测出了它的大小,下面就看下系统中各自的分配的大小: 可以发现这里的65536和上节管道里测出的相同(没有问题吧!^_^)

不同:消息队列是基于消息的,所以它的读取不一定是先入先出,是带有选择性的。而管道是基于字节流的,这样消息队列就有效避免了命名管道的同步和阻塞问题。


最后给出两个程序的Makefile:

.PHONY:all all:msg_send msg_receive msg_send:msg_send.c gcc -o $@ $^ msg_receive:msg_receive.c gcc -o $@ $^ .PHONY:clean clean: rm -f msg_send msg_receive
转载请注明原文地址: https://www.6miu.com/read-21669.html

最新回复(0)