农民 发表于 2024-10-25 03:51:59

历程间通信大总结Linux

目次
历程间通信介绍
历程间通信目的
历程间通信发展
历程间通信分类
管道
System V IPC
POSIX IPC
管道
什么是管道
匿名管道
用fork来共享管道原理
站在文件描述符角度-深度理解管道

管道读写规则
管道特点
命名管道
创建一个命名管道
匿名管道与命名管道的区别
命名管道的打开规则

system V共享内存
共享内存示意图
共享内存数据布局
共享内存函数
shmget函数
shmat函数
shmdt函数
shmctl函数
自己总结
共享内存
​编辑

   

[*]历程间通信介绍
[*]管道
[*]消息队列
[*]共享内存
[*]信号量
历程间通信介绍

历程间通信目的

   

[*]数据传输:一个历程必要将它的数据发送给另一个历程
[*]资源共享:多个历程之间共享同样的资源。
[*]关照事件:一个历程必要向另一个或一组历程发送消息,关照它(它们)发生了某种事件(如历程终止 时要关照父历程)。
[*]历程控制:有些历程希望完全控制另一个历程的执行(如Debug历程),此时控制历程希望可以或许拦截另 一个历程的全部陷入和异常,并可以或许及时知道它的状态改变。
历程间通信发展

   

[*]管道
[*]System V历程间通信
[*]POSIX历程间通信
历程间通信分类

管道

   

[*]匿名管道pipe
[*]命名管道
System V IPC

   

[*]System V 消息队列
[*]System V 共享内存
[*]System V 信号量
POSIX IPC

   

[*]消息队列
[*]共享内存
[*]信号量
[*]互斥量
[*]条件变量
[*]读写锁
管道

什么是管道

   

[*]管道是Unix中最古老的历程间通信的形式。
[*]我们把从一个历程毗连到另一个历程的一个数据流称为一个“管道”
https://i-blog.csdnimg.cn/direct/bae1f503be044114b37ba00c957e91ef.png
匿名管道

匿名管道 Linux-CSDN博客    详细看这里
#include <unistd.h>

功能:创建一无名管道
原型

int pipe(int fd);

参数

fd:文件描述符数组,其中fd表示读端, fd表示写端
返回值:成功返回0,失败返回错误代码 https://i-blog.csdnimg.cn/direct/5e17f56c4f3749ab96d455811992348a.png

管道
首先自己要用用户层缓冲区,还得把用户层缓冲区拷贝到管道里,(从键盘里输入数据到用户层缓冲区里面),然后用户层缓冲区通过体系调用(write)写到管道里,然后再通过read体系调用,被对方(读端)读取,就要从管道拷贝到读端,然后再表现到表现器上。
pipe创建一个管道
pipe的介绍https://i-blog.csdnimg.cn/direct/508c1018ac4246e9abf452ca6e20742e.png
1完成这件事:https://i-blog.csdnimg.cn/direct/04ec1a4f71864fbd82d7959a8e9ac36e.png
看图分析
https://i-blog.csdnimg.cn/direct/054d3d1487164e52b373b4cb4c2f588b.png
运行效果
https://i-blog.csdnimg.cn/direct/fbe8a521cba342479cbcfbae14359066.png
#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{
    //创建管道
    //先创建一个pipefd数组
    int pipefd;
    //用n接受一下,判断是否成功
    int n = pipe(pipefd);
    if(n<0) return 1;//创建失败了

    //创建成功
    //测试一下文件描述符是3和4
    cout<<"pipefd:"<<pipefd<<"pipefd:"<<pipefd<<endl;
   

    return 0;
}

2完成这件事:
https://i-blog.csdnimg.cn/direct/df704c13450d4b1a9b2ad5510bcd4263.png
创建一个子历程
https://i-blog.csdnimg.cn/direct/4a2daa7208dd438b8394aa63eb5927ca.png
pid_t id = fork();
    if(id < 0)return 2;//创建失败
    if(id == 0)//创建成功
    {
      //子进程

    }
    //父进程
让子历程写入,父历程读取
https://i-blog.csdnimg.cn/direct/e133ebd7c8fb4c0097dfe38530e188cc.png
要想让子历程历程写,就必要在历程中关闭读端
https://i-blog.csdnimg.cn/direct/463e5d68265747a6b7705673796d46cd.png
if(id == 0)//创建成功
{
   //子进程
   close(pipefd);
} 同理
https://i-blog.csdnimg.cn/direct/92ef35f4ae144f2dbd75381bac778758.png
//父进程
close(pipefd); 都用完竣事后,可以都关掉
https://i-blog.csdnimg.cn/direct/0834fbd3d84d447a9f2c886ab48a52fa.png
    if(id == 0)//创建成功
    {
      //子进程
      close(pipefd);
      //.....
      close(pipefd);
    }
    //父进程
    close(pipefd);
    //.....
    close(pipefd); IPC code,写通信代码
3这件事也完成了:
https://i-blog.csdnimg.cn/direct/920301abdade4888a8e1fb7a4be9984d.png
布局就有了


然后在pipefd这个管道里写,界说一个Writer函数ostream::write - C++ Reference (cplusplus.com)
istream::read - C++ Reference (cplusplus.com)
https://i-blog.csdnimg.cn/direct/1c3e0706233f48a6b3d366037cb35406.png
    if(id == 0)//创建成功
    {
      //子进程
      close(pipefd);
      //.....IPC code,写通信代码
      //在pipefd这个管道里写
      Writer(pipefd);


      close(pipefd);

      exit(0);//正常退出
    } 同理父历程的        
https://i-blog.csdnimg.cn/direct/ab853bb9b81c4089a6c204493598740f.png
    //父进程
    close(pipefd);
    //.....IPC code,写通信代码
    //在pipefd这个管道里写
    Reader(pipefd);


    close(pipefd); https://i-blog.csdnimg.cn/direct/cfd3d99eb611447b9acb626ff40ac6bc.png
//子进程
void Writer(int wfd)
{

}
//父进程
void Reader(int rfd)
{

} Writer
https://i-blog.csdnimg.cn/direct/84be68a7b3354d15a4c73a7b3ecb025a.png
//子进程
void Writer(int wfd)
{
    string s = "hello,I am child";
    pid_t self = getpid();
    int number = 0;

    char buffer;
    while(true)
    {
      buffer = 0;//字符串清空,只是为了提醒阅读代码的人,我把这个数组当字符串了

    }
}
用到snprintfsnprintf - C++ Reference (cplusplus.com)
介绍https://i-blog.csdnimg.cn/direct/d054e008896a4fa2b5eaf7e959ff9011.png
将s和self和number放进buffer
https://i-blog.csdnimg.cn/direct/a1372509a0c94ecda036882aefa3885b.png
char buffer;
    while(true)
    {
      buffer = 0;//字符串清空,只是为了提醒阅读代码的人,我把这个数组当字符串了
      snprintf(buffer,sizeof(buffer),"%s    pid:%d\n",s.c_str(),self);
      cout<< buffer <<endl;
      sleep(1);
    }; 用cout打印测试一下,打印成功分析写入buffer成功了

等待历程少不了,子历程exit后必要接纳
https://i-blog.csdnimg.cn/direct/b6fd3165246f48cc8c1f498c587afd02.png
//父进程
    close(pipefd);
    //.....IPC code,写通信代码
    //在pipefd这个管道里写
    Reader(pipefd);

    //等待进程缺少不了
    pid_t rid = waitpid(id,nullptr,0);
    if(rid < 0) return 3;//等待失败了

    close(pipefd);
怎样把消息发送/写入给父历程
用到了write
https://i-blog.csdnimg.cn/direct/dd98aa8019264ae0a3fbde06b60a00ee.png

