前面提到的管道pipe和fifo是半双工的,在某些场景不能发挥作用;
接下来描述的是消息队列(一种全双工的通信方式);
比如消息队列可以实现两个进程互发消息(不像管道,只能1个进程发消息,1个进程读消息);
注意:消息队列是由内核进行管理的,初级开发者可以先不了解其机制。
初级开发者应该关注:如何把消息加到队列?如何从队列获取消息。
一、消息队列的基本概念
1.特点
消息队列是消息的链接表,存放在内核中。一个消息队列由标识符(即队列ID)来标识。
(1)消息队列是面向记录的,其中的消息具有特定的格式及特定的优先级;
(2)消息队列独立于发送和接收进程。进程终止时,消息队列及其内容并不会被删除;
(3)消息队列可以实现消息的随机查询;消息不一定要 以先进先出的次序读取,也可以按消息的类型读取;(初级开发者一般 采用按消息的类型读取数据)
2.API
头文件:#include<sys/msg.h>
(1)创建或打开消息队列(成功返回队列ID,失败返回-1)
int msgget(key_ t key, int msgflg);
参数:
key:消息队列的标识符
msgflg:创建的标志,例如IPC_CREAT
IPC_CREAT:如果不存在就创建:按位或上一个权限(8进制的数字)
返回值:
成功:返回队列ID
失败:返回-1,并设置erron
注意:以下两种勤情况,msgget将创建1个新的消息队列
(1)如果没有与键值key相对应的消息队列,并且flag中 包含了IPC_CREAT标志位。
(2)key参数为IPC_PRIVATE.
(2)添加消息(成功返回0,失败返回-1)
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgid:消息队列ID
msgp:指向msgbuf 的指针,用来指定发送的消息
msgsz:要发送消息的长度,消息内容的长度
msgflg:创建标记,如果指定IPC_NOWAIT,失败会立即返回
0:阻塞发送
IPC_NOWAIT:非阻塞发送
返回值:
成功:返回0
失败:返回-1,并设置erron
(3)读取消息(成功返回消息的数据长度,失败返回-1)
int msgrcv(int msqid, void *msgp, sizet msgsz, long msgtyp, int msgflg);
msgid:消息队列ID
msgp:指向msgbuf的指针,用来接收消息
msgsz:要接收消息的长度
注意:参数msgsz 指定由msgp 参数指向的结构的成员mtext的最大大小(以字节为单位),msgtyp 也有,3种方式:
msgtyp:接收消息的方式
1. msgtyp = 0:读取队列中的第一条消息(不在乎当前对头元素时什么消息类型,将他当作普通队列来处理)
2. msgtyp > 0:读取队列中类型为msgtyp 的第一条消息。(就是读取对列元素中第一个香蕉)除非在msgflg中指定了MSG_ EXCEPT, 将读取类型不等于msgtyp 绝对值的第一条消息
3. msgtyp< : 0:读取队列中最小类型小于或等于msgtyp 绝对值的第一条消息
msgflg:创建标记,如果指定IPC_ NOWAIT,获取失败会立刻返回
返回值:
成功返回实际读取消息的字节数,,
失败返回-1,并设置erron
注意 :msgflg置为0时,是以默认的方式读数据,如果读不到数据会阻塞。
(4)控制消息队列(成功返回0,失败返回-1)
int msgctl(int msqid, int cmd, struct msqid_ ds *buf);
参数:
msqid:消息队列ID
cmd:控制命令,
例如IPC_ RMID,删除命令 ,
IPC STAT,获取状态
buf:存储消息队列的相关信息的buf
返回值:
成功根据不同的cmd有不同的返回值,
失败返回-1,并设置erron
3.基本开发流程
.针对消息队列,
若A想对B进行通信(假设A从B读信息),
A需要做如下操作:
(1)获取队列
(2)A读B发送过来的消息
B要做的操作:
(1)创建队列
(2)B向A发送消息(即写数据放到队列中)
4.基于API的基本实验
实验要求:创建两个进程(进程A和进程B),两个进程相互发送和接收消息。
以下是进程A的步骤:
(1)创建/打开 消息队列(key=0x1234)
(2)将要发送的消息:
struct DuiLie_data{
long type;
char send_buff[128];
};
struct DuiLie_data STU_sendbuff = {111,"hello\n"};
注意:STU_sendbuff 的type要和msgrcv()的倒数第二个参数一致;
消息队列独立于发送和接收进程。进程终止时,消息队列及其内容并不会被删除;
(3)创建消息队列,返回值为消息队列ID;msgID = msgget(0x1234,IPC_CREAT|0777);
判断返回值msgID;
返回值为-1,则表示队列 创建失败;
否则创建成功;
(5)通过队列发送消息:
send_flag =msgsnd(msgID,&STU_sendbuff,strlen(STU_sendbuff.send_buff),0);
判断返回值send_flag ;
返回值为-1,表示发送失败;
返回值为0,表示发送成功;
(6)通过队列读取消息:msgrcv(msgID,&STU_readbuff,sizeof(STU_readbuff),111,0);
参数1:消息队列的ID
参数2:一个指针,指向自定义的结构体,该结构体有2个成员(成员1:消息类型,成员2:消息内容)
参数3:参数2所指向的结构体的大小。
参数 4:
等于0,读取队列中的第一条消息(不在乎当前对头元素时什么消息类型,将他当作普通队列来处理);
大于0,读取队列中类型为msgtyp 的第一条消息。(就是读取对列元素中第一个消息)除非在msgflg中指定了MSG_ EXCEPT, 将读取类型不等于msgtyp 绝对值的第一条消息。
小于0,读取队列中最小类型小于或等于msgtyp 绝对值的第一条消息
参数5:msgflg置为0时,是以默认的方式读数据,如果读不到数据会阻塞。
------
进程B的步骤与进程 A 的步骤基本一致,不再赘述。
main.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct DuiLie_data{
long type;
char send_buff[128];
};
int main()
{
int msgID;
char send_flag;
// char send_buff[128] = "12345\n";
struct DuiLie_data STU_readbuff;
struct DuiLie_data STU_sendbuff = {111,"hello\n"};
msgID = msgget(0x1223,IPC_CREAT|0777);
if( msgID == 0)
{
printf("creat failed\n");
}
else
{
printf("duilie creat success! msgID = %x\n",msgID);
send_flag = msgsnd(msgID,&STU_sendbuff,strlen(STU_sendbuff.send_buff),0);
if(send_flag == 0)
{
printf("send success\n");
}
else if(send_flag == -1)
{
printf("send failed\n");
}
printf("A will get data from queue\n");
msgrcv(msgID,&STU_readbuff,sizeof(STU_readbuff),111,0);
printf("return from get B:%s\n",STU_readbuff.send_buff);
}
return 0;
}
send.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct DuiLie_data{
long type;
char send_buff[128];
};
int main()
{
int msgID;
char send_flag;
struct DuiLie_data STU1 = {111,"123456789\n"};
//char read_buff[128];
struct DuiLie_data readSTU ;
msgID = msgget(0x1223,IPC_CREAT|0777);
if( msgID == -1)
{
printf("creat failed\n");
}
else
{
printf("duilie creat success! msgID = %x\n",msgID);
printf("B will get data from queue\n");
msgrcv(msgID,&readSTU,sizeof(readSTU.send_buff),111,0);
printf("return from get A:%s\n",readSTU.send_buff);
send_flag = msgsnd(msgID,&STU1,sizeof(STU1),0);
if(send_flag == 0)
{
printf("send success\n");
}
else if(send_flag == -1)
{
printf("send failed\n");
}
}
return 0;
}
运行结果如图所示:
5.补充的知识点
(1)键值的生成及消息队列的移除
系统IPC键值的格式转换函数:ftok;
建立IPC通信(消息队列,信号量,共享内存)时,必须指定1个ID值。通常情况下,该ID通过ftok函数得到;
头文件:#include <sys/types.h>
#include <sys//ipc.h>
函数原型:key_t ftok(const char *fname,int id)
fname :是开发者指定的文件名(已存在的文件名),一般使用当前目录;
如:
ket_t key;
key = ftok(".",1);//这样就是把当前目录设置为fname;
id是子序号,虽然是整型,但只能 使用8bits(1~255)
在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
可以使用 ls-ai查看 当前目录的索引节点;
如图当前 目录的索引节点为1179979
修改前面的实验;
把 msgID = msgget(0x1223,IPC_CREAT|0777);//手动生成键值
改为用ftok生成键值
key_t key;
key = ftok(".",'z');
msgID = msgget(key,IPC_CREAT|0777);
printf("key :%x\n",key);
运行结果如下:
自动生成的键值为7a01014b
(2)msgctl移除队列
msgctl(msgID,IPC_RMID,NULL);//实现移除队列
两个文件都添加这句话,实现移除队列