0. IPC对象
除了最原始的进程间通信方式信号、无名管道和有名管道外,还有三种进程间通信方式,这三种方式称之为IPC对象。
IPC对象分类:消息队列、共享内存、信号灯集
IPC对象也是在内核空间开辟区域,每一种IPC对象创建好之后都会将其设置为全局,并且会给其分配一个编号,只要找到唯一的这个编号就可以进行通信,所以不相关的进程可以通过IPC对象通信。
IPC对象创建好之后,会在当前系统中可见,只要不删除或者不关闭系统,就会一直存在。
查看已创建的IPC对象:
ipcs 查看当前系统中所有创建的IPC对象
ipcs -q 查看创建的消息队列
ipcs -m 查看创建的共享内存
ipcs -s 查看创建的信号量
ipcrm 删除IPC对象
例如:ipcs -q msqid 删除标号为msqid的消息队列
1. 消息队列概述
消息队列是消息的链表,存放在内存中,由内核维护。
消息队列的特点:
- 消息队列中的消息是有类型的。
- 消息队列中的消息是有格式的。
- 消息队列可以实现消息的随机查询。消息不一定要以先进先出的次序读取,编程时可以按消息的类型读取。
- 消息队列允许一个或多个进程向它写入或者读取消息。
- 与无名管道、命名管道一样,从消息队列中读取消息,消息队列中对应的数据都会被删除。
- 每个消息队列都有消息队列标识符,消息队列的标识符在整个系统中是唯一的。
- 只有内核重启或人工删除消息队列时,该消息队列才会被删除。若不人工删除消息队列,消息队列会一直存在于系统中。
在Ubuntu12.04中消息队列限制如下:(了解即可)
- 每个消息内容最多为8K字节
- 每个消息队列容量最多为16K字节
- 系统中消息队列个数最多为1609个
- 系统中消息个数最多为16384个
2. 消息队列相关函数
2.1 生成key值:ftok函数
System V提供的IPC通信机制需要一个key值,通过key值就可以在系统内获得一个唯一的消息队列标识符。
key值可以是人为指定的,也可以通过ftok函数获得。
如果多个进程向通过IPC对象通信,则必须找到唯一的标识,而唯一的标识是由key决定的,所以只要key知道,则就可以实现多个进程通信。
ftok函数:
#include<sys/types.h>
#include<sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:通过文件名和目标值共同创造一个键值并返回值
参数:
pathname:任意一个文件名(文件名或目录名)
proj_id:目标值,范围一般是0~127(低八位)
返回值:
成功:键值
失败:-1
如果使用ftok函数获取键值,得到的键值是由ftok的第一个参数对应文件
的信息和第二个参数一起决定的。
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
int main()
{
//使用ftok函数获取键值
//只要保证ftok的第一个参数对应的文件和第二个参数值相同,则不管
//程序运行多少遍或者多少个进程
//键值一定是唯一的
key_t mykey;
if ((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
printf("key = %#x\n", mykey);
return 0;
}
执行截图:
2.2 创建消息队列:msgget函数
msgget函数:
#include<sys/msg.h>
int msgget(key_t key, int msgflg);
功能:
创建一个消息队列,得到消息队列的id
参数:
key:键值,唯一的键值确定唯一的消息队列
方法1:任意指定一个数
方法2:使用ftok函数获取键值(推荐使用)
msgflg:消息队列的访问权限,一般设置为
IPC_CREAT | IPC_EXCL |0777
IPC_CREAT | 0777
返回值:
成功:消息队列的id
失败:-1
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<unistd.h>
int main()
{
key_t mykey;
if ((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
printf("mykey = %#x\n", mykey);
int msqid;
if ((msqid = msgget(mykey, IPC_CREAT | 0666)) == -1)
{
perror("fail to msgget");
exit(1);
}
printf("msqid = %d\n", msqid);
return 0;
}
执行截图:
2.3 发送消息:msgsnd函数
msgsnd函数:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向指定的消息队列发送数据(写操作)
参数:
msqid:消息队列的id
msgp:要写入的数据,需要自己定义结构体
struct struct msg{
long mtype; //消息 的编号,必须大于0
char mtext[128]; //消息正文,可以定义多个成员
}MSG;
msgsz:消息正文的大小,不包括消息的编号长度
msgflg:标志位
0 阻塞
IPC_NOWAIT 非阻塞
返回值:
成功:0
失败:-1
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<unistd.h>
#define N 128
typedef struct {
long msg_type;//必须要有,且必须是long类型
char msg_text[N];//可以有多个,自己定义
}MSG;
#define MSGTEXT_SIZE (sizeof(MSG) - sizeof(long))
int main()
{
key_t key;
if ((key = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
int msgid;
if ((msgid = msgget(key, IPC_CREAT | 0777)) == -1)
{
perror("fail to msgget");
exit(1);
}
MSG msg1 = { 1, "hello world" };
MSG msg2 = { 2, "hello beijing" };
MSG msg3 = { 3, "nihao zhangsan" };
MSG msg4 = { 4, "lisi hello" };
if (msgsnd(msgid, &msg1, MSGTEXT_SIZE, 0) == -1)
{
perror("fail to msgsnd");
exit(1);
}
if (msgsnd(msgid, &msg2, MSGTEXT_SIZE, 0) == -1)
{
perror("fail to msgsnd");
exit(1);
}
if (msgsnd(msgid, &msg3, MSGTEXT_SIZE, 0) == -1)
{
perror("fail to msgsnd");
exit(1);
}
if (msgsnd(msgid, &msg4, MSGTEXT_SIZE, 0) == -1)
{
perror("fail to msgsnd");
exit(1);
}
return 0;
}
执行截图:
2.4 接收消息:msgrcv函数
msgrcv函数:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能:从消息队列中接收数据(读操作),接收的数据会从消息队列中删除
参数:
msqid:消息队列id
msgp:保存接收到的数据的结构体
struct struct_name{
long mytpe; //消息的编号,必须大于0
char metxt[128]; //消息正文,可以定义多个成员
}
msgsz:消息正文的大小
msgtype:设置要接收哪个消息
0 按照写入消息队列的顺序一次读取
>0 只读去消息队列中消息编号为当前参数的第一个消息
<0 只读取消息队列中小于等于当前参数的绝对值中内最小的第一个消息
msgflg:标志位
0 阻塞
IPC_NOWAIT 非阻塞
返回值:
成功:接收到的消息正文的长度
失败:-1
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define N 128
typedef struct {
long msg_type;
char msg_text[N];
}MSG;
#define MSGTEXT_SIZE (sizeof(MSG) - sizeof(long))
int main()
{
key_t key;
if ((key = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
int msgid;
if ((msgid = msgget(key, IPC_CREAT | 0777)) == -1)
{
perror("fail to msgget");
exit(1);
}
//通过msgrcv函数接收消息队列中的信息(读操作)
//注意:如果没有第四个参数指定的消息时,,msgrcv函数会阻塞等待
MSG msg;
//如果第四个参数为>0,则获取当前值的消息类型的数据
//if (msgrcv(msgid, &msg, MSGTEXT_SIZE, 2, 0) == -1)
// 如果第四个参数<0,则获取当前值的绝对值内消息类型最小的数据
//if (msgrcv(msgid, &msg, MSGTEXT_SIZE, -3, 0) == -1)
//如果第四个参数为0,则按照先进先出的方式读取数据
if (msgrcv(msgid, &msg, MSGTEXT_SIZE, 0, 0) == -1)
{
perror("fail to msgrcv");
exit(1);
}
printf("recv_msg = %s\n", msg.msg_text);
return 0;
}
执行截图:
2.5 消息队列的控制:msgctl函数
msgctl函数:
#include<sys/mg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:
对消息队列进行各种控制,如修改消息队列的属性,或删除消息队列
参数:
msqid:消息队列的标识符
cmd:函数功能的控制
IPC_RMID:删除由msqid指示的消息队列,将它从系统种删除并破坏相关数据结构。
IPC_STAT:将msqid相关的数据结构中各个元素的当前值存入到由buf指向的结构中。
IPC_SET:将msqid相关的数据结构中的元素设置为由buf指向的结构中的对应值。
buf:msqid_ds数据类型的地址,用来存放或修改消息队列的属性。
返回值:
成功:0
失败:-1
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/msg.h>
int main()
{
key_t key;
if ((key = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
int msqid;
if ((msqid = msgget(key, IPC_CREAT | 0777)) == -1)
{
perror("fail to msgget");
exit(1);
}
printf("msqid = %d\n", msqid);
system("ipcs -q");
if ((msgctl(msqid, IPC_RMID, NULL)) == -1)
{
perror("fail to msgctl");
exit(1);
}
system("ipcs -q");
return 0;
}
执行截图:
总结:
消息队列作为一种IPC机制,它提供了进程间发送和接收消息的强大功能。通过使用消息队列,可以构建复杂的多进程应用程序,实现数据的安全传输和有序交流,从而极大地提高了系统的并发处理能力。但,也要意识到消息队列的限制,比如队列的大小、存储消息的总数量等,它们都可能会对应用程序的性能产生影响。
因此,在使用消息队列时,我们需要权衡其优点和潜在的挑战,确保在满足业务需求的同时,能保持高效的性能和稳定的运行。