用write写入管道(管道也是文件),用strlen,不用+1,不用管\0,因为C语言规定\0末端,和文件没有关系,wfd写入管道
https://i-blog.csdnimg.cn/direct/d8931bb680914882b25db2777cda88c6.png
//子进程
void Writer(int wfd)
{
    string s = "hello,I am child";
    pid_t self = getpid();
    int number = 0;
    char buffer;
    while(true)
    {
      buffer = 0;//字符串清空,只是为了提醒阅读代码的人,我把这个数组当字符串了
      snprintf(buffer,sizeof(buffer),"%s    pid:%d%d\n",s.c_str(),self,number++);
      //用write写入管道(管道也是文件),用strlen,不用+1,不用管\0,因为C语言规定\0结尾,和文件没有关系,wfd写入管道
      write(wfd,buffer,strlen(buffer));
      //cout<< buffer <<endl;
      sleep(1);
    };
} 父历程该怎么读取呢
用到了read,fd是文件描述符,从特定的文件描述符里读取,放在这个buf里,buf的长度是count
https://i-blog.csdnimg.cn/direct/37502e652079464f85cadf5554c19c3d.png
这里就必要思量到\0,因为buffer中必要\0
https://i-blog.csdnimg.cn/direct/82df80577eca459cb017a488eea93fb0.png
//父进程
void Reader(int rfd)
{
    char buffer;
    while(true)
    {
      buffer = 0;
                                    //用sizeof是为了留个空间放\0
      ssize_t n = read(rfd, buffer, sizeof(buffer));//sizeof!=strlen
      if(n > 0)
      {
            //添加\0,因为要放在buffer数组中读取
            buffer=0;
            cout << "father get a message[" << getpid() <<"]"<< buffer <<endl;
      }
    }
} 运行效果
也会发现:为什么子历程sleep,父历程不sleep,父历程照旧会跟着子历程sleep,因为父子历程是要协同的
https://i-blog.csdnimg.cn/direct/6ec70a333f294e2f8ed04f6eabd40aa1.png
管道本质
通信是为了更好的发送变革的数据,管道本质上是文件
所以必须要用到体系调用接口来访问管道,其是由体系管理,read和write
,操纵体系相称于中介 
结论:管道的特征:
1:具有血缘关系的历程进行历程间通信
2:管道只能单向通信
3:父子历程是会历程协同的,同步与互斥的--掩护管道文件的数据安全
4:管道是面向字节省的
5:管道是基于文件的,而文件的生命周期是随历程的
1:具有血缘关系的历程进行历程间通信2:管道只能单向通信3:父子历程是会历程协同的,同步与互斥的--掩护管道文件的数据安全4:管道是面向字节省的5:管道是基于文件的,而文件的生命周期是随历程的
再测试,把子历程sleep去掉,就是让子历程写快一点,父历程sleep几秒,就是让父历程读慢一点,看有什么征象

 管道的四种情况
https://i-blog.csdnimg.cn/direct/a962a8edefb94de096482414f621de68.png
https://i-blog.csdnimg.cn/direct/3e989e21d2ed47b9ab785e9f6caa0495.png
https://i-blog.csdnimg.cn/direct/87d8448e3ae84a6dbd014ba8998dc156.png
https://i-blog.csdnimg.cn/direct/8c29e9dfe2a448bd86de57448e4c3df3.png
https://i-blog.csdnimg.cn/direct/b8939b281e9146d689f2176765182bb5.png
测试管道大小
把c一直往管道里写,把父历程中休眠50秒
https://i-blog.csdnimg.cn/direct/c1735eb26fac4137ae7199fbde1ee891.png
https://i-blog.csdnimg.cn/direct/f7ff3f72faf94cd5814dcc10c30371d6.png
效果差不多64kb
https://i-blog.csdnimg.cn/direct/5ffad034b0de4533846b7b37ff8682b7.png


写端退了,测试效果
https://i-blog.csdnimg.cn/direct/135e56b19f79442bb24bc6b723215cd6.png
效果是:
读端正常读,写端关闭,读端就会读到0,表明读到了文件(pipe)末端,不会被阻塞
read读取成功会返回读到的字符个数,读到末端返回0
https://i-blog.csdnimg.cn/direct/2d76269839db4468b4d79876e4e86491.png

读到末端父历程也就可以停止读取了,break后去把僵尸的子历程接纳
https://i-blog.csdnimg.cn/direct/9d9849bf87a949baabe8a2692b84c152.png
break到这里
https://i-blog.csdnimg.cn/direct/64f13a34af1d4fdab7161056b6eb2dc6.png
最后子历程会被waitpid接纳

测试子历程一直写,父历程读一会就退出
界说一个cnt控制退出的时间
https://i-blog.csdnimg.cn/direct/b0e1cb2e58c549ceb6e7acdc1e1c7eb0.png
这里也要修改一下,加个sleep(5),观察,close提前关闭
https://i-blog.csdnimg.cn/direct/1748ae9779c646f58639acf21da3fd90.png
https://i-blog.csdnimg.cn/direct/7e0b6fc04acc4aefb744be2fdc4898ba.png
效果:通过13号信号杀死 
https://i-blog.csdnimg.cn/direct/28a587c03006473f8d8e7758ded4f09f.pnghttps://i-blog.csdnimg.cn/direct/41b6d86cd9f64fda81982b7799b534df.png

管道到的应用场景
https://i-blog.csdnimg.cn/direct/db53ac35c459428a84527122697043be.png
都会酿成一个历程
https://i-blog.csdnimg.cn/direct/77e37c2e219148b588738f19044d41cd.png


写一个历程池(pipe_use)
首先创建好文件
https://i-blog.csdnimg.cn/direct/aafbc31744b34d2ca1aecb2e5ccfd21f.png

创建5个历程
channel通道的意思cmdfd文件描述符slaverid代表哪个子历程 https://i-blog.csdnimg.cn/direct/8138ea2229ad4553b2afeaf60a7ccc3e.png
https://i-blog.csdnimg.cn/direct/3d0b81d04dfc43dba6d581f48d53a006.png

把它放进vector容器里

思绪步骤
https://i-blog.csdnimg.cn/direct/bfd86ad5e34848d69eb3f3581283f964.png

管道创建
void(n),冒充利用一下,要不然编译不过
https://i-blog.csdnimg.cn/direct/bb0c82dc0fe5402bb03bb2725726dc79.png
创建父子历程
https://i-blog.csdnimg.cn/direct/e629ddfc1c0b40b78d0305a792ff77af.png
父历程写,子历程读
子历程要读取,就要关闭自己的写端,父历程同理
https://i-blog.csdnimg.cn/direct/1c8a9f9ca8504f4fba79672c22e31367.png
子历程中的任务
https://i-blog.csdnimg.cn/direct/ab1a9dde70fb4ab592f68022b079887b.pnghttps://i-blog.csdnimg.cn/direct/b458362207ab4a4bb13f30bba05d47e8.png

子历程pid有了管道也有了,就差在父历程添加字段了

先更改一下,在class里构造一下
https://i-blog.csdnimg.cn/direct/1230daca85bc45c2bd85332984b717eb.png

添加字段
https://i-blog.csdnimg.cn/direct/f7ef72e12c324b309110944fb7ee25d2.png
测试一下:效果:文件描述符0,1,2是默认打开,3是从管道里读,4是写入管道
https://i-blog.csdnimg.cn/direct/bea0d88de03e4425923c341791179d44.png

把初始化改造成函数
https://i-blog.csdnimg.cn/direct/6becaac443c047ce940c74898013e3bc.png
https://i-blog.csdnimg.cn/direct/5704c86a61c64b19b66e526f8d50b4ef.png


debug测试函数,纯输入函数
https://i-blog.csdnimg.cn/direct/6dfc2363bce44610af19a1356194101e.png
https://i-blog.csdnimg.cn/direct/2ac41151de3a4694a80bd0ee1ac4cb26.png

第二步开始控制历程了(想让子历程做什么)
https://i-blog.csdnimg.cn/direct/8e21d2884ffa493eb79a245ff78080cc.png
这里打印的rfd都是3,正常吗,文件描述符是可以被子历程继承的

父历程对应的写端拿到的是4-8,子历程拿到的读端fd是3
https://i-blog.csdnimg.cn/direct/c34c5b8df6244a8eb9d0fd6b9211ac89.png

改变一下,直接从键盘(0号描述符)里读,不从管道(3)里读了,就没有管道的概念了,slaver就不用传参了,父历程通过管道写,子历程通过标准输入读
用到了dup2,将从pipefd中读酿成从0开始读
https://i-blog.csdnimg.cn/direct/b5319ca1b2104b5d8b894b3bdb457120.png
https://i-blog.csdnimg.cn/direct/4c3142b16dd14ed98118074cba038c02.png
想让父历程固定的向管道里写入指定大小字节的内容,必须读取四个字节,四个字节四个字节的写和读,这里的管道64kb
必须读取四个字节
https://i-blog.csdnimg.cn/direct/5c098cba66244255844fd4e6eab33d1e.png

假如父历程不给子历程发送数据呢?阻塞等待!
https://i-blog.csdnimg.cn/direct/b767dc88c7b54ba1af04ab11c5b11add.png


开始控制子历程
https://i-blog.csdnimg.cn/direct/6bfc2597eda24fa997d5c6dadc248add.png

