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