一、管道概念
管道是Linux中的最古老的通信方式,我们把一个进程链接到另一个进程的一个数据流称为一个"管道"。

linux中指令 | 这个被称为管道符。
zxw@hcss-ecs-cc58:~/lesson1$ ll
total 56
drwxrwxrwx 2 root root 4096 Feb 13 18:10 ./
drwxr-x--- 6 zxw zxw 4096 Feb 13 17:05 ../
-rw-rw-r-- 1 zxw zxw 77 Feb 12 19:36 Makefile
-rwxrwxr-x 1 zxw zxw 17720 Feb 13 18:10 my*
-rwxrwxr-x 1 zxw zxw 16720 Feb 12 19:37 mypipe*
-rw-rw-r-- 1 zxw zxw 1447 Feb 13 18:10 mypipe.cxx
-rw-rw-r-- 1 zxw zxw 0 Feb 12 19:01 test.c
zxw@hcss-ecs-cc58:~/lesson1$ ls | head -3
Makefile
my
mypipe
通过管道符 | 创建的管道是匿名管道,用完了就会被自动销毁。需要注意的是,匿名管道只能在具有亲缘关系(父子进程,兄弟进程,爷孙进程)的进程间使用。也就是说,匿名管道只能用于亲缘进程之间的通信。
二、文件描述符角度-理解管道

三、内核角度-理解管道

四、创建管道
#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
using namespace std;
int main()
{
//1. 创建管道
int fds[2] = {0};
int n = pipe(fds);//fds:输出型参数
if (n != 0)
{
cerr << "pipe error" << endl;
return 1;
}
//2. 创建子进程
pid_t id = fork();
if(id < 0)
{
cerr << "fork error" << endl;
return 2;
}
else if(id == 0)
{
// 子进程 --> 写
// 3.关闭不需要的fd,关闭read
close(fds[0]);
int cnt = 0;
int total = 0;
while(true)
{
string message = "hello world ";
message += to_string(getpid());
message += ", ";
message += to_string(cnt);
cnt++;
//fds[1]
::write(fds[1],message.c_str(),message.size());
sleep(1);
}
exit(0);
}
else
{
// 父进程 --> 读
// 3. 关闭不需要的fd,关闭write
close(fds[1]);
char buffer[1024];
while(true)
{
ssize_t n = ::read(fds[0],buffer,1024);
if(n > 0)
{
buffer[n] = 0;
cout << " child->father,message: " << buffer << endl;
}
else
{
cout << "error" << endl;
}
sleep(1);
}
pid_t rid = waitpid(id,nullptr,0);
}
return 0;
}
五、管道读写规则
管道四大现象:
1.管道为空&&管道正常,read数据会阻塞。[read是一个系统调用]
2.管道为满&&管道正常,write会阻塞。[write是一个系统调用]
3.管道写端关闭&&读端继续,读端读到0(read结束后返回值),表示读到文件结尾
4.管道写端正常&&读端关闭,OS会直接杀掉写入的进程!
操作系统会给目标进程发送信号:13)SIGPIPE ---> 验证一下
int main()
{
//1. 创建管道
int fds[2] = {0};
int n = pipe(fds);//fds:输出型参数
if (n != 0)
{
cerr << "pipe error" << endl;
return 1;
}
//2. 创建子进程
pid_t id = fork();
if(id < 0)
{
cerr << "fork error" << endl;
return 2;
}
else if(id == 0)
{
// 子进程 --> 写
// 3.关闭不需要的fd,关闭read
close(fds[0]);
int cnt = 0;
int total = 0;
while(true)
{
string message = "h";
cnt++;
//fds[1]
total += ::write(fds[1],message.c_str(),message.size());
// cnt++;
cout << "total: " << total << endl;
sleep(2);
}
exit(0);
}
else
{
// 父进程 --> 读
// 3. 关闭不需要的fd,关闭write
close(fds[1]);
char buffer[1024];
while(true)
{
sleep(1);
ssize_t n = ::read(fds[0],buffer,1024);
if(n > 0)
{
buffer[n] = 0;
cout << " child->father,message: " << buffer << endl;
}
else if(n == 0)
{
cout << "n:" << n << endl;
cout << "child quit ??? me too" << endl;
break;
}
close(fds[0]);
break;
cout << endl;
}
int status = 0;
pid_t rid = waitpid(id,&status,0);
cout << "father wait chid success: " << rid << " exit code "<<
((status<<8)&0xFF) << ", exit sig: " << (status & 0x7F) << std::endl;
}
return 0;
}
zxw@hcss-ecs-cc58:~/lesson1$ ./my
total: 1
child->father,message: h
father wait chid success: 35943 exit code 0, exit sig: 13
zxw@hcss-ecs-cc58:~/lesson1$ g++ -o my mypipe.cxx -std=c++11
tips:
管道是有固定大小的,在不同内核里,大小可能有差别。在我的这台机器中管道当前环境下管道的大小是64KB
六、管道通信的场景
进程池