生成一个随机数种子
https://i-blog.csdnimg.cn/direct/9da66ec5c9404ee8ab8f01bc0080867d.png
可以随机选择任务和选择历程
https://i-blog.csdnimg.cn/direct/815f9f4ab185486492353bc1f951ab8a.png
https://i-blog.csdnimg.cn/direct/d54bda92a21245e0915319930024926e.png
https://i-blog.csdnimg.cn/direct/231f3975cd614fe69010d47f2ebf4bd8.png

cmd是任务码,测试一下,父历程控制子历程,父历程发送给子历程(通过cmdcode连续)
https://i-blog.csdnimg.cn/direct/e9b78fb87b03481a8c73de27f3ba409e.png
https://i-blog.csdnimg.cn/direct/3a97a0787d70494aacf3cdc6adcf366b.png

在Task.hpp里
https://i-blog.csdnimg.cn/direct/a57cad05bb3e47a0b54ce53570898fa6.png

要用到函数指针
https://i-blog.csdnimg.cn/direct/cb1d20b0273e44e69fddc1ad1d90752e.png

main中的任务了就属于
https://i-blog.csdnimg.cn/direct/e3d0685c247748aebb87e4f8802737f6.png
再把任务装载进来
https://i-blog.csdnimg.cn/direct/a5db4049ea6a4054bb29836e79c64694.png

输出型参数用*
https://i-blog.csdnimg.cn/direct/1130b892287e429e98cf541cbcc600c5.png

现在开始选择任务和历程
https://i-blog.csdnimg.cn/direct/ee142fe095a04e058f2db57219205a75.png
https://i-blog.csdnimg.cn/direct/967799960fd14fdc92a00f48c218f1ba.png

再把main中的任务弄玉成局的
https://i-blog.csdnimg.cn/direct/73bd6d9e04904857837b5787b197e6ca.png

进行判定一下
https://i-blog.csdnimg.cn/direct/b3b103abb7a04a2483f79aa8b2b2b17e.png

测试 ,comcode和任创建的任务同等
https://i-blog.csdnimg.cn/direct/fa08b4b28ce1490fa20bf48d5c75f6f3.png

这里的write是父历程进行写入,向子历程发送,子历程不得闲,先写到管道里,等得闲了再读
https://i-blog.csdnimg.cn/direct/2488326798ce40e7a8bc66c601cf1427.png

也可以轮询选择,界说一个计数器,++弄,再%等


整理一下控制代码,这里是输入型参数,只必要读
https://i-blog.csdnimg.cn/direct/eca45e533250462d9ebdb2a9df88ab5c.png
https://i-blog.csdnimg.cn/direct/a583fc02d342447b8e75bc6250b79a3b.png

这样就可以轮询方式选择历程了,不用随机了
https://i-blog.csdnimg.cn/direct/fdb9b83a03a34f5d923dc2ecca9d3ca8.png
效果
https://i-blog.csdnimg.cn/direct/0676da99729f467fba058b5766b37d06.png


清算收尾
思绪:把全部文件的描述符都关掉
https://i-blog.csdnimg.cn/direct/e48497f8316b48a994cdd63e15766f68.png
https://i-blog.csdnimg.cn/direct/18f77cc10959438fb577e1eaac0a3636.png

等待方式设置为0 
https://i-blog.csdnimg.cn/direct/0850cf4313b641839a792d9a5eb52ae7.png

read返回0,就是失败了,然后slaver就会调完
https://i-blog.csdnimg.cn/direct/f797cb8be7e44011aa12ea10ddbdd90c.png
竣事完就会exit直接退出
https://i-blog.csdnimg.cn/direct/0389505aad474b7aae38b3eed54c723b.png
打印下更好表现
https://i-blog.csdnimg.cn/direct/0d2250d2c9ee44a6baee8a2ada035a01.png

关闭文件描述符后sleep(10)秒,
https://i-blog.csdnimg.cn/direct/753bc968b40a4615bec34a73b36e591c.png

然后这10个子历程一瞬间都应该break,然后最后到exit直接就退了,10秒竣事后,父历程再接纳他       

测试时不弄死循环,用cnt,5秒后自动竣事控制,正常退出流程
https://i-blog.csdnimg.cn/direct/f0e59176bd914da9958e9c7851ec542b.png


测试效果
https://i-blog.csdnimg.cn/direct/42bfdc1cf3b24cdfa68e42cbcb9a06f7.png

手动控制一下
https://i-blog.csdnimg.cn/direct/ff6e5269ce0b4c6ca013a221bf1f76db.png
界说一个select,输入0就是退出了,判定完后,就走到了选择任务
https://i-blog.csdnimg.cn/direct/bc87e1d0c8d146d2bfc105095ac2a49c.png
然后直接把cmdcode改为选择的select,-1是因为是从下标0开始的,输入1就是0下标的
https://i-blog.csdnimg.cn/direct/113059d8611d4613b4e2145de04742b2.png
测试
https://i-blog.csdnimg.cn/direct/6d0e0f8fc471487ea75ec581d58e5182.png

bug的地方:
这样会有一些bug(一个子历程不是只有一个写端(每一次子历程的创建都是有继承))
 这样会有一些bug(一个子历程不是只有一个写端(每一次子历程的创建都是有继承))https://i-blog.csdnimg.cn/direct/8487a9424017496cab60b808c75f7492.png

https://i-blog.csdnimg.cn/direct/d8e3f5b662264e8cb48691ca8655bd43.png


按理说这样是对的,可是这样就错了
https://i-blog.csdnimg.cn/direct/7f1507e95a3747f6944abc7a99764da9.png因为下面两个红线还没有关掉,它们历程了最开始的w
https://i-blog.csdnimg.cn/direct/0bee7551c7c0445a94be1fb46c9c7d0c.png


这样倒着接纳是可以的
https://i-blog.csdnimg.cn/direct/ea80eb8d7c114f3e916c2a8e2a86e399.png


正确改法
https://i-blog.csdnimg.cn/direct/499317be7a7e4a60a9099277765ce1ed.png




修改一下
https://i-blog.csdnimg.cn/direct/f10217fe05a4490fbae8b041bfe914a2.png
最后一个push_back的就都是父历程的写入fd,

然后加一句这个红线的,每创建子历程后都先把上一次父历程的读端fd关掉就可以了,这里很妙,因为vector一开始是空的
https://i-blog.csdnimg.cn/direct/902adbf0c39f4788a42f63e3abd9b987.png
方便看
https://i-blog.csdnimg.cn/direct/47b2f0021850446285ad623a5de8601f.png

这里这样就可以了        
https://i-blog.csdnimg.cn/direct/cc5b62fcba0f4c449d6668a3a16d0114.png


管道已经完成
https://i-blog.csdnimg.cn/direct/d30b136d5fe542f2bfba81bdff58a82c.png
以上是匿名管道 

总文件总代码
https://i-blog.csdnimg.cn/direct/a92d09507bb04e819bc3e6974ba18159.png
makefile中代码
ProcessPool:ProcessPool.cc
        g++ -o $@ $^ -std=c++11
.PHNOY:clean
clean:
        rm -f ProcessPool Task.hpp中代码
#pragma once

#include<iostream>
#include<vector>

using namespace std;

typedef void (*task_t)();

void task1()
{
    cout<< "lol 刷新日志" <<endl;
}

void task2()
{
    cout<< "lol 更新野区" <<endl;
}

void task3()
{
    cout<< "lol 检测软件更新" <<endl;
}

void task4()
{
    cout<< "lol 释放技能" <<endl;
}
ProcessPool.cc中代码
#include "Task.hpp"
#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<assert.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

//打算创建5个进程
const int processnum = 5;
//全局任务
vector<task_t> tasks;


//先描述
class channel//管道
{
public:
    channel(int cmdfd,pid_t slaverid,string& processname)
    :_cmdfd(cmdfd)
    ,_slaverid(slaverid)
    ,_processname(processname)
    {}

public:
    int _cmdfd;//文件描述符
    pid_t _slaverid;//代表哪个子进程
    string _processname;//子进程的名字,方便打印日志
};

//子进程中读的任务
// void slaver(int rfd)
// {
//   while(true)
//   {
//         cout<< getpid() <<" - "<< "read fd is->"<<rfd<<endl;
//         sleep(1000);
//   }
// }
//改变一下从fd为0的地方开始读
void slaver()
{
    //read(0);
    while(true)
    {
      int cmdcode = 0;
      int n = read(0, &cmdcode, sizeof(int));
      if(n == sizeof(int))
      {
            //执行cmdcode对应的任务列表
            cout<< "slaver say@ get a command:" << getpid() << ":cmdcode:" << cmdcode <<endl;

            //判断一下并执行
            if(cmdcode >= 0 && cmdcode < tasks.size())tasks();
      }
      if(n == 0) break;
    }
}

