Linux进程间通信——消息队列(一)
小编心声:昨天刚又完成一个996~
认真写文章,希望可以帮到大家!
我学习一个东西,喜欢先从整体上了解框架,然后再了解所学习的东西是框架中的哪一细分部分。今天就聊一聊Linux系统进程之间的通信。
程序环境:ubuntu16.04 x_64 虚拟机
一、 站得高,望得远
有三种IPC(进程间通讯 )
我们称作 XSI IPC
,即消息队列、信号量和共享内存
1. XSI IPC
① POSIX
标准 Portable Operating System Interface(
可移植操作系统接口 )
② Single UNIX Specification
是 POSIX
的超集
③ X/Open System Interface(XSI IPC)
符合Single UNIX规范的系统的核心应用程序编程接口
有点儿蒙圈吧,正常正常~
个人理解:听说过POSIX多线程程序设计吧,就是符合①的可移植操作系统接口的多线程设计,然后②又是①的超集,再然后③是符合②的 ……
可能很多人就是因为这些才不想学一些东西吧,不过这些不清楚也没多大关系
2. 进程间通信分类
进程间数据通信必须通过内核,因为不同进程的用户地址空间是不
同的,他们
各自的全局变量是不可见的。
所以他们通过在内核地址上开辟出一段空间来进行数
据传输。
进程间通信根据是否在同一台主机上进行通信可分为无名管道和有名管道(FIFO),消息队列、信号量和共享内存这些都是只能在同一台主机上进行通信的
Socket和 Streams(
这个没接触过 )
是可以在不同主机上进行进程通讯的。
3. 进程间通信之管道简介
①无名管道
②有名管道
无名管道的限制:半双工
两个进程需要有公共祖先
有名管道举例:当在终端连续使用两个命令时,一条命令的输出通过管道作为另一条命令的输入。
二、XSI IPC 的使用与注意事项
1. 标识符和Key
每个内核中的IPC结构 (
消息队列、信号量、共享内存 )
都用一个非负整数的标
识 符来进行调用。
如,当使用消息队列发送或接收消息队列时,需要知道队列标识符。
标识符是IPC内部的名称,在外部通信时使用 Key
作为标识符,每个 IPC
对象都与一个 Key
相关联。
2. 新建Key的方法及注意事项
get函数的两个参数分别是 Key
和一个整型 flag(
之后会介绍 get
函数 )
① Key
是 IPC_PRIVATE
② Key
当前未 与
特定的 IPC
结构相结合,并且 flag
中指定了 IPC_CREAT
位
③ ftok(
暂不具体介绍 )
当访问已存在的队列时,Key值必须与创建队列时指定的 Key
值相同,且不应指定 IPC_CREAT
注意
:
①为了访问一个现存的队列,决不能指定 IPC_PRIVATE
作为
Key
,因为它总是用于创建一个新队列。
②如果希望新建一个消息队列,而且要确保不是引用具有同一标识
符 的现有的消息队列,需在
flag
中指
定
IPC_CREAT
和
IPC_EXCL
。
这样,如果消息队列已经存在则返回值会报错。
3. 三种形式XSI IPC结构限制
我的系统默认限制如下:
4. 优点和缺点
XSI IPC的主要问题是: IPC
结构是在系统范围内起作用的,没有引用计数。这点可以类比 C++
的智能指针。例如:如果进程创建 了一个消息队列,并在队列中放入了几条消息,然后进程终止,但是该消息队列及其内容并不会被删除。
当以下情况出现时消息队列才不会继续存在系统中:
①某个进程调用 msgrcv
或 msgctl
读取或删除消息队列
②某个进程执行 ipcrm(1)
命令删除息队列
与管道相比,最后一个访问管道的进程结束时,管道就彻底被删除了(可与智能指针类比 )
。
5. 程序通信例子
① Send:
#include #include #include #include #include #include
#define MAX_TEXT 512 struct msg_st { long int msg_type; char text[MAX_TEXT]; };
int main() { int running = 1; struct msg_st data; char buffer[BUFSIZ]; int msgid = -1;
//建立消息队列 msgid = msgget((key_t)1234, 0666 | IPC_CREAT | IPC_EXCL); if(msgid == -1) { fprintf(stderr, "msgget failed with error: %d\n", errno); exit(EXIT_FAILURE); }
//向消息队列中写消息,直到写入end while(running) { //输入数据 printf("Enter some text: "); fgets(buffer, BUFSIZ, stdin); data.msg_type = 1; //注意2 strcpy(data.text, buffer); //向队列发送数据 if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1) { fprintf(stderr, "msgsnd failed\n"); exit(EXIT_FAILURE); }
printf("You wrote: %s\n",data.text);
//输入end结束输入 if(strncmp(buffer, "end", 3) == 0) running = 0; sleep(1); } exit(EXIT_SUCCESS); }
② Rcv:
#include #include #include #include #include #include
struct msg_st { long int msg_type; char text[BUFSIZ]; };
int main() { int running = 1; int msgid = -1; struct msg_st data; long int msgtype = 0; //注意1
//建立消息队列 //msgid = msgget((key_t)1234, 0666 | IPC_CREAT); msgid = msgget((key_t)1234, 0666); if(msgid == -1) { fprintf(stderr, "msgget failed with error: %d\n", errno); exit(EXIT_FAILURE); } //从队列中获取消息,直到遇到end消息为止 while(running) { if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1) { fprintf(stderr, "msgrcv failed with errno: %d\n", errno); exit(EXIT_FAILURE); } printf("You wrote: %s\n",data.text); //遇到end结束 if(strncmp(data.text, "end", 3) == 0) running = 0; } //删除消息队列 if(msgctl(msgid, IPC_RMID, 0) == -1) { fprintf(stderr, "msgctl(IPC_RMID) failed\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }
③程序运行效果
发送效果:
接收效果:
运行发送程序,根据提示输入字符串,接收端会收到字符,输入end消息队列
终止。
三、小结
程序就是网上最流行的例子,做了微小的改动,下面想几个问题:
①发送和接收可以对同一个 Key
多次使用不同的进程访问么?如果可以效果是什么样的,一对多还是多对一?多次访问属于正常操作么?
②使用什么方式让发送端与接收端都知道 Key
值呢?
③下次具体介绍 api
时还有其他精彩的用法
参考书籍 《UNIX环境高级编程第三版》
阅读一手资料,多思考,还是挺好的。