Makefile
BIN=processpool
CC=g++
FLAGS=-c -Wall -std=c++11
LDFLAGS=-o
# SRC=$(shell ls *.cc)
# wildcard,Makefile中函数 == 上面
SRC=$(wildcard *.cc)
OBJ=$(SRC:.cc=.o)
$(BIN):$(OBJ)
$(CC) $(LDFLAGS) $@ $^
%.o:%.cc
$(CC) $(FLAGS) $<
.PHONY:clean
clean:
rm -f $(BIN) $(OBJ)
.PHONY:test
test:
@echo $(SRC)
@echo $(OBJ)
ProcessPool.hpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstdio>
#include <vector>
#include <functional>
#include <sys/wait.h>
#include "Task.hpp"
#include "Channel.hpp"
using work_t = function<void()>;
using namespace std;
enum
{
OK = 0,
UsageError,
PipeError,
ForkError,
};
class ProcessPool
{
public:
ProcessPool(int n, work_t w)
: num(n), work(w)
{
}
// channels: 输出型参数
// work_t work:回调
int InitProcessPool()
{
// 2. 创建
for (int i = 0; i < num; i++)
{
// 1. 先有管道
int pipefd[2] = {0};
int n = pipe(pipefd);
if (n < 0)
return PipeError;
pid_t id = fork();
if (id < 0)
return ForkError;
// 建立通讯管道
// child
if (id == 0)
{
// // version 3
// // 关闭历史write_fd_
// for(auto &e : channels)
// {
// e.Close();
// }
// read
::close(pipefd[1]);
dup2(pipefd[0], 0);
work();
cout << "child->" <<getpid() <<"done ........Task" << endl;
::exit(0); // 执行完就say bye
}
// parent
// write
::close(pipefd[0]);
channels.emplace_back(pipefd[1], id);
// Channel ch(pipefd[1],id);
// channels.push_back(ch);
}
return OK;
}
void DispatchTask()
{
int who = 0;
// 2. 派发任务
int num = 10;
while (num--)
{
// a.选择一个任务
int task = tm.SelectTask();
// b.选择一个子进程channel
Channel &curr = channels[who++];
who %= channels.size();
cout << "-------------------------------------" << endl;
cout << "send this " << task << " to " <<curr.Name() << ", 任务还剩: " << num << endl;
cout << "-------------------------------------" << endl;
cout << " " << endl;
cout << " " << endl;
cout << " " << endl;
// c.派发任务
curr.Send(task);
sleep(1);
}
}
void CleanProcessPool()
{
// version 3
// in the InitProcessPool
// 为什么不能关掉一个读端,关一个进程
// version 2 --- true
// 管道写端正常&&读端关闭,OS会直接杀掉写入的进程!
for (int i = channels.size() - 1; i >= 0; i--)
{
channels[i].Close();
pid_t rid = ::waitpid(channels[i].who(), nullptr, 0);
if (rid > 0)
{
cout << "child " << rid << "wait...success " << "quit..." << endl;
}
}
// version 2 --- false
// for (auto &c : channels)
// {
// c.Close();
// pid_t rid = ::waitpid(c.who(), nullptr, 0);
// if (rid > 0)
// {
// cout << "child " << rid << "wait...success " << "quit..." << endl;
// }
// }
// version 1 ---
// for (auto &c : channels)
// {
// c.Close();
// }
// for (auto &e : channels)
// {
// pid_t rid = ::waitpid(e.who(), nullptr, 0);
// if (rid > 0)
// {
// cout << "child " << rid << "wait...success " << "quit..." << endl;
// }
// }
}
private:
vector<Channel> channels;
int num;
work_t work;
};
Task.hpp
#pragma once
#include <iostream>
#include <unordered_map>
#include <functional>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
using task_t = function<void()>;
void DownLoad()
{
cout << "-------------------------------------" << endl;
cout << getpid() <<" Working DownLoad Task" << endl;
}
void Log()
{
cout << "-------------------------------------" << endl;
cout << getpid() << " Working Log Task" << endl;
}
static int number = 0;
class TaskManger
{
public:
TaskManger()
{
InsertTask(DownLoad);
InsertTask(Log);
}
void InsertTask(task_t t)
{
tasks[number++] = t;
}
int SelectTask()
{
return rand() % number;
}
void Excute(int number)
{
if (tasks.find(number) == tasks.end())
return;
tasks[number]();
}
~TaskManger()
{
}
private:
unordered_map<int, task_t> tasks;
};
TaskManger tm;
void Worker()
{
while (true)
{
int cmd = 0;
int n = ::read(cmd, &cmd, sizeof(cmd));
if (n == sizeof(cmd))
{
tm.Excute(cmd);
}
else if (n == 0)
{
cout << getpid() << "->pid read nothing " << endl;
break;
}
else
{
}
}
}
Channel.hpp
#ifndef __CHANNEL_HPP__
#define __CHANNEL_HPP__
#include <iostream>
#include <string>
#include <unistd.h>
using namespace std;
class Channel
{
public:
Channel(int write_fd, pid_t who)
: _write_fd(write_fd), _who(who)
{
_name = "Channel-" + to_string(_write_fd) + "-" + to_string(who);
}
string Name()
{
return _name;
}
void Send(int cmd)
{
::write(_write_fd,&cmd,sizeof(cmd));
}
void Close()
{
::close(_write_fd);
}
~Channel()
{
}
int who()
{
return _who;
}
private:
int _write_fd;
string _name;
pid_t _who;
};
#endif
#include "ProcessPool.hpp"
#include "Task.hpp"
void Usage(string proc)
{
cout << "Usage" << proc << "prcocess-numm" << endl;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return UsageError;
}
int num = stoi(argv[1]);
ProcessPool *pp = new ProcessPool(num,Worker);
pp->InitProcessPool();
pp->DispatchTask();
pp->CleanProcessPool();
// vector<Channel> channels;
// //1. 初始化进程池
// InitProcessPool(num,channels,Worker);
// //2. 派发任务
// DispatchTask(channels);
// //3. 退出进程池
// CleanProcessPool(channels);
return OK;
}
这里主要流程就是
1.初始化进程池
2.派发任务
3.退出进程池
这里注意一点就是进程池的初始化

当我们创建一个子进程的时候,我们子进程会继承父进程的struct files_struct,fd[0] = 3 指向read
fd[1] = 4 指向write,我们关闭父进程的fd[0](read),关闭子进程的fd[4](write)。
当我们又创建一个子进程,父进程的fd[0] = 3 指向read,fd[1] = 5 指向write,我们子进程会继承父进程的struct files_struct,而这个struct files_struct继承了父进程的struct files_struct,其中 fd = 4指向了上一个管道,我们在进行关闭的时候发现就第一个管道会有两个read。如果创建3个管道4个管道以此类推。会发现第一个管道read端会随进程的增加而增加,第二管道相比第一个管道read端减少一个。