//初始化
void Init(vector<channel>& channels)
{
    for(int i =0;i < processnum;i++)
    {
      int pipefd;
      int n = pipe(pipefd);//创建管道
      //返回值小于0就创建失败了
      assert(!n);
      (void)n;

      pid_t id = fork();
      if(id == 0)
      {
            //子进程:读
            close(pipefd);

            //改变一下从fd为0的地方读
            dup2(pipefd,0);
            close(pipefd);
            //任务
            slaver();
            cout<< "process: " << getpid() << "quit" <<endl;
            //slaver(pipefd);

            exit(0);
      }
      //父进程:写
      close(pipefd);
      //channel添加字段
      string name = "processs-" + to_string(i);
      //插入的是自定义类型,要构造一下,第一个传的是文件描述符,要写入的fd
      channels.push_back(channel(pipefd, id, name));
    }
}
//测试函数,纯输入函数
//输入:const &
//输出:*
//输入输出:&
void debug(const vector<channel>& channels)
{
    for(auto&e : channels)
    {
      cout<< e._cmdfd <<""<<e._slaverid<<"   "<<e._processname<<endl;
    }

}

void Loadtask(vector<task_t> *tasks)
{   
    tasks->push_back(task1);
    tasks->push_back(task2);
    tasks->push_back(task3);
    tasks->push_back(task4);
}
void memu()
{
    cout<< "########################" <<endl;
    cout<< "1:lol 刷新日志   2:lol 更新野区" <<endl;
    cout<< "1:lol 检测软件更新   4:lol 释放技能" <<endl;
    cout<< "         0:退出             " <<endl;
    cout<< "########################" <<endl;
}
//2:开始控制子进程
void ctrlSlaver(vector<channel> &channels)
{
    int which = 0;
    int cnt = 5;
    while(true)
    {
      int select = 0;
      memu();
      cout<< "Please Enter@:";
      cin>> select;
      if(select == 0) break;
      //1:选择任务
      //int cmdcode = rand()%tasks.size();
      int cmdcode = select - 1;

      //2:随机选择进程
      //int processpos = rand()%channels.size();
      
      //2:轮询选择进程
      cout<< "father say:"<< "cmdcode:" << cmdcode << "   already sendto" <<channels._slaverid << "process name   "
            <<channels._processname << endl;

      //3:发送任务
      write(channels._cmdfd, &cmdcode, sizeof(cmdcode));
      which++;
      which%=channels.size();//保证不大于其长度
      cnt--;
      if(cnt == 0) break;
      sleep(1);
    }
}
void QuitProcess(const vector<channel> &channels)
{
    for(const auto& e : channels) close(e._cmdfd);
    sleep(10);
    for(const auto& e : channels) waitpid(e._slaverid, nullptr, 0);//进程的pid=_slaverid,关上了以后记得回收
}

int main()
{
    Loadtask(&tasks);

    //srand(time(nullptr)^getpid()^1023);//种一个随机数种子
    //在组织
    vector<channel> channels;
    //1:初始化
    Init(channels);

    debug(channels);

    //2:开始控制子进程
    ctrlSlaver(channels);

    //3:清理收尾
    QuitProcess(channels);

    return 0;
}


用fork来共享管道原理

https://i-blog.csdnimg.cn/direct/a33aad28ef8f4fd8b2aada238909e755.png
站在文件描述符角度-深度理解管道

https://i-blog.csdnimg.cn/direct/20e04863d0d148de8fcc2624a020553b.png
所以,看待管道,就犹如看待文件一样!管道的利用和文件同等,迎合了“Linux一切皆文件头脑”。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)
int main(int argc, char *argv[]) { int pipefd; if (pipe(pipefd) == -1) ERR_EXIT("pipe error");
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");

if (pid == 0) {
close(pipefd);
write(pipefd, "hello", 5);
close(pipefd);
exit(EXIT_SUCCESS);
}

close(pipefd);
char buf = {0};
read(pipefd, buf, 10);
printf("buf=%s\n", buf);

return 0;
}

管道读写规则

   

[*]当没有数据可读时
[*]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,此后父、子历程之间就可应用该管道。
[*]管道提供流式服务
[*]一样平常而言,历程退出,管道释放,所以管道的生命周期随历程
[*]一样平常而言,内核会对管道操纵进行同步与互斥
[*]管道是半双工的,数据只能向一个方向活动;必要两边通信时,必要建立起两个管道
https://i-blog.csdnimg.cn/direct/406414a818c942368b950aaddad0e576.png
命名管道

详细看这个
命名管道Linux-CSDN博客
   

[*]管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的历程间通信。
[*]假如我们想在不相干的历程之间交换数据,可以利用FIFO文件来做这项工作,它经常被称为命名管道。
[*]命名管道是一种特殊类型的文件 int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}
创建一个命名管道

   

[*]命名管道可以从命令行上创建,命令行方法是利用下面这个命令:
$ mkfifo filename   

[*]命名管道也可以从步伐里创建,相干函数有:
int mkfifo(const char *filename,mode_t mode);
创建命名管道:
int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}   管道是
https://i-blog.csdnimg.cn/direct/dc8936dffc63453e91838faad8a3a004.png
毫不相干的历程历程间通信::命名管道
管道
首先自己要用用户层缓冲区,还得把用户层缓冲区拷贝到管道里,(从键盘里输入数据到用户层缓冲区里面),然后用户层缓冲区通过体系调用(write)写到管道里,然后再通过read体系调用,被对方(读端)读取,就要从管道拷贝到读端,然后再表现到表现器上。
mkfifo命名管道
1号手册是指令,。2号体系调用接口
https://i-blog.csdnimg.cn/direct/274274a840b14b36a4b641398bcc2ce5.png


创建一个管道,p开头就是命名管道,并不会直接革新到磁盘中,现实是个符号
https://i-blog.csdnimg.cn/direct/ca928e72e62c4ae5a5b6714dd74f7f96.png

这样会阻塞
https://i-blog.csdnimg.cn/direct/de4bcc3d32b9484b869081f7ea68cdd6.png

这样会表现出来(先输入左边的,再输入右边的就会表现),左右两边是两个历程
https://i-blog.csdnimg.cn/direct/7f49f3fb9b6c4ed0b0ab835966082bca.png

>>追加写入的方式,但空间一直是0
https://i-blog.csdnimg.cn/direct/8c75dd0047124c0687324cbaf63af203.png



https://i-blog.csdnimg.cn/direct/0147bf449ccd4f12adc9c5ea8da6d61c.png

https://i-blog.csdnimg.cn/direct/479c2e6f5a1f4f6cb8e34b296ff31fbb.png

所以这就是文件里大小一直是0的原因         https://i-blog.csdnimg.cn/direct/6d24afdad99245ac9206d19f14b5fa95.png


你怎么知道打开的是同一个文件
https://i-blog.csdnimg.cn/direct/624dfcdc304f4a14a24fe11a87bea5d5.png
https://i-blog.csdnimg.cn/direct/e9a6379704404095ba1adb709bc2822e.png

正好符合前提
https://i-blog.csdnimg.cn/direct/6c5a04cafd4d41569a462029a4a8bcea.png

所以要创建两个可执行步伐,各自跑各自的,创建一个common是为了方便利用头文件        
client是客户   server是服务者
https://i-blog.csdnimg.cn/direct/18f59cd11f6740d49d50e00142aebe26.pnghttps://i-blog.csdnimg.cn/direct/a96f7507588e465cb9022f9cec526a7d.png
https://i-blog.csdnimg.cn/direct/8c59dcf98a2e4817af37b48719ce5c04.png
https://i-blog.csdnimg.cn/direct/c161abc5e51d48b3b1a84ae6143f82e1.pngmakefile中一下运行两个步伐
https://i-blog.csdnimg.cn/direct/98c8512625644c3b932be3275653c8dd.png

mkfifo,用步伐的方式创建管道,第一个参数是要创建的这个管道在那个路径下叫什么名字,也就是要保持唯一性的那些点,第二个是创建一个管道
这里是3号手册是函数。
https://i-blog.csdnimg.cn/direct/ee6023abc44248e7a66b7239dc95798c.png
返回 -1创建失败 
https://i-blog.csdnimg.cn/direct/96e8539b77b346d3a9ae84cfcfc25f00.png

创建一个共享文件
./myfifo
https://i-blog.csdnimg.cn/direct/eed62c44943343fa9c58e4665681c842.png

server.cc和client.cc想看到同一个文件,包罗上头文件就可以了

