# 进程间通信的方式
文章目录
管道
消息队列
信号
信号量
socket
消息队列
首先消息队列就是内核维护的一块链表区域,只要是有足够权限的进程都可以向队列中添加消息,只要是有读权限的进程都可以在里面拿出消息
- 克服了信号承载信息少,以及管道只能承载无格式字节流以及有限缓冲区大小的缺点
- 消息队列是随着内核可持续的
- 消息队列信息的传递是基于拷贝的,所以效率低下
- 消息队列只有内核重启或者人工删除的时候才会消失,所以不用的时候请关闭
进程通过key可以获取或者创建(系统维护)一个msqid_ds结构体。然后操作系统会返回key对应的一个id。后续进程通过id对msqid_ds进行存取。msqid_ds是表示一个消息队列的管理者。各个进程使用一个msqid_ds进行通信。操作系统会有权限控制,大小控制等。
每个消息由两部分组成,分别是消息编号(消息类型)和消息正文。
1)消息编号:识别消息用
2)消息正文:真正的信息内容
消息队列收发数据
发送消息
①进程先封装一个消息包
这个消息包其实就是如下类型的一个结构体变量,封包时将消息编号和消息正文
写到结构体的成员中。
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,并设置error
。
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被设置
使用步骤
1. 通过ftok(路径,整形)来生成一个唯一对应的key
2. msgget(key,flag)来创建或者获取一个消息队列,flag是指定权限用的
3. msgsnd(msqid,msgp,msgz,msgflag) 发送数据
4. msgrcv(msqidmnsgpmnsgz,msgtype,msgflag) 接收消息, flag可以设置阻塞和非阻塞接收