消息队列
消息队列是消息的链表,存放在内存中,由内核维护
特点:
1、消息队列中的消息是有类型的。
2、消息队列中的消息是有格式的。
3、消息队列可以实现消息的随机查询。消息不一定要以先进先出的次序读取,编程时 可以按消息的类型读取。
4、消息队列允许一个或多个进程向它写入或者读取消息。
5、与无名管道、命名管道一样,从消息队列中读出消息,消息队列中对应的数据都会被删除。
6、每个消息队列都有消息队列标识符,消息队列的标识符在整个系统中是唯一的。
7、只有内核重启或人工删除消息队列时,该消息队列才会被删除。若不人工删除消息队列,消息队列会一直存在于系统中
注意
:
在 ubuntu
某些版本中消息队列限制值如下
:
每个消息内容最多为8K
字节
每个消息队列容量最多为16K
字节
系统中消息队列个数最多为1609
个
系统中消息个数最多为16384
个
消息队列操作命令
ipcs -q
查看消息队列
ipcrm -q msgid
删除消息队列
id
为
msgid
的消息队列
获取Key值
System V
提供的进程间通讯机制
(IPC
通信机制
)
需要一个
key
值
key
值可以是人为指定的,也可以通过
ftok
函数获得。
ftok函数
所需头文件
:
#include <sys/types.h>
#include <sys/ipc.h>
函数
:
key_t ftok(const char *pathname, int proj_id);
参数:
pathname:路径名
,
这个路径必须是存在的而且可以访问的
//~
///
//./
proj_id:项目
ID
,非
0
整数
(
只有低
8
位有效
)
返回值:
成功返回 key
值
失败返回 -1
注意
:
当调用该函数时传入的参数一致获取到的key
值也将相同
多进程通讯时要保证路径名相同,id
也相同
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
int main(int argc, char const *argv[])
{
key_t key01 = ftok("./",2023);
key_t key02 = ftok("./",2023);
key_t key03 = ftok("./",2022);
printf("key1=%u\n",key01);
printf("key2=%u\n",key02);
printf("key3=%u\n",key03);
return 0;
}
创建或获取消息队列--msgget函数
创建一个新的或打开一个已经存在的消息队列。不同的进程调用此函数,只要用相同的 key 值就能得到同一个消息队列的标识符。
所需头文件
#include <sys/msg.h>
函数
int msgget(key_t key, int msgflg);
参数:
key:
IPC
键值。
msgflg:标识函数的行为及消息队列的权限。
msgflg 的取值:
IPC_CREAT:创建消息队列。
IPC_EXCL:检测消息队列是否存在。
位或权限位:消息队列位或权限位后可以设置消息队列的访问权限,格式和 open 函数的
mode_t
一样,但可执行权限未使用。
返回值:
成功:消息队列的标识符(msgid)
,
失败:返回-1
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main(int argc, char const *argv[])
{
//获取key值
//./表示当前路径
key_t key = ftok("./",2023);
printf("key=%u\n",key);
//创建消息队列,权限为可读可写
//返回值为消息队列id
int msgid = msgget(key,IPC_CREAT | 0666);
printf("msgid=%d\n",msgid);
//判断消息队列是否存在
key_t key02 = ftok("/",2022);
int msgid02 = msgget(key02,IPC_EXCL);
printf("msgid02=%d\n",msgid02);
return 0;
}
消息的格式
typedef struct
_msg
{
long
mtype
;
/*
消息类型
,
必须是第一个成员
,
必须是
long
型
,
就是该消息的
id*/
char
mtext
[
100
];
/*
消息正文
,
用户自定义
*/
...
/*
消息的正文可以有多个成员
*/
}
MSG
;
发送消息--msgsnd函数
作用
:
将新消息添加到消息队列。
函数
:
所需头文件
:
#include <sys/msg.h>
函数
:
int msgsnd(int msqid, const void *msgp,size_t msgsz, int msgflg);
参数
:
msqid:消息队列的标识符。
msgp:待发送消息结构体的地址。
msgsz:消息正文的字节数。
msgflg:函数的控制属性
0:
msgsnd
调用阻塞直到条件满足为止。
(
推荐
)
IPC_NOWAIT: 若消息没有立即发送则调用该函数的进程会立即返回。
返回值
:
成功:0
失败:-1
。
接收消息--msgrcv函数
作用
:
从标识符为
msqid
的消息队列中接收一个消息。一旦接收消息成功,则消息 在消息 队列中被删除。
函数
所需头文件
#include <sys/msg.h>
函数
:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
参数
:
msqid:消息队列的标识符,代表要从哪个消息列中获取消息。
msgp:存放消息结构体的地址。
msgsz:消息正文的字节数。
msgtyp:消息的类型、可以有以下几种类型
msgtyp=0:返回队列中的第一个消息
msgtyp>0:返回队列中消息类型为
msgtyp
的消息
msgtyp<0:返回队列中消息类型值小于或等于
msgtyp
绝对值的消息
,
如果这种 消息有若干个,
则取类型值最小的消息。
注意:
若消息队列中有多种类型的消息,msgrcv
获取消息的时候按消息类型获取,
不是先进先出的。
在获取某类型消息的时候
,
若队列中有多条此类型的消息
,
则获取最先添加的消息,
即先进先出原则
msgflg:函数的控制属性
0:msgrcv调用阻塞直到接收消息成功为止。
MSG_NOERROR:若返回的消息字节数比
nbytes
字节数多
,
则消息就会截短到 nbytes字节
,
且不通知消息发送进程
IPC_NOWAIT:调用进程会立即返回。若没有收到消息则立即返回
-1
。
返回值
:
成功返回读取消息的长度
失败返回-1
案例1:简单的消息收发
张三发出消息类型为
10
与
20
的消息
李四接收消息类型为
10
的消息
王五接收消息类型为
20
的消息
代码:zs.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct struct_msg{
long msgType;
char name[20];
char text[128];
}Msg;
int main(int argc, char const *argv[])
{
//获取key值
key_t key = ftok("./",258);
//创建或获取消息对象
int msgid = msgget(key,IPC_CREAT| 0666);
//准备要发送的消息
Msg msg01 = {10,"张三","你好 李四"};
Msg msg02;
msg02.msgType=20;
strcpy(msg02.name,"张三");
strcpy(msg02.text,"你好 王五");
//发送消息给李四
msgsnd(msgid,&msg01,sizeof(msg01) - sizeof(long),0);
//发现消息给王五
msgsnd(msgid,&msg02,sizeof(msg01) - sizeof(long),0);
return 0;
}
ls.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct struct_msg{
long msgType;
char name[20];
char text[128];
}Msg;
int main(int argc, char const *argv[])
{
key_t key = ftok("./",258);
int msqid = msgget(key,IPC_CREAT|0666);
Msg msg;
msgrcv(msqid,&msg,sizeof(msg)-sizeof(long),10,0);
printf("李四接收到的消息为\n");
printf("姓名:%s说%s\n",msg.name,msg.text);
return 0;
}
ww.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct struct_msg{
long msgType;
char name[20];
char text[128];
}Msg;
int main(int argc, char const *argv[])
{
key_t key = ftok("./",258);
int msqid = msgget(key,IPC_CREAT|0666);
Msg msg;
msgrcv(msqid,&msg,sizeof(msg)-sizeof(long),20,0);
printf("王五接收到的消息为\n");
printf("姓名:%s说%s\n",msg.name,msg.text);
return 0;
}
案例2:多人聊天程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
typedef struct info{
long type;
char name[30];
char text[128];
}INF;
int main(int argc, char const *argv[])
{
key_t key = ftok("./",1);
printf("请输入姓名\n");
int name[30]={0};
scanf("%s",&name);
printf("请输入接收的类型\n");
int rtype;
scanf("%d",&rtype);
printf("请输入发送的类型\n");
int stype;
scanf("%d",&stype);
int msgid = msgget(key,IPC_CREAT|0666);
int i = 0;
for(i = 0; i<2; i++){
pid_t pid = fork();
if (pid == 0)
{
break;
}
}
if(i==0){
//接收
INF inf;
while (1)
{
msgrcv(msgid,&inf,sizeof(INF)-sizeof(long),rtype,0);
printf("%s:%s\n",inf.name,inf.text);
if(strcmp("886",inf.text)==0){
break;
}
}
_exit(0);
}else if(i==1){
//发送
INF inf;
while (1)
{
strcpy(inf.name,name);
scanf("%s",inf.text);
inf.type = stype;
msgsnd(msgid,&inf,sizeof(INF)-sizeof(long),0);
// printf("%s:%s\n",info.name,info.text);
if(strcmp("886",inf.text)==0){
break;
}
}
_exit(0);
}else if(i==2){
while (1)
{
int id = waitpid(-1,NULL,WNOHANG);
if (id == -1)
{
break;
/* code */
}
}
}
return 0;
}
消息队列控制(了解)
作用
:
对消息队列删除
,
获取
,
修改等操作
语法
所需头文件
:
#include <sys/msg.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
struct msqid_ds
类型:
struct msqid_ds {
struct ipc_perm msg_perm; /* 所有者与权限
*/
time_t msg_stime; /* 最后一条消息发送的时间
*/
time_t msg_rtime; /* 最后一条消息接收的时间
*/
time_t msg_ctime; /* 最后一次更改的时间
*/
unsigned long __msg_cbytes; /* 队列中的当前字节数(非标准)
*/
msgqnum_t msg_qnum; /* 队列中的当前消息数
*/
msglen_t msg_qbytes; /* 队列中允许的最大字节数
*/
pid_t msg_lspid; /* 最后一次发送的进程
id */
pid_t msg_lrpid; /* 最后一次接收的进程
id */
};
struct ipc_perm
类型
:
struct ipc_perm {
key_t __key; /* 提供给
msgget
的密钥
(2) */
uid_t uid; /* 所有者的有效
UID */
gid_t gid; /* 所有者的有效
GID */
uid_t cuid; /* 创建者的有效
UID */
gid_t cgid; /* 创建者的有效
GID */
unsigned short mode; /* 权限
*/
unsigned short __seq; /* 序列号
*/
};
磁盘映射
磁盘映射MMAP
存储映射 I/O (Memory-mapped I/O)
使一个磁盘文件与存储空间中的一个缓冲区相 映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存 入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用 read
和
write
函数 的情况下,使用地址(指针)完成 I/O
操作。 使用存储映射这种方法,首先应通知内 核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap
函数来实现。
与文件读取的区别(了解)
首先简单的回顾一下常规文件系统操作(调用read/fread
等类函数)中,函数的调用
过程:
1、进程发起读文件请求。
2、内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此 文件的inode(
存储文件元信息的区域
,
中文名索引节点
)
。
3、
inode
在
address_space(
地址空间
)
上查找要请求的文件页是否已经缓存在页缓存 中。如果存在,则直接返回这片文件页的内容。
4、如果不存在,则通过
inode
定位到文件磁盘地址,将数据从磁盘复制到页缓存。之后 再次发起读页面过程,进而将页缓存中的数据发给用户进程。
总结来说,常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成 读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用 户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这 样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一 样,待写入的buffer
在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存, 再写回磁盘中(延迟写回),也是需要两次数据拷贝。
而使用mmap
操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映 射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺 页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数 据传入内存的用户空间中,供进程使用。
总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap
操 控文件,只需要从磁盘到用户主存的一次数据拷贝过程。
mmap
效率更高
mmap函数
作用:建立映射区
所需头文件
#include <sys/mman.h>
函数
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
参数
:
addr 内核创建映射的地址,填
NULL,
由内核自己选取地址
length 长度 要申请的映射区的长度
prot 权限
PROT_READ 可读
PROT_WRITE 可写
flags 标志位
MAP_SHARED 共享的
--
对映射区的修改会影响源文件
MAP_PRIVATE 私有的
fd 文件描述符 需要打开一个文件
offset 指定一个偏移位置 ,从该位置开始映射
返回值
成功 返回映射区的首地址
失败 返回 MAP_FAILED ((void *) -1)
munmap函数
作用:
释放映射区
所需头文件
#include <sys/mman.h>
函数
int munmap(void *addr, size_t length)
参数
:
addr 映射区的首地址
length 映射区的长度
返回值
成功 返回 0
失败 返回 -1
truncate函数
作用:
会将参数path
指定的文件大小改为参数
length
指定的大小
.
如果原来的文件大小比 参数length
大
,
则超过的部分会被删去
.
所需头文件
#include <unistd.h>
#include <sys/types.h>
函数
int truncate(const char *path, off_t length);
参数
:
path 要拓展的文件
length 要拓展的长度
使用步骤
1,
打开文件
2,
指定文件大小
3,
建立映射
4,
释放映射区
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
// 1、通过open打开文件
int fd = open("tmp", O_RDWR | O_CREAT, 0666);
if (fd < 0)
{
perror("open");
return 0;
}
// 2、拓展文件大小
truncate("tmp", 16);
// 3、mmap建立映射
char *buf = (char *)mmap(NULL, 16, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
// 4、使用内存区域,读
printf("buf=%s\n", buf);
printf("buf=%s\n", buf);
// 4、使用内存区域,写
//strcpy(buf, "hello mmap");
// 5、断开映射
munmap(buf, 16);
close(fd);
return 0;
}
共享内存
共享内存允许两个或者多个进程共享给定的存储区域。
物理内存:
电脑物理内存就是指的内存条
虚拟内存:
是系统默认在
C
盘划分的一部分磁盘空间临时存储数据用的
特点:
1、共享内存是进程间共享数据的一种最快的方法。 一个进程向共享的内存区域写入了 数据,共享这个内存区域的所有进程就可以立刻看到其中的内容.
2、使用共享内存要注意的是多个进程之间对一个给定存储区访问的互斥。 若一个进程 正在向共享内存区写数据,则在它做完这一步操作前,别的进程不应当去读、写这些数据
在 ubuntu 部分版本中共享内存限制值如下
共享存储区的最小字节数:
1
共享存储区的最大字节数:
32M
共享存储区的最大个数:
4096
每个进程最多能映射的共享存储区的个数:
4096
共享内存命令
查看共享内存
ipcs -m
删除共享内存
ipcrm -m shmid
shmget函数
作用:
创建或打开一块共享内存区
,
获得一个共享存储标识符
所需头文件
#include <sys/ipc.h>
#include <sys/shm.h>
函数
int shmget(key_t key, size_t size, int shmflg);
参数
key:IPC 键值
(
进程间通讯键值
)
size:该共享存储段的长度
(
字节
)
shmflg:标识函数的行为及共享内存的权限。
IPC_CREAT:创建
,
如果不存在就创建
IPC_EXCL:如果已经存在则返回失败
位或权限位:
共享内存位或权限位后可以设置共享内存的访问权限,格式和 open函数的
mode_t
一样
,
但可执行权限未使用。
返回值
:
成功:返回共享内存标识符。
失败:返回-1
。
shmat函数
作用:
建立进程和物理内存的映射
头文件:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr,int shmflg);
参数
:
shmid:共享内存标识符。
shmaddr:共享内存映射地址
(
若为
NULL
则由系 统自动指定
)
,推荐使用 NULL。
shmflg:共享内存段的访问权限和映射条件
0:共享内存具有可读可写权限。
SHM_RDONLY:只读。
SHM_RND:(
shmaddr
非空时才有效)没有指定
SHM_RND
则此段连接到
shmaddr 所指定的地址上
(shmaddr
必需页对齐
)
。
指定了 SHM_RND
则此段连接到
shmaddr- shmaddr%SHMLBA
所表示的地址 上。
返回值:
成功:返回共享内存段映射地址
失败:返回 -1
注意
:
shmat函数使用的时候第二个和第三个参数一般设为
NULL
和
0,
即系统自动指定共享 内存地址,
并且共享内存可读可写
shmdt函数
作用:
将共享内存和当前进程分离(
仅仅是断开本进程与共享内存的联系
,
并不删除共享内存)
。
所需头文件
#include <sys/types.h>
#include <sys/shm.h>
函数
int shmdt(const void *shmaddr);
参数:
shmaddr:共享内存映射地址。
返回值:
成功返回 0
失败返回 -1
shmctl函数
作用:
共享内存空间的控制。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:共享内存标识符。
cmd:函数功能的控制。
IPC_RMID:删除。
IPC_SET:设置
shmid_ds
参数。
IPC_STAT:保存
shmid_ds
参数。
SHM_LOCK:锁定共享内存段
(
超级用户
)
。
SHM_UNLOCK:解锁共享内存段。
buf:
shmid_ds
数据类型的地址,用来存放或修改共享内存的属性。
返回值:
成功返回 0
失败返回 -1
。
注意:
SHM_LOCK 用于锁定内存,禁止内存交换。并不代表共享内存被锁定后禁止其它进程访问。其真正的意义是:被锁定的内存不允许被交换到虚拟内存中。这样做的优势在 于让共享内存一直处于内存中,从而提高程序性能
使用步骤
1,
获取唯一
key
值
2,
获取共享内存标识符
3,
建立共享内存映射
4,
操作映射
读
写
5,
关闭映射
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main(int argc, char const *argv[])
{
// 1、获取唯一的key值
key_t key = ftok("/", 2023);
// 2、根据唯一的key的共享内存标识(分配物理内存)
int shm_id = shmget(key, 32, IPC_CREAT | 0666);
printf("shm_id=%d\n", shm_id);
// 3、建立进程和物理内存的映射
char *p = (char *)shmat(shm_id, NULL, 0);
//4,操作映射,写
strcpy(p, "hello shm");
//4,操作映射,读
// printf("%s\n",p);
// 5、断开进程和物理内存的映射
shmdt(p);
return 0;
}