这里先用server控制管道文件
https://i-blog.csdnimg.cn/direct/6c99c01bd4764810b02638637eef334d.png
创建管道失败了设置为1 ,假如失败了就exit(1)
https://i-blog.csdnimg.cn/direct/196fbe1dfaaa475fb46d2daf7e5906d1.png

谁控制的先运行运行谁就好了
make一下生成两个可执行步伐,因为是server控制的,所以要先运行server
https://i-blog.csdnimg.cn/direct/03ec6abe481047d9a5b3570e5ac46eca.png
运行后就会多一个myfifo命名管道
https://i-blog.csdnimg.cn/direct/6fbdc49ca11d49c5b9e488281ddb9b2a.png

命名管道的删除https://i-blog.csdnimg.cn/direct/14bde202672f45d9b63d86aaf7c3cbc8.png
想删除这个myfifo用unlink(成功返回0 ,失败返回-1) 
命令行删除
https://i-blog.csdnimg.cn/direct/00dfe6184fbf4bc2a06cffe1dacaf7dd.png
代码也可以删(成功返回0 ,失败返回-1),头文件是unistd.h


创建完文件,5秒后就删除了
https://i-blog.csdnimg.cn/direct/c809ab2cfd0749a198aa82b5d29178d4.png

思绪
https://i-blog.csdnimg.cn/direct/d1dc861ae46a4f5d96770122fe4c1e81.png

用到了open
https://i-blog.csdnimg.cn/direct/58cd8d6ee3e04b8e91668f7ebc89913d.png

打开管道文件,第二个参数是只进行读取
https://i-blog.csdnimg.cn/direct/85b3fb220111424981b07be94b18e5ff.png
enum中
https://i-blog.csdnimg.cn/direct/6c70d8f46be84119a19690ecf016b62e.png

fd<0打开失败了
https://i-blog.csdnimg.cn/direct/85d41cc544634d49b723c1e2a366c197.png

服务端读取数据
https://i-blog.csdnimg.cn/direct/314f2049e9fa4cbd817f07f3b3de0fc3.png

客户端,只要用就行
 第二个参数就是打开文件为了写入的
https://i-blog.csdnimg.cn/direct/513f8709f40b4bdba152a72cef94e72c.png
https://i-blog.csdnimg.cn/direct/9d6be315b6c94dde860ca5430e274a98.png

用户输入完成以后,就要发送输入的消息到另一端
https://i-blog.csdnimg.cn/direct/01ad87d1ba544c62901b5bb9feea6357.png

打开顺序肯定
然后打开的顺序就肯定了,先打开server,然后再打开另一个cc
先打开服务端,会阻塞在这里,然后再打开客户端,进行输入
https://i-blog.csdnimg.cn/direct/193887ed24344552aff032961e2c5f42.png
右边输入啥,左边就会有啥
https://i-blog.csdnimg.cn/direct/bb949e004d5a456d8f664c50444652a1.png
无法输入空格问题(getline)
但有一个问题就是cin没法输入空格,,要用到getline
https://i-blog.csdnimg.cn/direct/900bbfcd51564a9c9af8a6a1153ef570.png

会发现一个问题,客户端退出了,服务端还没退出
客户端退出,会read到0,所以服务端(读端)也要退出https://i-blog.csdnimg.cn/direct/d1571cd1acc14d5aa064cf59e873b48a.png
 改正
https://i-blog.csdnimg.cn/direct/1f81a1008db846a7ac402d5b5ad48481.png

sever端
https://i-blog.csdnimg.cn/direct/e42c9e5bfe094b529fd57033770800c1.png
等待写入方式打开后,自己才会打开文件,向后执行,open阻塞了!


优化一下
https://i-blog.csdnimg.cn/direct/1a9158fe00874dfab80f9e42bfa75bc1.png

https://i-blog.csdnimg.cn/direct/8ea1ce1c27444a3cbc026a1a7b56a7f3.png


写成历程池的样子
https://i-blog.csdnimg.cn/direct/53276ca56aeb41ae9101e5b7aa53841d.png
https://i-blog.csdnimg.cn/direct/38b1fc2cff174fbcbd5e19f49ef5f96f.png

日志
创建一个新文件
https://i-blog.csdnimg.cn/direct/889f9e69d137401291e87d9f72793e8c.png


用到了可变参数(形参实例化是从右到左)
可变参数必须右至少一个详细的参数
举个例子:步骤:s指向可变部门
这里的sum第一个参数是几个数求和的意思,传差别的类型不可以的,因为上面va_arg里已经写死了
https://i-blog.csdnimg.cn/direct/ed47fa265e2f434e87da605fae14f8cd.png

开始写日志,level日志品级
https://i-blog.csdnimg.cn/direct/cc5f870296de4a98b7f2510f024f0d94.png

先界说时间,time,时间戳
https://i-blog.csdnimg.cn/direct/8ac214b9d5ba48dbb6cabc04af8ecffd.png

https://i-blog.csdnimg.cn/direct/6553071bf21c474f9a6b7b94914bf7f6.png
ctime
头文件
https://i-blog.csdnimg.cn/direct/67221f4f2ab542bb8ce1af31da771ca2.png
https://i-blog.csdnimg.cn/direct/526dc1f4c4464280bb01eaf0bf7b657a.png
打印详细年月日 
年是从1900年开始的
https://i-blog.csdnimg.cn/direct/132e16a146734118992c5d26255ac59f.png
https://i-blog.csdnimg.cn/direct/802880c6c75241ba9dc56aeb744b55f1.png
 年月日时分秒
https://i-blog.csdnimg.cn/direct/43b80e3717c4440f98d9c580ea587e15.png

vsnprint
vsnprint,跟不带v的区别就是,去除了...换成了可变参数部门
https://i-blog.csdnimg.cn/direct/40eea0da7ccb4daab7de17f34816229e.png

把日志品级转换成字符串风格,全部有大概的地方都必要返回
https://i-blog.csdnimg.cn/direct/be4fbf00eb364ec88784206892c5e53d.png

改进
va_start(s,format),用format修饰s的指向,上面的sum是(s,n),类似
这里要用c_str,因为返回的是string
https://i-blog.csdnimg.cn/direct/d06fbf43690d4936ac6aa9c760f627fa.png
用完以后再用end
https://i-blog.csdnimg.cn/direct/7b689d7c1f884e9a97914d11d91d5879.png

这里是往表现器打印的,这里要*3,以为%s和%s中间有几个空格,空间不敷
https://i-blog.csdnimg.cn/direct/10501f11327d46988b80cdaef3b1c2f1.png
https://i-blog.csdnimg.cn/direct/c93e09d9c2e84f29b8ed506fec9f3a1e.png

把这里修改一下,打开失败的话
https://i-blog.csdnimg.cn/direct/eec0beac44964811be32d013d607f7a3.png
这样就形成日志了
https://i-blog.csdnimg.cn/direct/5b4811939aa741f2a03b103d8a6a067f.png
https://i-blog.csdnimg.cn/direct/f00a0748a02e472986d0c68fa0859b22.png
打印最后一行就是正常打开
https://i-blog.csdnimg.cn/direct/b18e3e7fa1bd457abfa4d92924046c2e.png
这里也改一下
https://i-blog.csdnimg.cn/direct/bc3a4d7ed77f453eba42d130a633d708.png
测试,先./server,然后会阻塞,然后./client,就会打印出,logmessage里的信息
为啥./client之前不打印
因为等待写入方式打开后,自己才会打开文件,向后执行,open阻塞了!https://i-blog.csdnimg.cn/direct/5917e738ba1e478682c5368f60522448.pnghttps://i-blog.csdnimg.cn/direct/e650a3e41a8e4e04a7ad5a4a6ab1b71f.png
往文件里打印(上面是往屏幕打印)
先把这些内容全放在Log,日志类
https://i-blog.csdnimg.cn/direct/06a5c6d443c04b74a89920df21234bfc.png
分类
1:向屏幕打印
2:向一个文件打印
3:分类打印https://i-blog.csdnimg.cn/direct/ae8b0744758d41279764ce537ec3fdbe.png
打印格式printMethod
https://i-blog.csdnimg.cn/direct/2cd29059ea1a447dbc69753028218a61.png

这里构造默认往屏幕去印
https://i-blog.csdnimg.cn/direct/ef34af44428e485d93e49b90963b7252.png

析构
https://i-blog.csdnimg.cn/direct/8737b914874f416999cb9cd153c7c6ef.png

