概述缓存
System V IPC引入了3中通讯方式,本文主要介绍消息队列函数
System V IPC再也不以文件的形式存在,所以没有文件描述符这个东西,可是它有相似的“标识符”。彻底能够认为这个“标识符”就是文件描述符的替代者,可是它是专门给System V IPC使用的,因此咱们不能使用文件IO函数来操做“标识符”,只能使用System V IPC的特有API才能操做。ui
如何获取这个“标识符”?spa
调用某API建立好某个“通讯结构”之后,API就会返回一个惟一的“标识符”。3d
消息队列原理code
消息队列本质blog
消息队列的本质就是由内核建立的用于存放消息的链表,因为是存放消息的,因此咱们就把这个链表称为了消息队列。通讯的进程经过共享操做同一个消息队列,就能实现进程间通讯。队列
消息队列模型进程
每一个消息由两部分组成,分别是消息编号(消息类型)和消息正文。
1)消息编号:识别消息用
2)消息正文:真正的信息内容ip
消息队列收发数据
发送消息
①进程先封装一个消息包
这个消息包其实就是以下类型的一个结构体变量,封包时将消息编号和消息正文
写到结构体的成员中。
struct msgbuf { long mtype; /* 放消息编号,必须> 0 */ char mtext[msgsz]; /* 消息内容(消息正文) */ };
②调用相应的API发送消息
调用API时经过“消息队列的标识符”找到对应的消息队列,而后将消息包发送给消息队列,消息包(存放消息的结构体变量)会被做为一个链表节点插入链表。
接收消息
调用API接收消息时,必须传递两个重要的信息
(a)消息队列标识符
(b)你要接收消息的编号
有了这两个信息,API就能够找到对应的消息队列,而后从消息队列中取出你所要编号的消息,如此就收到了别人所发送的信息。
消息队列使用步骤
①使用msgget函数建立新的消息队列、或者获取已存在的某个消息队列,并返回惟一标识消息队列的标识符(msqID),后续收发消息就是使用这个标识符来实现的。
②收发消息
发送消息:使用msgsnd函数,利用消息队列标识符发送某编号的消息
接收消息:使用msgrcv函数,利用消息队列标识符接收某编号的消息
③使用msgctl函数,利用消息队列标识符删除消息队列
对于使用消息队列来通讯的多个进程来讲,只须要一个进程来建立消息队列就能够了,对于其它要参与通讯的进程来讲,直接使用这个建立好的消息队列便可。为了保证消息队列的建立,最好是让每个进程都包含建立消息队列的代码,谁先运行就由谁建立,后运行的进程若是发现它想用的那个消息队列已经建立好了,就直接使用,当众多进程共享操做同一个消息队列时,便可实现进程间的通讯。
API
msgget
函数原型
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg);
功能
利用key值建立、或者获取一个消息队列。key值也可以惟一的标识消息队列。
若是key没有对应任何消息队列,那就建立一个新的消息队列
若是key已经对应了某个消息队列,说明你要的消息队列已经存在了,那就获取这个消息队列来使用
参数
key值:
用于为消息队列生成(计算出)惟一的消息队列ID。
咱们能够指定三种形式的key值:
①指定为IPC_PRIVATE宏,指定这个宏后,每次调用msgget时都会建立一个新的消息队列。若是你每次使用的必须是新消息队列的话,就能够指定这个,不过这个用的不多。由于通常来讲,只要有一个消息队列能够用来通讯就能够了,并不须要每次都建立一个全新的消息队列。
②能够本身指定一个整形数,可是容易重复指定。原本我想建立一个新的消息队列,结果我所指定的这个整形数,以前就已经被用于建立某个消息队列了,当个人指定重复时,msgget就不会建立新消息队列,而是使用的是别人以前就建立好的消息队列。不多使用这种方式
③使用ftok函数来生成key
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
ftok经过指定路径名和一个整形数,就能够计算并返回一个惟一对应的key值,只要路径名和整形数不变,所对应的key值就惟一不变的。不过因为ftok只会使用整形数(proj_id)的低8位,所以咱们每每会指定为一个ASCII码值,由于ASCII码值恰好是8位的整形数。
msgflg:
指定建立时的原始权限,好比0664
建立一个新的消息队列时,除了原始权限,还须要指定IPC_CREAT选项。msgid = msgget(key, 0664|IPC_CREAT);
若是key值没有对应任何消息队列,就会建立一个新的消息队列,此时就会用到msgflg参数,可是若是key已经对应了某个早已存在消息队列,就直接返回这个已存在消息队列的ID(标识符),此时不会用到msgflg参数。
返回值
成功:返回消息队列标识符(消息队列的ID)对于每个建立好的消息队列来讲,ID是固定的。
失败:失败返回-1,并设置errno。
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:消息队列的标识符。
msgp:存放消息的缓存的地址,类型struct msgbuf类型。这个缓存就是一个消息包(存放消息的结构体变量)。
struct msgbuf { long mtype; /* 放消息编号,必须 > 0 */ char mtext[msgsz]; /* 消息内容(消息正文) */ };
msgsz:消息正文大大小。
msgflg:
0:阻塞发送消息。也就是说,若是没有发送成功的话,该函数会一直阻塞等,直到发送成功为止。
IPC_NOWAIT:非阻塞方式发送消息,无论发送成功与否,函数都将返回也就是说,发送不成功的的话,函数不会阻塞。
返回值
成功:返回0,失败:返回-1,errno被设置
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:消息队列的标识符。
msgp:缓存地址,缓存用于存放所接收的消息。类型仍是struct msgbuf,同上
msgsz:消息正文的大小
msgtyp:你要接收消息的编号
int msgflg:
0:阻塞接收消息。也就是说若是没有消息时,阻塞接收(msgrcv函数会休眠)。
IPC_NOWAIT:非阻塞接收消息。也就是说没有消息时,该函数不阻塞。他会由于读取失败而错误返回
返回值
成功:返回消息正文的字节数。失败:返回-1,errno被设置
msgctl
函数原型
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能
根据cmd指定的要求,去控制消息队列。能够有那些控制?
获取消息队列的属性信息
修改消息队列的属性信息
删除消息队列
等等
咱们调用msgctl函数的最多见目的就是删除消息队列,事实上,删除消息队列只是各类消息队列控制中的一种。
参数
msqid:消息队列标识符
cmd:控制选项,其实cmd有不少选项,我这里只简单介绍三个
①IPC_STAT:将msqid消息队列的属性信息,读到第三个参数所指定的缓存。
②IPC_SET:使用第三个参数中的新设置去修改消息队列的属性
+ 定一个struct msqid_ds buf。
+ 将新的属性信息设置到buf中
+ cmd指定为IPC_SET后,msgctl函数就会使用buf中的新属性去修改消息队列原有的属性。
③IPC_RMID:删除消息队列.删除消息队列时,用不到第三个参数,用不到时设置为NULL。
buf:存放属性信息
有的时候须要给第三个参数,有时不须要,取决于cmd的设置。buf的类型为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; /* 最后一次向队列发送消息的进程PID */ pid_t msg_lrpid; /* 最后一次从队列接受消息的进程PID */ }; struct ipc_perm { key_t __key; /* Key supplied to msgget(2):消息队列的key值 */ uid_t uid; /* UID of owner :当前这一刻正在使用消息队列的用户 */ gid_t gid; /* GID of owner :正在使用的用户所在用户组 */ uid_t cuid; /* UID of creator :建立消息队列的用户 */ gid_t cgid; /* GID of creator :建立消息队列的用户所在用户组*/ unsigned short mode; /* Permissions:读写权限(好比0664) */ unsigned short __seq; /* Sequence number :序列号,保障消息队列ID不被当即重复使用 */ };
返回值
成功:返回0。失败:返回-1,errno被设置
多进程共享消息队列
建立进程
若是建立者使用"./file", 'a'生成一个key值,而后调用msgget建立了一个消息队列,好比:
key = ftok("./file", 'a'); msgid = msgget(key, 0664|IPC_CREAT);
当建立者获得msgid后,便可操做消息队列。
其余进程共享操做消息队列
共享的方法很简单,只要你能拿到别人建立好的消息队列的ID,便可共享操做同一个消息队列,实现进程间通讯。
获取别人建立好的消息队列的ID,有两个方法:
①建立者把ID保存到某文件,共享进程读出ID便可。这种状况下,共享进程根本不须要调用msgget函数来返回ID。
②调用msgget获取已在消息队列的ID
使用ftok函数,利用与建立者相同的“路径名”和8位整形数,生成相同的key值
调用msgget函数,利用key找到别人建立好的消息队列,返回ID
key = ftok("./file", 'a'); msgid = msgget(key, 0664|IPC_CREAT);
拿到了消息队列的ID后就能共享操做了。这种方法是最经常使用的方法,由于ftok所用到的“路径名”和“8位的整形数”比较好记忆,因此,你只要记住别人生成key值时所用的“路径名”和“8位的整形数”,你就必定能共享操做别人建立好的消息队列。
ipcs
用于报告Linux中进程间通讯设施的状态,显示的信息包括消息列表、共享内存和信号量的信息。
- a 或者 什么都不跟:消息队列、共享内存、信号量的信息都会显示出来
[root@localhost ~]# ipcs ------ Message Queues -------- key msqid owner perms used-bytes messages ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 262144 root 600 524288 2 dest 0x00000000 360449 root 600 16777216 2 dest 0x00000000 393218 root 600 524288 2 dest ------ Semaphore Arrays -------- key semid owner perms nsems [root@localhost ~]# ipcs -a ------ Message Queues -------- key msqid owner perms used-bytes messages ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 262144 root 600 524288 2 dest 0x00000000 360449 root 600 16777216 2 dest 0x00000000 393218 root 600 524288 2 dest ------ Semaphore Arrays -------- key semid owner perms nsems
- m:只显示共享内存的信息
- q:只显示消息队列的信息
- s:只显示信号量的信息
ipcrm
用来删除一个或更多的消息队列、信号量集或者共享内存标识。
删除共享内存
M:按照key值删除
ipcrm -M key
m:按照标识符删除
ipcrm -m msgid
删除消息队列
Q:按照key值删除
q:按照标识符删除
删除信号量
S:按照key值删除
s:按照标识符删除
消息队列示例代码
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/ipc.h> 6 #include <sys/msg.h> 7 #include <sys/types.h> 8 #include <sys/stat.h> 9 #include <fcntl.h> 10 #include <strings.h> 11 #include <signal.h> 12 13 #define MSG_FILE "./msgfile" 14 15 #define MSG_SIZE 1024 16 17 struct msgbuf 18 { 19 long mtype; /* 放消息编号,必须 > 0 */ 20 char mtext[MSG_SIZE]; /* 消息内容(消息正文) */ 21 }; 22 23 24 void print_err(char *estr) 25 { 26 perror(estr); 27 exit(-1); 28 } 29 30 int creat_or_get_msgque(void) 31 { 32 int msgid = -1; 33 key_t key = -1; 34 int fd = 0; 35 36 /* 建立一个消息队列的专用文件,ftok会用到这个文件的路径名 */ 37 fd = open(MSG_FILE, O_RDWR|O_CREAT, 0664); 38 if(fd == -1) print_err("open fail"); 39 40 /* 利用存在的文件路径名和8位整形数,计算出key */ 41 key = ftok(MSG_FILE, 'a'); 42 if(key == -1) print_err("ftok fail"); 43 44 /* 利用key建立、或者获取消息队列 */ 45 msgid = msgget(key, 0664|IPC_CREAT); 46 if(msgid == -1) print_err("msgget fail"); 47 48 return msgid; 49 } 50 51 int msgid = -1; 52 void signal_fun(int signo) 53 { 54 msgctl(msgid, IPC_RMID, NULL); 55 remove(MSG_FILE); 56 57 exit(-1); 58 } 59 60 int main(int argc, char **argv) 61 { 62 int ret = -1; 63 long recv_msgtype = 0; 64 65 if(argc != 2) 66 { 67 printf("./a.out recv_msgtype\n"); 68 exit(-1); 69 } 70 //atol字符串转长整形 71 recv_msgtype = atol(argv[1]); 72 73 74 msgid = creat_or_get_msgque(); 75 76 ret = fork(); 77 if(ret > 0) //发送消息 78 { 79 signal(SIGINT, signal_fun); 80 struct msgbuf msg_buf = {0}; 81 while(1) 82 { 83 bzero(&msg_buf, sizeof(msg_buf)); 84 /* 封装消息包 */ 85 scanf("%s", msg_buf.mtext); 86 printf("input snd_msgtype:\n"); 87 scanf("%ld", &msg_buf.mtype); 88 89 /* 发送消息包 */ 90 msgsnd(msgid, &msg_buf, MSG_SIZE, 0); 91 } 92 } 93 else if(ret == 0)//接收消息 94 { 95 struct msgbuf msg_buf = {0}; 96 int ret = 0; 97 while(1) 98 { 99 bzero(&msg_buf, sizeof(msg_buf)); 100 ret = msgrcv(msgid, &msg_buf, MSG_SIZE, recv_msgtype, 0); 101 if(ret > 0) 102 { 103 printf("%s\n", msg_buf.mtext); 104 } 105 } 106 } 107 108 109 return 0; 110 }