管道
管道:**管道本质是内核中的一块缓冲区,多个进程通过访问同一块缓冲区实现通信。**一般用于进程间通信。通过向管道里面读写,完成进程之间的通信。
包括匿名管道和命名管道
匿名管道:为具备血缘关系之间的进程通信
- a. 管道的4种情况
-
1.如果管道写端没有数据了,那么读端就必须等待
-
2.管道被写满了,就需要等待读取端读走数据,才能继续写
-
3.如果写端关闭,读端就会一直读取,直到读取到文件末尾.
-
4.如果读端关闭,写入就没有意义,就是异常写入,LINUX会发送信号SIGPIPE(13),终止目标进程
- b. 管道的5种特性
-
1. 匿名管道,建立一个管道文件,具备亲缘关系的进程可以进行通信
-
2. 匿名管道需要提供同步机制。能读取多少,一般就读取多少。
-
3. 匿名管道是面向字节流的
-
4. 管道也是文件,生命周期随着进程的结束就结束了。就像一直都打开的 ,0,1,2三个默认标准输入输出,进程退出后,会自动释放
-
5. 管道是单向通信的,半双工通信的特殊情况
写代码时可能存在的BUG:
如图,是一个父进程开管道连接到多个子进程。
我们发现,由于子进程会拷贝父进程的文件描述符表,使得后面子进程也有打开对应的文件描述符,使得在关闭的时候出现问题。也就是说,第n个子进程里面,其前面n-1个子进程的写端。原因就是创建子进程时,会拷贝父进程的文件描述符表
linux下创建匿名管道的系统调用
int pipe(int pipefd[2]);
返回值:0成功,-1失败
pipefd[2]:返回一个管道文件描述符数组,pipefd[0]是读端,pipefd[1]是写端。要进行读,就要关闭写端/要进行写,就要关闭读端。
实例代码:
#include <iostream>
#include <string>
#include <unistd.h>
#include <sstream>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>
#define MAX 100
using namespace std;
// a. 管道的4种情况
/// 正常情况下:1.如果管道写端没有数据了,那么读端就必须等待
/// 2.管道被写满了,就需要等待读取端读走数据,才能继续写
// 3.如果写端关闭,读端就会一直读取,直到读取到文件末尾.
// 4.如果读端关闭,写入就没有意义,就是异常写入,LINUX会发送信号SIGPIPE(13),终止目标进程
// b. 管道的5种特性
// 1. 匿名管道,建立一个管道文件,具备亲缘关系的进程可以进行通信
// 2. 匿名管道需要提供同步机制。能读取多少,一般就读取多少。
// 3. 匿名管道是面向字节流的
// 4. 管道也是文件,生命周期随着进程的结束就结束了。就像一直都打开的 ,0,1,2三个默认标准输入输出,进程退出后,会自动释放
// 5. 管道是单向通信的,半双工通信的特殊情况
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int pipefd[2] = {0};
assert(!pipe(pipefd)); //为0退出
cout << pipefd[0] << " " << pipefd[1] << endl;
//创建子进程
pid_t id = fork();
if (id < 0)
{
cerr << "fork" << endl;
}
if (id == 0)
{
// //父子间通信
// //child
// //关闭不需要的管道进行相应的读或者写操作
close(pipefd[0]);
for (int i = 0; i < 10; i++)
{
sleep(1);
stringstream msg;
msg << "Hello parent process,I am child,pid is" << getpid() << " cnt" << i << endl;
write(pipefd[1], msg.str().c_str(), msg.str().size());
}
cout << "--------child write---------------" << endl;
sleep(5);
// close(pipefd[1]);
// exit(0);
// pid_t id1 = fork();
// if(id1 >0) exit(0);
// //孙子进程
// //我们发现可以通信,切正好验证了对应的ppid是1,就是一个进程成了孤儿进程之后会被领养
// close(pipefd[0]);
// for(int i = 0;i<10;i++)
// {
// stringstream msg;
// msg<<"Hello parent process,I am grandson,pid is"<<getpid()<<" cnt"<<i<<",myppid is:"<<getppid()<<endl;
// write(pipefd[1],msg.str().c_str(),msg.str().size());
// sleep(1);
// }
close(pipefd[1]);
exit(0);
}
// Parent
close(pipefd[1]);
//关闭写端就可以进行读
// while (1)
// {
// sleep(1);
// char msg[MAX] = {0};
// int n = read(pipefd[0], msg, sizeof(msg) - 1);
// if (n > 0)
// {
// msg[n] = 0;
// cout << getpid() << ", n=" << n << "child say:" << msg << endl;
// }
// else
// {
// break;
// }
// }
sleep(2);
close(pipefd[0]);
sleep(2);
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid == id)
{
cout << getpid() << ","
<< "wait success,child exit sig:" << (status & 0x7f) << endl;
}
return 0;
}
下面是利用匿名管道实现的一种进程池:其中展现了匿名管道代码编写种可能出现的bug
.h
#pragma once
#include <bits/stdc++.h>
#include <unistd.h>
#include <functional>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>
using task_t = std::function<void()>;
void Download()
{
std::
cout
<< "I am a task to Download"
<< " hanlder : " << getpid() << std::endl;
}
void Printlog()
{
std::
cout
<< "I am a task to record log"
<< " hanlder : " << getpid() << std::endl;
}
void GetSeeds()
{
std::
cout
<< "I am a task to Get Seeds"
<< " hanlder : " << getpid() << std::endl;
}
class Init
{
const int g_download_code = 0;
const int g_PrintLog_code = 1;
const int g_GetSeeds_code = 2;
public:
std ::vector<task_t> tasks;
Init()
{
tasks.push_back(Download);
tasks.push_back(Printlog);
tasks.push_back(GetSeeds);
}
bool CheckSafe(int code)
{
if (code >= 0 && code < tasks.size())
{
return 1;
}
else
{
return 0;
}
}
void RunTask(int code)
{
return tasks[code]();
}
int SelectTask()
{
return rand() % tasks.size();
}
};
Init init;
.cpp
#include "Task_Process_Pool.hpp"
using namespace std;
const int num = 5;
static int CNT = 1;
struct channel
{
int _ctrlfd;
pid_t _workerId;
std::string _name;
public:
channel(int fd, pid_t id)
: _ctrlfd(fd), _workerId(id)
{
this->_name = "channel-" + to_string(CNT++);
}
};
void Work()
{
while (true)
{
int code;
int n = read(0, &code, sizeof(code));
if (n == 0)
{
break;
}
else if (n > 0)
{
if (!init.CheckSafe(code))
continue;
else
{
init.RunTask(code);
}
}
else
{
cerr << "read error" << endl;
}
}
cout << "my task is ended,pid is " << getpid() << endl;
}
void printDebug(const vector<channel> &c)
{
for (const auto &channel : c)
{
cout << channel._name << "," << channel._ctrlfd << ',' << channel._workerId << endl;
}
}
void PrintCloseFD(const vector<int> &FDs)
{
for (auto fd : FDs)
{
cout << "process:" << getpid() << "close the FD:" << fd << endl;
}
}
void CreatChannels(vector<channel> &channels)
{
//以下代码是由bug的,会导致文件描述符的错乱,导致子进程多余的文件描述符指向
//导致释放的时候出现问
// for (int i = 0; i < num; i++)
// {
// int pipfd[2];
// int n = pipe(pipfd);
// assert(n == 0);
// pid_t id = fork();
// assert(id != -1);
// if (id == 0)
// {
// // child
// close(pipfd[1]);
// //这里重定向,能够很方便直接从标准输入里面读取
// //可以直接读取0号
// dup2(pipfd[0], 0);
// Work();
// close(pipfd[0]);
// exit(0);
// }
// // father
// close(pipfd[0]);
// channels.push_back(channel(pipfd[1], id));
// }
//为了解决上述问题,写如下代码
vector<int> temp;
for (int i = 0; i < num; i++)
{
int pipfd[2];
int n = pipe(pipfd);
assert(n == 0);
pid_t id = fork();
assert(id != -1);
if (id == 0)
{
// child
if (!temp.empty())
{
for (auto fd : temp)
{
close(fd);
}
PrintCloseFD(temp);
}
close(pipfd[1]);
//这里重定向,能够很方便直接从标准输入里面读取
//可以直接读取0号
dup2(pipfd[0], 0);
Work();
close(pipfd[0]);
exit(0);
}
// father
close(pipfd[0]);
channels.push_back(channel(pipfd[1], id));
temp.push_back(pipfd[1]);
}
}
void SendCommand(const std::vector<channel> &channels, bool Isloop = true, int TaskNum = -1)
{
int pos = 0;
while (true)
{
int command = init.SelectTask();
const auto &c = channels[pos++];
pos %= channels.size();
// 3.发送任务
sleep(1);
cout << "send Command task " << command << " to" << c._name << " Worker is " << c._workerId << endl;
write(c._ctrlfd, &command, sizeof(command));
if (!Isloop)
{
TaskNum--;
if (TaskNum <= 0)
{
cout << "Send done\n";
break;
}
}
}
}
void ReleaseChannels(const vector<channel> &channels)
{
// version2也可以顺着解决这个问题
for (int i = num - 1; i >= 0; i--)
{
close(channels[i]._ctrlfd);
waitpid(channels[i]._workerId, nullptr, 0);
}
//解决由于子进程反复创建,和文件描述符创建的问题,会导致后面的子进程会有写端指向之前的写端
// version1 ,
// for (const auto &c : channels)
// {
// close(c._ctrlfd);
// }
// for (const auto &channel : channels)
// {
// pid_t rid = waitpid(channel._workerId, nullptr, 0);
// if (rid = channel._workerId)
// {
// cout << "wait child success : " << channel._workerId << endl;
// }
// }
}
int main()
{
vector<channel> channels;
CreatChannels(channels);
//父进程处理任务
// 1.选择任务
// 2.选择信道
SendCommand(channels, false, 2);
return 0;
}
命名管道
本质:linux下指定对应的文件来作为管道去进行进程间的通信。
系统调用:
int mkfifo(const char *pathname, mode_t mode);
返回值:成功返回0,失败返回-1
mode:指打开文件的权限,一般来说要减去umask
pathname:打开管道文件的地址
使用实例代码:
.h
#include <bits/stdc++.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
#define FILENAME "./fifo.tmp"
server
#include "Common.h"
bool MakeFifo()
{
int n = mkfifo(FILENAME, 0666);
if (n < 0)
{
cerr << "errno:" << errno << ",errstring" << strerror(errno) << endl;
return 0;
}
cout << "mkfifo success..." << endl;
return 1;
}
int main()
{
int rfd = open(FILENAME, O_RDONLY);
if (rfd < 0)
{
cerr << "errno" << errno << ",ErrnoString:" << strerror(errno) << endl;
if (MakeFifo())
{
cout << "The last Fifo not exist is solved\n";
rfd = open(FILENAME, O_RDONLY);
}
else
return 1;
}
cout << "open fifo success..." << endl;
while (1)
{
char buffer[1024];
ssize_t s = read(rfd, buffer, sizeof(buffer) - 1);
if (s > 0)
{
cout << "Client Say:" << buffer << endl;
buffer[s] = 0;
}
// else if (s == 0)
// {
// }
// else
// {
// }
}
close(rfd);
cout << "close fifo success..." << endl;
return 0;
}
client
#include "Common.h"
int main()
{
int wfd = open(FILENAME, O_RDWR);
if (wfd < 0)
{
cerr << "errno:" << errno << ",errstring" << strerror(errno) << endl;
return 1;
}
cout<<"open success\n";
string msg;
while (1)
{
cout << "Please Enter:\n";
getline(cin, msg);
ssize_t s = write(wfd, msg.c_str(), msg.size());
if (s < 0)
{
cerr << "errno:" << errno << ",errstring" << strerror(errno) << endl;
break;
}
}
close(wfd);
return 0;
}