打印方式也改一下
https://i-blog.csdnimg.cn/direct/c647cb2733564b50bde9a8103e6140ee.png
https://i-blog.csdnimg.cn/direct/212b274e58cc42f88345f447b87858f6.png
https://i-blog.csdnimg.cn/direct/1c2634e60e2f4220982e55ae5db58c60.png
打印单个
以0666的权限打开这个文件
https://i-blog.csdnimg.cn/direct/7e6631c45b1d4f38aba4b8476c48c4c4.png

打印多个文件(添加一下level)
https://i-blog.csdnimg.cn/direct/f6deecbf4a78489997a03aeb5db41f78.png
https://i-blog.csdnimg.cn/direct/6bad0eb37e15468d97f6cbf19cfa0d54.png
实现一下
https://i-blog.csdnimg.cn/direct/ad52e9994c4b41a7bf6d34cf10b084df.png
优化一下
https://i-blog.csdnimg.cn/direct/4de0c06910c14e8c8cf19ebfad675afa.png
https://i-blog.csdnimg.cn/direct/d958969318614f18bf9aaa35c263c7e3.png

以后再打印日志就不用这样打了
https://i-blog.csdnimg.cn/direct/a8bb6d6bf3674030a72ce9fa299f8a75.png

这样就可以了,要记住先创建一个Log对象
https://i-blog.csdnimg.cn/direct/cf2c56fa9ae146bb89e5e0184c54019f.png


这样以后就写入一个文件了,写入log.txt
https://i-blog.csdnimg.cn/direct/16f18183b3b54e909fce34fdf95e8310.png

这样就把日志分类了
https://i-blog.csdnimg.cn/direct/fe758436b7fc4dd1bc772f42b04394f1.png
效果
https://i-blog.csdnimg.cn/direct/7e7c5c77845b4d44b68d0c51589a1b3f.png


但是日志文件这么多太杂乱了
这样操纵后就统一进入一个文件了
https://i-blog.csdnimg.cn/direct/8134c0e4340f460f909f282cc55f63b5.pnghttps://i-blog.csdnimg.cn/direct/79eeb9a875b546708f52071124c1bbe0.png
https://i-blog.csdnimg.cn/direct/f00deecb571941ae9ea99a1a9332749d.png

makefile也修改一下,先把path界说的log目次创建一下
https://i-blog.csdnimg.cn/direct/bd3e3c4bff73457baddb97de3fba9e92.png
日志放入一个文件测试效果:https://i-blog.csdnimg.cn/direct/8dbd699b711f496a9ad70364a36ed530.png
https://i-blog.csdnimg.cn/direct/43569ce281b34db981a10173b0a00fce.png

日志分类测试效果:
https://i-blog.csdnimg.cn/direct/e65b313c8fba46648d35173b96a7d2e4.png
https://i-blog.csdnimg.cn/direct/5011f2da267e40b2996d6d419a603795.png

log.hpp里头文件
https://i-blog.csdnimg.cn/direct/d73e2e24acca40bbbb37088b4acb0de3.png

优化一下调用
https://i-blog.csdnimg.cn/direct/b4723b3b8d7a43419f7df0932d8f39e4.png

然后修改一下server.cc
https://i-blog.csdnimg.cn/direct/68e872ff6ed842d49e1d5a5c05d0adab.png
 https://i-blog.csdnimg.cn/direct/f0f69d62473743f5a1bb4e4204260036.png
client.cc
#include "common.hpp"
#include "log.hpp"

int main()
{
    int fd = open(FIFO_FILE,O_WRONLY);
    if(fd < 0)
    {
      perror("open");
      exit(FIFO_OPEN_ERR);
    }
    string line;
    while(true)
    {
      cout<< "Please Enter@ ";
      // cin>> line;
      getline(cin, line);

      write(fd, line.c_str(),line.size());
    }

    close(fd);
    return 0;
}


common.hpp
#pragma noce
#include<iostream>
#include<vector>
#include<string>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
using namespace std;

#define FIFO_FILE "./myfifo"
#define MODE 0664 //用于设置文件的权限,0664代表着8进制写法,4是其他用户可读不可写

enum
{
    FIFO_CREATE_ERR = 1,
    FIFO_DELETE_ERR,
    FIFO_OPEN_ERR
};

class Init
{
public:
    Init()
    {
      //创建管道
      int n = mkfifo(FIFO_FILE,MODE);
      if(n == -1)
      {
            perror("mkfofi");
            exit(FIFO_CREATE_ERR);
      }
    }
    ~Init()
    {
         //删除命名管道
      int m = unlink(FIFO_FILE);
      if(m == -1)
      {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
      }      
    }
}; log.hpp
#pragma noce
#include <stdarg.h>
// #include "common.hpp"
#include <iostream>
#include <stdio.h>
#include<string.h>//strerror(errno)头文件
#include<stdlib.h>
using namespace std;

#define Info      0
#define Debug       1
#define Warning   2
#define Error       3
#define Fatal       4 // 致命的

//打印方式
#define Screen      1 //屏幕
#define Onefile   2 //一个文件
#define Classfile   3 //多个文件

#define LogFile    "log.txt"

class Log
{
public:
    Log()
    {
      printMehod = Screen;
      path = "./log/";
    }
    void Enable(int method)
    {
      printMehod = method;
    }
    string levelToString(int level)
    {
      switch (level)
      {
      case Info:
            return "Info";
      case Debug:
            return "Debug";
      case Warning:
            return "Warning";
      case Error:
            return "Error";
      case Fatal:
            return "Fatal";
      default:
            return "";
      }
      return "";
    }
    // void logmessage(int level, const char *format, ...)
    // {
    //   time_t t = time(nullptr);
    //   struct tm *ctime = localtime(&t);
    //   char leftbuffer;
    //   snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d%d:%d:%d]", levelToString(level).c_str(),
    //             ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

    //   va_list s;
    //   va_start(s, format);
    //   char rightbuffer;
    //   vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
    //   va_end(s);

    //   // 格式:默认部分+自定义部分
    //   char logtxt;
    //   snprintf(logtxt, sizeof(logtxt), "%s%s\n", leftbuffer, rightbuffer);
    //   //cout << logtxt << endl; // 暂时打印
    //   printLog(level, logtxt);
    // }
    void operator()(int level, const char* format, ...)
    {
      time_t t = time(nullptr);
      struct tm *ctime = localtime(&t);
      char leftbuffer;
      snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d%d:%d:%d]", levelToString(level).c_str(),
                ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

      va_list s;
      va_start(s, format);
      char rightbuffer;
      vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
      va_end(s);

      // 格式:默认部分+自定义部分
      char logtxt;
      snprintf(logtxt, sizeof(logtxt), "%s%s\n", leftbuffer, rightbuffer);
      //cout << logtxt << endl; // 暂时打印
      printLog(level, logtxt);
    }

    void printLog(int level, const string &logtxt)
    {
      switch(printMehod)
      {
            case Screen:
                cout<< logtxt <<endl;
            break;
            case Onefile:
                printOneFile(LogFile, logtxt);//"log.txt"
            break;
            case Classfile:
                printClassFile(level, logtxt);
            break;
            default:
            break;
      }

    }
    void printOneFile(const string &logname, const string &logtxt)
    {
      //          "./log/" "log.txt"
      string _logname =path + logname;
      int fd = open(_logname.c_str(), O_WRONLY|O_CREAT|O_APPEND, 0666);
      if(fd < 0) return;
      write(fd, logtxt.c_str(), logtxt.size());
      close(fd);
    }
    void printClassFile(int level, const string &logtxt)
    {
      string filename = LogFile;//"log.txt"
      filename += ".";//"log.txt."
      filename += levelToString(level); //log.txt.Debug/Waring/Fatal
      printOneFile(filename, logtxt);
    }
    ~Log()
    {}
private:
    int printMehod;
    string path;
};


makefile
.PHONY:all
all:client server
server:server.cc
        g++ -o $@ $^ -g -std=c++11
        mkdir log
client:client.cc
        g++ -o $@ $^ -g -std=c++11
.PHONY:clean
clean:
        rm -f server client server.cc
#include "common.hpp"
#include "log.hpp"


