如果上天开了眼
请多给我点蓝调
多给我点沙锤
多给我点甲壳
让我吃鸡!
星元自动机,新的版本之神
给宁磕一个
完蛋
你说这不是问题吗
我这篇文章从我写开始,到写完
炉石都换赛季了!!!!!
伙伴没了我心碎
#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include"Task.hpp"
// void work(int rfd)
// {
// while(true)
// {
// int command = 0;
// int n = read(rfd, &command,sizeof(command));
// if(n == sizeof(int))
// {
// std::cout << "pid is:" << getpid() << "handler task" << std::endl;
// ExcuteTask(command);
// }
// else if(n == 0)
// {
// std::cout<<"Pipe closed"<<std::endl;
// break;
// }
// else
// {
// perror("read");
// break;
// }
// }
// }
//master
class Channel
{
public:
Channel(int wfd, pid_t id, const std::string &name)
:_wfd(wfd),_subprocessid(id),_name(name)
{}
int Getfd()const
{
return _wfd;
}
pid_t GetProcessId()const
{
return _subprocessid;
}
std::string GetName()const
{
return _name;
}
void CloseChannel()
{
close(_wfd);
}
void Wait()
{
pid_t rid = waitpid(_subprocessid,nullptr,0);
if(rid > 0)
{
std::cout << "wait " <<rid << "success" << std::endl;
}
}
~Channel()
{
}
private:
int _wfd;
pid_t _subprocessid;
std::string _name;
};
void CreateChannelAndSub(std::vector<Channel>* channels,int num1,task_t task)
{
for(int i = 0; i < num1; i++)
{
//创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
if(n < 0)
{
perror("pipe");
exit(1);
}
//创建紫禁城
pid_t id = fork();
if(id < 0)
{
perror("fork");
exit(1);
}
if(id == 0)
{
if(!channels->empty())
{
//第二次之后创建的管道
for(auto &channel : *channels)
{
channel.CloseChannel();
}
}
//child
close(pipefd[1]);
dup2(pipefd[0],0);
task();
close(pipefd[0]);
exit(0);
}
//父进程
close(pipefd[0]);
//构建名字
std::string channel_name = "Channel " + std::to_string(i);
channels->push_back(Channel(pipefd[1],id,channel_name));
//close(pipefd[1]);
}
}
int NextChannel(int channelnum)
{
static int next = 0;
int channel = next;
next++;
next %= channelnum;
return channel;
}
void SendTaskCommand(const Channel &channel,int taskcommand)
{
size_t n = write(channel.Getfd(),&taskcommand,sizeof(taskcommand));
if(n != sizeof(taskcommand))
{
perror("write");
}
}
void CtrlProcessOnce(std::vector<Channel> &channels)
{
sleep(1);
//选择任务
int taskcommand = Select();
//选择信道和进程
int channel_index = NextChannel(channels.size());
//发送任务
SendTaskCommand(channels[channel_index],taskcommand);
std::cout << "taskcommand:" << taskcommand << " channel:"\
<<channels[channel_index].GetName() << " sub process:"\
<< channels[channel_index].GetProcessId() << std::endl;
}
//通过channel来控制紫禁城
void CtrlProcess(std::vector<Channel> &channels,int times = -1)
{
if(times > 0)
{
while(times--)
{
CtrlProcessOnce(channels);
}
}
else
{
while(true)
{
CtrlProcessOnce(channels);
}
}
}
//回收管道和子进程
void CleanUpChannels(std::vector<Channel> &channels)
{
// for(auto &channel : channels)
// {
// channel.CloseChannel();
// }
for(auto &channel : channels)
{
channel.Wait();
}
}
// ./processpool 5
int main(int argc,char* argv[])
{
if(argc!=2)
{
std::cerr<<"Usage:"<<argv[0]<<"processnum"<<std::endl;
return 1;
}
int num = std::stoi(argv[1]);
LoadTask();
std::vector<Channel> channels;
//创建信道和子进程
CreateChannelAndSub(&channels,num,work);
//通过channel控制子进程
CtrlProcess(channels,10);
CleanUpChannels(channels);
// for(auto &channel : channels)
// {
// std::cout << " ---------------------- " <<std::endl;
// std::cout << channel.GetName() << std::endl;
// std::cout << channel.Getfd() << std::endl;
// std::cout << channel.GetProcessId() << std::endl;
// }
// sleep(100);
return 0;
}
青春版Shell中添加管道实现
管道读写规则
当没有数据可读时
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将不再保证写入的原子性
管道特点
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信
一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道
管道提供流式服务
进程退出,管道释放,所以管道的生命周期随进程
内核会对管道操作进行同步与互斥 管道是半双工的,数据只能向一个方向流动
需要双方通信时,需要建立起两个管道
命名管道
原理
有进程、磁盘上有文件,进程要打开对应的文件描述符表struct files_struct
里面包含数组struct file* fd_array[ ]
struct file里面要有属性集合和操作集合
通过文件内核级的缓冲区向磁盘文件中写数据
另一个进程要打同一文件,要有对应的PCB和文件描述符表,有相对应的文件对象,新文件的属性集操作集文件内核缓冲区都差不多,没必要再创建一份,操作系统不会做浪费时间浪费空间的事
至此两份进程可以看到同一文件了
怎么保证两个毫不相关的进程打开的是同一文件呢?
每一个文件都有文件路径(这个文件路径具有唯一性)
这就是命名管道,依旧是内存级别的进行通信的方案
文件需要是特殊文件,文件打开后不会将数据刷新到磁盘,而是在内存级别进行文件通信
通过路径标识保证唯一性的叫命名管道
代码
这是个接口,可以制作一个FIFO
使用呢是这样用:
mkfifo myfifo
可以这样进行读写:
如果想让左侧的终端不断向右侧写入则可以:
while :;do sleep 1;echo "hello named pipe"; done >> myfifo
那怎样让我们写的进程实行代码级别的通信呢?
还得是这个库函数
这是一个unlink:
可以删除指定目录下的文件
这是两个接口:创建一个管道、移除一个管道
namedPiped.hpp:
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<cerrno>
#include<cstdio>
#include<unistd.h>
#include<string>
const std::string comm_path = "./myfifo";
int CreateNamedPipe(const std::string &path)
{
int res = mkfifo(path.c_str(),0666);
if(res != 0)
{
perror("mkfifo");
}
return res;
}
int RemoveNamedPipe(const std::string &path)
{
int res = unlink(path.c_str());
if(res != 0)
{
perror("unlink");
}
return res;
}
用cpp对代码封装:
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<cerrno>
#include<cstdio>
#include<unistd.h>
#include<string>
const std::string comm_path = "./myfifo";
class NamePiped
{
public:
NamePiped(const std::string &path):_fifo_path(path)
{
int res = mkfifo(path.c_str(),0666);
if(res != 0)
{
perror("mkfifo");
}
}
~NamePiped()
{
int res = unlink(_fifo_path.c_str());
if(res != 0)
{
perror("unlink");
}
}
private:
const std::string _fifo_path;
};
相应的我们要对其进行身份的识别,是创建者才需要执行对应的操作:
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<cerrno>
#include<cstdio>
#include<unistd.h>
#include<string>
#define Creater 1
#define User 2
const std::string comm_path = "./myfifo";
class NamePiped
{
public:
NamePiped(const std::string &path,int who):_fifo_path(path), _id(who)
{
if(_id == Creater)
{
int res = mkfifo(path.c_str(),0666);
if(res != 0)
{
perror("mkfifo");
}
}
}
~NamePiped()
{
if(_id == Creater)
{
int res = unlink(_fifo_path.c_str());
if(res != 0)
{
perror("unlink");
}
}
}
private:
const std::string _fifo_path;
int _id;
};
创建管道为了读写:
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<cerrno>
#include<cstdio>
#include<unistd.h>
#include<string>
#include<fcntl.h>
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
const std::string comm_path = "./myfifo";
class NamePiped
{
private:
//打开文件的模式
bool OpenNamedPipe(int mode)
{
_fd = open(_fifo_path.c_str(),mode);
if(_fd < 0)
{
return 0;
}
return true;
}
public:
NamePiped(const std::string &path,int who)
:_fifo_path(path), _id(who),_fd(DefaultFd)
{
if(_id == Creater)
{
int res = mkfifo(path.c_str(),0666);
if(res != 0)
{
perror("mkfifo");
}
}
}
bool OpenForRead()
{
return OpenNamedPipe(Read);
}
bool OpenForWrite()
{
return OpenNamedPipe(Write);
}
~NamePiped()
{
if(_id == Creater)
{
int res = unlink(_fifo_path.c_str());
if(res != 0)
{
perror("unlink");
}
}
if(_fd != DefaultFd)
{
close(_fd);
}
}
private:
const std::string _fifo_path;
int _id;
int _fd;
};
于是服务器端和客户端的调用方式也出来了
client.cc:
#include"namedPiped.hpp"
int main()
{
NamePiped fifo(comm_path,User);
fifo.OpenForWrite();
return 0;
}
server.cc:
#include"namedPiped.hpp"
int main()
{
NamePiped fifo(comm_path,Creater);
fifo.OpenForRead();
return 0;
}
还要有相应的读写管道的操作:
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<cerrno>
#include<cstdio>
#include<unistd.h>
#include<string>
#include<fcntl.h>
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096
const std::string comm_path = "./myfifo";
class NamePiped
{
private:
//打开文件的模式
bool OpenNamedPipe(int mode)
{
_fd = open(_fifo_path.c_str(),mode);
if(_fd < 0)
{
return 0;
}
return true;
}
public:
NamePiped(const std::string &path,int who)
:_fifo_path(path), _id(who),_fd(DefaultFd)
{
if(_id == Creater)
{
int res = mkfifo(path.c_str(),0666);
if(res != 0)
{
perror("mkfifo");
}
}
}
bool OpenForRead()
{
return OpenNamedPipe(Read);
}
bool OpenForWrite()
{
return OpenNamedPipe(Write);
}
//输出:const &:const std::string &XXX
//输入:* std::string *
//输入输出:& std::string &
int ReadNamedPipe(std::string *out)
{
char buffer[BaseSize];
int n = read(_fd,buffer,sizeof(buffer));
if(n > 0)
{
//读取成功
buffer[n] = 0;
*out = buffer;
}
return n;
}
int WriteNamedPipe(const std::string &in)
{
return write(_fd,in.c_str(),in.size());
}
~NamePiped()
{
if(_id == Creater)
{
int res = unlink(_fifo_path.c_str());
if(res != 0)
{
perror("unlink");
}
}
if(_fd != DefaultFd)
{
close(_fd);
}
}
private:
const std::string _fifo_path;
int _id;
int _fd;
};
接下来就是实现服务器端和客户端进行通信:
client.cc:
#include"namedPiped.hpp"
int main()
{
NamePiped fifo(comm_path,User);
if(fifo.OpenForWrite())
{
std::cout << "Please Enteer -> ";
std::string message;
std::getline(std::cin,message); //从标准输入中获取信息到message中
fifo.WriteNamedPipe(message);
}
return 0;
}
server.cc:
#include"namedPiped.hpp"
int main()
{
NamePiped fifo(comm_path,Creater);
if(fifo.OpenForRead())
{
while(true)
{
std::string message;
int n = fifo.ReadNamedPipe(&message);
if(n > 0)
{
std::cout << "Client Say " << message << std::endl;
}
}
}
return 0;
}
对于读端而言,如果我们打开文件,但是写端还没来,会阻塞在open调用中,直到对方打开
命名管道是通过文件路径让不同进程看到同一份资源的~
下篇说共享内存捏