介绍管道知识之前我们先要引入一个概念-----进程通信
这里引用其他博主的文章做介绍,仅供学习,侵删-
原文链接
1.父进程创建管道,对同一文件分别以读&写方式打开
2. 父进程fork创建子进程
3. 因为管道是一个只能单向通信的信道,父子进程需要关闭对应读写端,至于谁关闭谁,取决于通信方向。
#include
int pipe(int pipefd[2]);
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
//1.创建管道
int pipefd[2] = {0};
int n = pipe(pipefd); //失败返回-1
assert(n != -1); //只在debug下有效
(void)n; //仅此证明n被使用过
#ifdef DEBUG
cout<< "pipefd[0]" << pipefd[0] << endl; //3
cout<< "pipefd[1]" << pipefd[1] << endl; //4
#endif
//2.创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
//子进程
//3. 构建单向通信的信道
//3.1 子进程关闭写端[1]
close(pipefd[1]);
exit(0);
}
//父进程
//父进程关闭读端[0]
close(pipefd[0]);
return 0;
}
在此基础上,我们就要进行通信了,实际上就是对某个文件进行写入,因为管道也是文件,下面提提前查看要用到的函数
#include
ssize_t write(int fd, const void *buf, size_t count);
#include
ssize_t read(int fd, void *buf, size_t count);
返回值:
- 返回写入的字节数
- 零表示未写入任何内容,这里意味着对端进程关闭文件描述符
#include
unsigned int sleep(unsigned int seconds);
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
//1.创建管道
int pipefd[2] = {0};
int n = pipe(pipefd); //失败返回-1
assert(n != -1); //只在debug下有效
(void)n; //仅此证明n被使用过
#ifdef DEBUG
cout<< "pipefd[0]" << pipefd[0] << endl; //3
cout<< "pipefd[1]" << pipefd[1] << endl; //4
#endif
//2.创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
//子进程 - 读
//3. 构建单向通信的信道
//3.1 子进程关闭写端[1]
close(pipefd[1]);
char buffer[1024];
while(1)
{
size_t s = read(pipefd[0], buffer, sizeof(buffer)-1);
if(s > 0)
{
buffer[s] = 0;//因为read是系统调用,没有/0,此处给加上
cout<<"child get a message["<< getpid() << "] 爸爸对你说" << buffer << endl;
}
}
//close(pipefd[0]);
exit(0);
}
//父进程 - 写
//父进程关闭读端[0]
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);
count++;
//3.3写入
write(pipefd[1], send_buffer, strlen(send_buffer));//此处strlen不能+1
//3.4 故意sleep
sleep(1);
}
pid_t ret = waitpid(id, nullptr, 0);
assert(ret != -1);
(void)ret;
return 0;
}
为什么不定义一个全局的buffer来进行通信呢?
之前父子进程同时向显示器中写入的时候,二者会互斥 —— 缺乏访问控制
而对于管道进行读取的时候,父进程如果写的慢,子进程就会等待读取 —— 这就是说明管道具有访问控制(是自带的)
父进程疯狂的进行写入,子进程隔10秒才读取,子进程会把这10秒内父进程写入的所有数据都一次性的打印出来!
代码如非就是在父进程添加了打印conut,子进程sleep(10),可以自行的在test代码上添加
父进程写了1220次,子进程一次就给你读完了,读写之间没有关系,这就叫做流式的服务。
也就是管道是面向字节流的,也就是只有字节的概念,究竟读成什么样也无法保证,甚至可能读出乱码,所以父子进程通信也是需要制定协议的,但这个我们网络再细说。。
管道没有数据的时候,读端必须等待:父进程每隔2秒才进行写入,子进程疯狂的读取
父进程写入10秒,后把写端fd关闭,读端会怎么样?
写入的一方,fd没有关闭,如果有数据就读,没有数据就等
写入的一方,fd关闭了,读取的一方,read会返回0,表示读到了文件结尾,退出读端
父进程写入10秒,后把写端fd关闭,读端会怎么样?
写入的一方,fd没有关闭,如果有数据就读,没有数据就等
写入的一方,fd关闭了,读取的一方,read会返回0,表示读到了文件结尾,退出读端
由此可知,当发生情况四时,操作系统向子进程发送的是SIGPIPE信号将子进程终止的。
管道的容量是有限的,如果管道已满,那么写端将阻塞或失败,那么管道的最大容量是多少呢?
为了解决匿名管道只能在父子之间通信,我们引入命名管道,可以在任意不相关进程进行通信
多个进程打开同一个文件,OS只会创建一个struct_file
命名管道就是一种特殊类型的文件(可以被打开,但不会将数据刷新进磁盘),两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了。
命名管道就是通过唯一路径/文件名的方式定位唯一磁盘文件的
ps:命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像(所以有名字),但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。
mkfifo (named pipes)