int main()
{
    //logmessage(Info, "hello");
    //创建管道
    Init init;
    Log log;
    //log.Enable(Onefile);
    log.Enable(Classfile);

    // //创建管道
    // int n = mkfifo(FIFO_FILE,MODE);
    // if(n == -1)
    // {
    //   perror("mkfofi");
    //   exit(FIFO_CREATE_ERR);
    // }
    // sleep(5);
    //打开管道
    int fd = open(FIFO_FILE,O_RDONLY);
    if(fd < 0)
    {
      //log.logmessage(Fatal, "error string:%s,error code:%d",strerror(errno), errno);
      //优化后
      log(Fatal, "error string:%s,error code:%d",strerror(errno), errno);
      exit(FIFO_OPEN_ERR);
    }
    // log.logmessage(Info, "server open file done,error string:%s,error code:%d",strerror(errno), errno);
    // log.logmessage(Warning, "server open file done,error string:%s,error code:%d",strerror(errno), errno);
    //优化后
    log(Info, "server open file done,error string:%s,error code:%d",strerror(errno), errno);
    log(Warning, "server open file done,error string:%s,error code:%d",strerror(errno), errno);
    //......
    //开始通信
    while(true)
    {
      char buffer = {0};
      int x = read(fd, buffer, sizeof(buffer));
      if(x > 0)
      {
            buffer = 0;
            cout<< "client say# " << buffer <<endl;
      }
      else if(x == 0)
      {
            //log.logmessage(Debug, "sclient quit too!,error string:%s,error code:%d",strerror(errno), errno);
            //优化后
            log(Debug, "sclient quit too!,error string:%s,error code:%d",strerror(errno), errno);

            //cout<< "client quit too!\n" <<endl;
            break;
      }
      else break;
    }
    close(fd);

    // //删除命名管道
    // int m = unlink(FIFO_FILE);
    // if(n == -1)
    // {
    //   perror("unlink");
    //   exit(FIFO_DELETE_ERR);
    // }


    return 0;
}


匿名管道与命名管道的区别

   

[*]匿名管道由pipe函数创建并打开。
[*]命名管道由mkfifo函数创建,打开用open
[*]FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式差别,一但这些工作完 成之后,它们具有相同的语义。
命名管道的打开规则

   

[*]假如当前打开操纵是为读而打开FIFO时
[*]O_NONBLOCK disable:阻塞直到有相应历程为写而打开该FIFO
[*]O_NONBLOCK enable:立即返回成功
[*]假如当前打开操纵是为写而打开FIFO时
[*]O_NONBLOCK disable:阻塞直到有相应历程为读而打开该FIFO
[*]O_NONBLOCK enable:立即返回失败,错误码为ENXIO


system V共享内存

详细看这里:
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的历程的地点空间,这些历程间数据通报不再涉及到 内核,换句话说是历程不再通过执行进入内核的体系调用来通报相互的数据
共享内存示意图

https://i-blog.csdnimg.cn/direct/76b8fd5faf5c4316b16ce9555c30d212.png
共享内存数据布局

struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
共享内存函数

shmget函数

   功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标记构成,它们的用法和创建文件时利用的mode模式标记是一样的返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
shmat函数

   功能:将共享内存段毗连到历程地点空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定毗连的地点
shmflg:它的两个大概取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1


[*]分析:
   shmaddr为NULL,焦点自动选择一个地点
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为毗连地点。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则毗连的地点会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示毗连操纵用来只读共享内存
shmdt函数

   功能:将共享内存段与当前历程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前历程脱离不即是删除共享内存段
shmctl函数

   功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要接纳的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据布局返回值:成功返回0;失败返回-1
https://i-blog.csdnimg.cn/direct/1ca583b813c54e94a802a7d2dbc1f5da.png
 命令      分析IPC_STAT把shmidds布局中的数据设置为共享内存的当前关联值IPC_SET   在历程有充足权限的前提下,把共享内存的当前关联值设置为Shmidds数据布局中给出的值IPC_RMID 删除共享内存段
自己总结

共享内存

文件
https://i-blog.csdnimg.cn/direct/385430f9471848e0aca6967310dabecb.png

shmget
https://i-blog.csdnimg.cn/direct/2a45b3fad18845ceb0e17f254813c25a.png
申请一个共享V的内存,从内存中开辟一段空间
第二个参数:创建共享内存的大小(问题1),单元是字节
https://i-blog.csdnimg.cn/direct/251a041816954057b2085f68034fb910.png在man中输入这个,这样就可以找到,错了返回一个-1,对了返回共享内存标识符(问题2,与文件描述符有关系吗)
https://i-blog.csdnimg.cn/direct/ee0c3a32612d4a3d957c996088a9066f.png
 第三个参数可以临时选这两个
 https://i-blog.csdnimg.cn/direct/2b0eb4224cc14b16b426ebd294be0678.pnghttps://i-blog.csdnimg.cn/direct/7af2d54bcaac4e36a762b88740f39146.png
https://i-blog.csdnimg.cn/direct/a8e3c0a74b2d40af96d542249920da7f.png https://i-blog.csdnimg.cn/direct/bfc0c76dafae46c19adb6716d3be1e7a.png


https://i-blog.csdnimg.cn/direct/4b34a608353b4debac0c54e8dd56a898.png
权限问题:
你怎么保证让差别的历程看到同一个共享内存呢?
你怎么知道这个共享内存存在照旧存在呢???
答:key来保证,唯一标识,key也是唯一的

https://i-blog.csdnimg.cn/direct/bfe49d91af2a4859af0d162feb815cc7.pnghttps://i-blog.csdnimg.cn/direct/5f2fe4701fc4400a897fce0a492cb7cd.pnghttps://i-blog.csdnimg.cn/direct/b63ed9bb2d5b4e0293fa33f00660cfb2.pnghttps://i-blog.csdnimg.cn/direct/7df5ebe5adcb494da5ef69e66b09f2e6.png路径也有唯一性 
https://i-blog.csdnimg.cn/direct/0695cfbea7f14a72894114a448691c8f.pnghttps://i-blog.csdnimg.cn/direct/6a1aec9271c445a692caf856cfa7bb6f.png
怎么形成一个key(ftok)
转化一个路径名和一个项目的标识符酿成一个System V IPC的key
https://i-blog.csdnimg.cn/direct/d817415b118042a8b7808fa692537807.png
https://i-blog.csdnimg.cn/direct/d0e48e2a1d50483e91ec7eec36b337fe.png一个路径字符串,一个叫项目id(由用户自由指定)
https://i-blog.csdnimg.cn/direct/c1f6bbfa1d94479bbbb6baf096605d77.png
key实在是啥不紧张,只要能保持唯一性就好
创建共享内存也是有大概失败       


使两个通信的历程看到同一个key
pathname和proj_id随便写,一样平常不会冲突
https://i-blog.csdnimg.cn/direct/26d2d4b32607452fa377d308e6930c37.png
获取共享内存
https://i-blog.csdnimg.cn/direct/baa3959048c94670a16ca19f80c895d0.png
创建一个key
shmget中的三个参数,第一个key必要先创建一个key,-1创建失败,创建失败最好再把日志打印出来(用c_str!!!!!!!!)
https://i-blog.csdnimg.cn/direct/77b7fdcb2c9e4ee6a4bf445319f3558f.png
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>

using namespace std;

const string pathname = "/home/ljw";
const int proj_id = 0x99999;

int GetShareMen()
{
    int shmid = shmget();
}

key_t GetKey()
{
    key_t k = ftok(pathname.c_str(), proj_id);
    if(k < 0)
    {
      exit(1);
    }
    return k;
} 把log.hpp(日志)拷贝进来,日志也写上,可变参数和printf类似
https://i-blog.csdnimg.cn/direct/7e1e8ac391e94844897e9262e079d639.png
https://i-blog.csdnimg.cn/direct/cda840fd17bd43cd92dedcee5b53969a.png
Info就是正常创建成功了,log.hpp中默认是往屏幕上打印     
https://i-blog.csdnimg.cn/direct/f72aab3c1163422b984982717e13eb58.png

key有后就可以美满GetShareMem中的shmget
GetShareMem,key有了,共享内存也创建成功了,创建成功返回共享内存的标识符 ,失败-1返回,创建一个全新的共享内存
先把第二个参数创建一下
https://i-blog.csdnimg.cn/direct/f043addbaf7c4ceab96f71413f51de54.png
https://i-blog.csdnimg.cn/direct/525a15fe0c3d40819639d169656babab.png
返回共享内存的标识符
创建成功了,就Info,返回共享内存的标识符
https://i-blog.csdnimg.cn/direct/53db32ca26a04895aa019c753c0a1c1b.png

两个文件里都是共同的头文件,调用共同的key
https://i-blog.csdnimg.cn/direct/f10a8e33d89146a48cfcaed2686bff84.png
 照旧为了这个本质
https://i-blog.csdnimg.cn/direct/3287103810b44c8d95e2dd571aff3733.png

