文章目录
- 1. system-v IPC简介
- 2. 函数ftok()函数介绍
- 3. 消息队列MSG
- 4. 消息队列相关接口函数
- 5. 消息队列代码示例
1. system-v IPC简介
消息队列、共享内存和信号量统称为system-V IPC,V是罗马数字5,是UNIX的AT&T分支中的一个版本,一般习惯称之为IPC对象。这些对象的操作接口比较相似,在系统中它们都使用一种名为key的值也唯一标识,而且它们是“可持续”资源–它们被创建以后,不会因为进程的退出而消息,而会持续存在,除非调用特殊的函数或者命令来删除。
进程每次“打开”一个IPC对象,就会获得一个表征这个对象的ID,进而再使用这个ID来操作这个对象。IPC对象的key是唯一的,但是ID是可变的。key类似文件的路径名,ID类似文件的描述符
2. 函数ftok()函数介绍
IPC对象的键值key是怎么产生的呢?理论上它就是一个整数,一般用函数ftok()函数来产生,函数ftok()的接口规范如下:
函数项描述 | 函数项说明 | |
函数功能 | 获取一个当前未用的IPC的key | |
头文件 | #include <sys/types.h> | #include <sys/ipc.h> |
原型 | key_t ftok(const char *pathname, int proj_id); | |
参数1 | pathname | 一个合法的路径,比如/目录或者./当前目录 |
参数2 | proj_id | 工程ID,非0,仅仅使用低8位,通常传入一个unsigned char |
返回值 | 成功:返回合法未用的键值 | 失败:返回-1 |
这个函数需要注意以下几点:
- 如果两个参数相同,那么产生的key值也相同
- 第一个参数一般取进程所在的目录,因为一个项目中需要通信的几个进程通常会出现在一个工程目录中
- 如果同一个目录中的进程需要使用超过一个IPC对象,可以通过第2个参数来标识
- 系统中只有一套key标识,也就是说,不同类型的IPC对象也不能重复
可以使用如下命令来查看或者删除系统中的IPC对象
- 查看消息队列:ipcs -q
- 查看共享内存:ipcs -m
- 查看信号量:ipcs -s
- 查看所有IPC对象:ipcs -a
- 删除指定的消息队列:ipcs -q MSG_ID 或者 ipcrm -Q msg_key
- 删除执行的共享内存:ipcs -m SHM_ID 或者 ipcrm -M shm_key
- 删除指定的信号量:ipcs -s SEM_ID 或者 ipcrm -S sem_key
3. 消息队列MSG
我们熟知的PIPE或者FIFO管道的通信机制有一个缺点:读端无法读取一个指定类型的数据,只能将管道中的数据不加筛选的读出来,因为这些管道中的数据是没有标识的,所以读端只能按照顺序依次读取,因此多进程之间不同数据类型数据之间的传递,除非使用多个管道,否则无法使用一条管道完成该需求。
那么怎么解决这个问题呢?消息队列是种带有数据标识的特殊管道,因为每一段写入的数据都带有数据标识,所以对于读端来讲,只需要筛选出自己关注的带某些带标识的数据就可以了
消息队列的使用方法如下
发送者:
- 获取消息队列的ID
- 将数据放入一个附带有标识的特殊结构体中,发送给消息队列
接收者:
- 获取消息队列的ID
- 将关注的带消息标识的数据读出
当发送者和接收者不再使用消息队列时,及时删除消息队列以释放系统资源
4. 消息队列相关接口函数
获取消息队列ID的函数原型:
int msgget(key_t key, int msgflg);
发送函数原型:
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);
使用发送和接收函数时需要注意以下几点:
- 发送消息时,消息必须被组织成以下形式:
struct msgbuf{
long msg_type; //消息的标识,大于0的整数
char msg_text[0]; //消息的正文,具体的数据
};
//也就是说,发送出去的消息必须是一个long类型数据打头,作为消息的标识,正文部分数据没有要求
- 消息的标识可以是任意长度的整数,但是不能是0L
- 参数msgsz是消息正文部分的大小,不包含消息标识
5. 消息队列代码示例
示例展示了一个进程Jack如何使用消息队列给另一个进程Rose发送消息的过程,以及如何使用msgctl()函数删除不再使用的消息队列
Jack向消息队列中发消息
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <signal.h>
#define MSG_SIZE 64 //单个消息的最大字节数
#define PROJ_PATH "." //使用当前路径来产生消息队列的Key值
#define PROJ_ID 1
#define J2R 1L //Jack发送给Rose的消息标识
#define R2J 2L //Rose发送给Jack的消息标识
struct T_msgbuf{ //带标识的消息结构体
long msg_type;
char msg_text[MSG_SIZE];
};
int main(int argc, char**argv){
key_t key = ftok(PROJ_PATH, PROJ_ID); //获取Key
int msgid = msgget(key, IPC_CREAT | 0666); //获取消息队列ID
struct T_msgbuf message1;
struct T_msgbuf message2;
bzero(&message1, sizeof(message1));
bzero(&message2, sizeof(message2));
message1.msg_type = J2R; //指定消息标识
strncpy(message1.msg_text, "I love you, Rose!!!\n", MSG_SIZE);
message2.msg_type = 8L; //指定消息标识
strncpy(message2.msg_text, "Other message!!!\n", MSG_SIZE);
int i;
for(i = 0; i < 10; i++){
int res1 = msgsnd(msgid, &message1, strlen(message1.msg_text), 0);
int res2 = msgsnd(msgid, &message2, strlen(message2.msg_text), 0);
if((res1 != 0) || (res2 != 0)){
perror("msgsend() error");
exit(1);
}
}
return 0;
}
Rose从消息队列中读消息
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <signal.h>
#define MSG_SIZE 64 //单个消息的最大字节数
#define PROJ_PATH "." //使用当前路径来产生消息队列的Key值
#define PROJ_ID 1
#define J2R 1L //Jack发送给Rose的消息标识
#define R2J 2L //Rose发送给Jack的消息标识
struct T_MsgBuf{ //带标识的消息结构体
long msg_type;
char msg_text[MSG_SIZE];
};
int main(int argc, char**argv){
key_t key = ftok(PROJ_PATH, PROJ_ID);
int mgsid = msgget(key, IPC_CREAT | 0666); //获取消息队列的ID
struct T_MsgBuf msgbuf;
bzero(&msgbuf, sizeof(msgbuf));
int i;
for(i = 0; i < 10; i++){
if(-1 == msgrcv(mgsid, &msgbuf, MSG_SIZE, J2R, 0)){
perror("msgrcv()");
exit(1);
}
printf("from msg: %s\n", msgbuf.msg_text);
}
msgctl(mgsid, IPC_RMID, NULL); //删除消息队列
return 0;
}
消息队列使用简单,但它和管道一样,都需要“代理人”的进程通信机制:内核充当了这个代理人,内核为使用者发呢配内存、边界检查、设置阻塞,以及各种权限控制,使得我们用起来非常省心,省力。但任何事都是有代价的:代理人机制使得它们的效率不是很高,因为两个进程的消息传输不是直接的,而是要经过内核转发,因此消息队列不适合用来传输海量数据