目录
进程间通信的目的
·数据传输:一个进程需要将它的数据发送给另一个进程
·资源共享:多个进程之间共享同样的资源
·通知事件:一个进程需要向另一个或一组进程发送消息,通知它发生了某种时间
·进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
·进程间通信的本质:让不同的进程看到相同的一份资源
一、管道
1、什么是管道
·管道是Unix中最古老的进程间通信的形式
·我们把从一个进程链接到另一个进程的一个数据流称为一个“管道”
2、匿名管道
int pipe(int fd[2])
参数
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
站在文件描述符的角度,理解管道
实例代码
#include <iostream>
#include <assert.h>
#include <unistd.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
//1、创建管道
int pipefd[2] = {0}; //pipefd[0]:读端,pipefd[1]:写端
int n = pipe(pipefd);
assert(n != -1);
(void)n;
//2、创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
//子进程
//3、构建单向通信的信道,父进程写入,子进程读取
//3.1关闭子进程不需要的fd
close(pipefd[1]);
char buffer[1024];
while(true)
{
ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);
if(s > 0)
{
buffer[s] = 0;
cout <<"chile get a message[" << getpid() << "] Father# " << buffer << endl;
}
}
exit(0);
}
//父进程
//3、构建单向通信的信道
//3.1关闭父进程不需要的fd
close(pipefd[0]);
string message = "我是父进程,我正在给你发消息";
int count = 0;
char send_buffer[1024];
while(true)
{
//3.2构建一个变化的字符串
snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);
//3.3写入
write(pipefd[1], send_buffer, strlen(send_buffer));
//3.4故意sleep
sleep(1);
}
pid_t ret = waitpid(id, nullptr, 0);
assert(ret < 0);
(void)ret;
close(pipefd[1]);
return 0;
}
总结管道的特点
·管道是用来进行具有血缘关系的进程进行进程间通信——常用于父子进程
·管道具有通过让进程间协同,提供了访问控制
·管道提供的是面向流式的通信服务
·管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的
·管道是单向通信的,就是半双工的一种特殊情况
3、管道读写规则
·当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN
·当管道满的时候
O_NONBLOCK disable:write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
·如果所有管道写段对应的文件描述符被关闭,则read返回0
·如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
·当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性
·当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性
4、管道特点
·只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可以应用该管道。
·一般而言,进程退出,管道释放,所以管道的生命周期随进程
·一般而言,内核会对管道操作进行同步与互斥
·管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
二、命名管道
·管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信
·如果我们想在不相关的进程之间交换雏菊,可以使用FIFO文件来做这项工作,它被称之为命名管道。
·命名管道是一种特殊类型的文件,管道文件可以被打开,但不会将内存数据刷新到磁盘
1、匿名管道与命名管道的区别
·匿名管道由pipe函数创建并打开
·命名管道由mkfifo函数创建,打开用open
·命名管道与匿名管道之间唯一的区别在它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义
2、实例代码
客户端向服务端发送消息
#ifndef _COMM_H_
#define _COMM_H_
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"
using namespace std;
#define MODE 0666
#define SIZE 128
string ipcPath = "./fifo.ipc";
#endif
#include "comm.hpp"
#include <sys/wait.h>
static void getMessage(int fd)
{
char buffer[SIZE];
while(true)
{
memset(buffer, '\0', sizeof(buffer));
ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
if(s > 0)
{
cout << "[" << getpid() << "]" << "client say: " << buffer << endl;
}
else if(s == 0)
{
cerr << "[" << getpid() << "]" << "read end of file, client quit, sever quit too!" << endl;
break;
}
else
{
perror("read");
break;
}
}
}
int main()
{
//创建管道文件
if(mkfifo(ipcPath.c_str(), MODE) < 0)
{
perror("mkfifo");
exit(1);
}
Log("创建管道文件成功", Debug) << " step 1" << endl;
//正常的文件操作
int fd = open(ipcPath.c_str(), O_RDONLY);
if(fd < 0)
{
perror("open");
exit(2);
}
Log("打开管道文件成功", Debug) << " step 2" << endl;
int num = 3;
for(int i = 0; i < num; i++)
{
pid_t id = fork();
if(id == 0)
{
//正常通信的代码
getMessage(fd);
exit(1);
}
}
for(int i = 0; i < num; i++)
{
waitpid(-1, nullptr, 0);
}
//关闭文件
close(fd);
Log("删除管道文件成功", Debug) << " step 3" << endl;
unlink(ipcPath.c_str()); //通信完毕就删除文件
Log("删除管道文件成功", Debug) << " step 4" << endl;
return 0;
}
#include "comm.hpp"
int main()
{
//获取管道文件
int fd = open(ipcPath.c_str(), O_WRONLY);
if(fd < 0)
{
perror("open");
exit(1);
}
//ipc过程
string buffer;
while(true)
{
cout << "Please Enter Message Line :>";
std::getline(std::cin, buffer);
if(buffer.compare("quit") == 0)
break;
write(fd, buffer.c_str(), buffer.size());
}
close(fd);
return 0;
}
三、共享内存
共享内存位于堆栈之间
共享内存函数
1、shmget函数
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码,失败返回-1
2、shmat函数
功能:将共享内存段链接到进程地址空间
原型:
void* shmat(int shmid, const void* shmaddr, int shmflg);
参数:
shmid:共享内存标识
shmaddr:指定链接的地址
shmflg:它的两个肯能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节,失败返回-1
3、shmdt函数
功能:将共享内存段与当前进程脱离
原型:
int shmdt(const void* shmaddr);
参数:
shmaddr:由shmat所返回的指针
返回值:成功返回0,失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
4、shmctl函数
功能:用于控制共享内存
原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0,失败返回-1