测试一下
https://i-blog.csdnimg.cn/direct/2e38c92a066941fba6a29e76f92670fb.png
创建失败的话可以改一下数字
https://i-blog.csdnimg.cn/direct/5d35f08139034a15844ddd15423958ad.png
https://i-blog.csdnimg.cn/direct/72446a76ea7c4014896a236ed5fb4441.png

key  vs  shmid
https://i-blog.csdnimg.cn/direct/78973cd050e54f70892a19e51158a717.png
历程退了共享内存还存在

ipcs -m 检察共享内存的资源,历程退了还存在
https://i-blog.csdnimg.cn/direct/fac9642a31e34afdb5a53680499a0cb1.png
https://i-blog.csdnimg.cn/direct/1e8ba244ebb74d2e80d2be5e52d36f70.png

关掉共享内存,用shmid删除(ipcrm)不行要用key
用户层统一用shmid删除,key有操纵体系控制
https://i-blog.csdnimg.cn/direct/9b6fba3174a840ff9ed46d410fc50b45.png

16进制打印key
https://i-blog.csdnimg.cn/direct/c6f286c5a725486abd219f4560f7596b.png

perms权限问题和nattch关联问题
https://i-blog.csdnimg.cn/direct/83b6fb50688947c89c3f741cdf2be82a.png
添加权限
https://i-blog.csdnimg.cn/direct/baa7f7636c8b464bb6161ec10c377c72.png
https://i-blog.csdnimg.cn/direct/736e8ccaffe64edeb73a02a47266d778.png

这样就是一个创建(全新的共享内存)一个获取(存在的共享内存)
https://i-blog.csdnimg.cn/direct/b95a65fdae3749dfad89bb8729d18b50.png
创建新的就CreateShm
https://i-blog.csdnimg.cn/direct/f533f616ef1641a4beee328e706f9b54.png

内存大小
申请多少就能用多少,虽然操纵体系给了2倍的
https://i-blog.csdnimg.cn/direct/8c5c1bb9e84e4b42a2a590533656136e.png
shmat叫做挂接 
让当前历程和指定的共享内存进行关联起来
第一个:参数就是上面获取的shmid,
第二个:你想挂接到共享区的什么位置,不知道,由体系去决定就可以了,填null
第三个:共享内存的默认权限,设置为0就行
返回值是void*,跟malloc类似,必要强转,在假造地点空间创建的
https://i-blog.csdnimg.cn/direct/49f0cb13efd94e439a4b752d8b84d704.png
挂接起来:attach挂接
运行后,CreateShm创建一个共享内存,创建好以后,5秒以后,就会发现自己的历程就把当前的共享内存挂接到地点空间了,挂接到地点空间之后,就能看到我们对应的,nattch的0就会变1,又过5秒历程退出后,nattch就酿成0了
https://i-blog.csdnimg.cn/direct/06b4d56399f94040ae7b10464b56c1f3.png

去关联shmdt
https://i-blog.csdnimg.cn/direct/6aee3d044736467abdc15df90f21e01f.png
只必要起始的地点就可以释放,返回值也是成功返回0,失败返回1 
去关联
https://i-blog.csdnimg.cn/direct/aaaa8b3c04934180a7e85d4223840a42.png

申请内存,关联,去关联完成


释放共享内存(shmctl)
第一个:参数,共享内存的ip
https://i-blog.csdnimg.cn/direct/aadbac0d34874905a74658f4ebefd4ea.png
https://i-blog.csdnimg.cn/direct/30ebadeee9524005b2dea894aa5f8388.png
#include "comm.hpp"

int main()
{
    int shmid = CreateShm();
    log(Debug, "createShm done...");

    sleep(5);

    char *shmaddr = (char*)shmat(shmid, nullptr, 0);
    log(Debug, "attach shm done...");
    sleep(5);

    shmdt(shmaddr);
    log(Debug, "detach shm done,shmaddr:0x%x...", shmaddr);
    sleep(5);

    shmctl(shmid, IPC_RMID, nullptr);
    log(Debug, "destory shm done,shmaddr:0x%x...", shmaddr);
    sleep(5);


    return 0;
} 开始让两个历程通信
a来历程创建,b就不用创建,只必要获取就行,也要挂接
b中也不用删除,a删除就可以
https://i-blog.csdnimg.cn/direct/bc48e4ef62d34b1496ac4af1df25fe56.png
https://i-blog.csdnimg.cn/direct/ced6429e3def4feea7557153255e1ec4.png
https://i-blog.csdnimg.cn/direct/0000c1dd5b9d474ca9becb61ab0617ed.png

测试
要让a先跑要创建,两个都挂接成功nattch就酿成2了

目前为止还没有通信
有没有通信呢???没有!
让差别历程看到同一个共享资源!

a的通信代码在这里
https://i-blog.csdnimg.cn/direct/f09cb223b39944ba86338afa264f97da.png

b的在这里
https://i-blog.csdnimg.cn/direct/2a87357fcbc74dbca17154d16deae98b.png

写一个最简单的通信
这就是通信了
https://i-blog.csdnimg.cn/direct/e8bbbf7b9c124e3e9d28d706048dcc5a.png
b中输入,这样就可以通信了
 1和2的区别
https://i-blog.csdnimg.cn/direct/87ba42f93db74f0b90dede6151a78f4c.pnghttps://i-blog.csdnimg.cn/direct/3a6787b7140f4b3bbc70788d2f472d19.pnghttps://i-blog.csdnimg.cn/direct/4ce56c7f66b14c23b2bb51f8ea0c4bd9.png
https://i-blog.csdnimg.cn/direct/ee09ddf8057a4d31ae4988007198bfc8.png
b中这样更简单,直接写进去共享内存里
https://i-blog.csdnimg.cn/direct/83b8b74638024047aa3d19bf15b43a16.png
processa会一直读,不会管b写没写
https://i-blog.csdnimg.cn/direct/591f7cb154ad4309a38687503d2fbf1c.png
小结
a不会等待b,a会一直读,不会跟管道一样,没数据还会等等你https://i-blog.csdnimg.cn/direct/aba7e4797f554de3a621ea6415598c00.png
https://i-blog.csdnimg.cn/direct/2fd7bc91288341388472e74a0afd193e.png
https://i-blog.csdnimg.cn/direct/bcb36ddbdd0343cdad4c9e3c0cdd6520.png

为什么速度最快:拷贝少

跟管道比
首先自己要用用户层缓冲区,还得把用户层缓冲区拷贝到管道里,(从键盘里输入数据到用户层缓冲区里面),然后用户层缓冲区通过体系调用(write)写到管道里,然后再通过read体系调用,被对方(读端)读取,就要从管道拷贝到读端,然后再表现到表现器上。

 看看共享内存的属性(没有同步机制)
管道有同步机制
https://i-blog.csdnimg.cn/direct/e49616216ab748ac963c5f14bef67df3.png
获取共享内存的属性
https://i-blog.csdnimg.cn/direct/19791c25ce28420f8b8268f7a6ba7990.png

把管道移入(实现同步问题)(用管道关照)
把这个(创建和释放管道)
https://i-blog.csdnimg.cn/direct/fd89b3afb7dd4069b4eb6aa98922faee.png放进这里 
https://i-blog.csdnimg.cn/direct/d648884bf7704b63a686a8d57356121d.png
再打开管道文件,就可以对管道文件进行读写了
https://i-blog.csdnimg.cn/direct/341cfd47b1844ab3aa1fa93f15f5bf72.png

记得关掉
https://i-blog.csdnimg.cn/direct/26b8366412ef4756a7c69be900297300.png

b中以写的方式打开

两边通信要怎么通呢
b中往管道里写入一个字符
https://i-blog.csdnimg.cn/direct/96626d4e2e154049b6e83563155752f2.png
a中的写这个进行读,b中假如不输入,则a会一直在read这阻塞
https://i-blog.csdnimg.cn/direct/6768f54c6cfd4782bf78f70c826f3b8b.png

测试:先a运行,创建好共享内存,但阻塞了,等b输入
阻塞在这,不会像只有共享内存那样一直读
https://i-blog.csdnimg.cn/direct/c1162cca239747c9a691702897afec0a.png
b输入后a才会表现,不输入,你就要等我
https://i-blog.csdnimg.cn/direct/7d24f2b98cb440ca8bcead6a25708178.png
b(ctrl C)后,read就读到0了,就break了(实现同步问题)以为原来,a不管b写不写,都会读

用到的体系调用接口
创建成功返回一个消息队列标识符,否则返回-1
https://i-blog.csdnimg.cn/direct/6edbe33ec05d439f9930b948ef205400.png
消息队列和信号量(通信方式)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 历程间通信大总结Linux