首先上篇文章我们说到了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