管道

1、管道的概念

本质:

  • 内核缓冲区,打开管道时会在内存中分配一块空间
  • 伪文件--不占用磁盘空间

特点:

  • 两部分:读端,写端,对应两个文件描述符
  • 数据写端流入,读端流出
  • 操作管道的进程被销毁之后,管道自动被释放
  • 管道只读或只写打开时会阻塞。需要两个进程同时打开,一个读,一个写。
  • 当写端关闭时,读端不会阻塞

管道的原理:

  • 内部实现方式:队列(环形队列)。
  • 管道中有头指针:负责写。
  • 管道中有尾指针:负责读。
  • 刚开始头尾指针都指向头。
  • 写数据是头指针不断向后移动,管道写满后又回头部循环写入,遇到尾指针(管道满,写阻塞)则无法写入
  • 当尾指针读时遇到头指针(管道空,读阻塞)则读取结束
  • 缓冲区大小:默认4k,内核会适当的调整,但有一定范围,不会无限放大

3、管道的局限性:

  • 基于队列的数据结构,数据只能读取一次,不能重复读取。
  • 半双工(双向,但不能同时工作)
  • 匿名管道:适用于有血缘关系的进程

4、常见问题

  • 有名管道和无名管道的区别?

       无名:父子间进程通信。

       有名:任意两个进程间通信。

  • 数据在什么地方存放?

      在内存中存放,但是主要、有名的管道文件在磁盘

  • 管道时半双工还是全双工?    

      半双工

二、匿名管道

1、创建匿名管道

init pipe(int fd[2])

  • fd-传出参数
  • fd[0] -- 读端
  • fd[1] -- 写端

示例:

# include <stdio.h>
# include <errno.h>
# include <unistd.h>
# include <stdlib.h>
# include <sys/types.h>
# include <sys/stat.h>

int main()
{
    int fd[2];
    int ret = pipe(fd);


    if(ret == -1)
    {
        perror("pipe error");
        exit(0);
    }

    printf("pipe[0] = %d\n",fd[0]);
    printf("pipe[1] = %d\n",fd[1]);

    close(fd[0]);
    close(fd[1]);
    return 0;
}

2、父子进程使用管道通信

  • 单个进程可以使用管道完成读写操作
  • 先打开管道后fork子进程
  • 在父子进程利用管道通信过程中,因为数据只能读取一次,如果某一进程执行写操作,则需关闭该进程的读端,如果某一进程执行读操作,则需关闭读端。

示例:父进程查询当前所有进程信息并写入管道;子进程从管道中查询当前所有进程信息。

# include <stdio.h>
# include <errno.h>
# include <unistd.h>
# include <stdlib.h>
# include <sys/types.h>
# include <sys/stat.h>

int main()
{
    int fd[2];
    int ret = pipe(fd);//打开管道


    if(ret == -1)//管道打开失败
    {
        perror("pipe error");
        exit(0);
    }
    
    pid_t pid = fork();//创建子进程
    if(pid == -1)//子进程创建失败
    {
        printf("fork error");
        exit(0);
    }
    //父进程
    //将当前所有进程信息写入管道
    //ps默认将信息输入到终端(即标准输出STDOUT),通过dup2将进程信息输入到管道写端
    if(pid > 0)
    {
        close(fd[0]);
        dup2(fd[1],STDOUT_FILENO);
        execlp("ps","ps","aux",NULL);
        exit(0);
    }
    //子进程
    //查询管道中数据
    //grep默认从当前终端查询数据(即标准输入STDIN),通过dup2函数使其从管道中查询数据
    else if(pid == 0)
    {
        close(fd[1]);
        dup2(fd[0],STDIN_FILENO);
        execlp("grep","grep","bash",NULL);
    }

    close(fd[0]);
    close(fd[1]);
    return 0;
}

3、兄弟进程使用管道通信

原理和父子间进程通信相同

  • 父进程无需对管道进行操作,关闭读写两端
  • 子进程1执行写操作,关闭读端
  • 子进程2执行读操作,关闭写端

示例:

# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <sys/wait.h>

int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret == -1)
    {
        printf("pipe error");
        exit(1);
    }
    int i;
    for(i = 0; i < 2; i++)
    {
        pid_t pid = fork();
        if(pid == 0)//子进程不再创建进程
        {
            break;
        }
        if(pid == -1)
        {
            printf("fork error");
            exit(1);
        }
    }

    if(i == 0)//子进程1
    {
        close(fd[0]);//关闭读操作
        dup2(fd[1],STDOUT_FILENO);
        execlp("ps","ps","aux",NULL);
        perror("execlp");
        exit(1);
    }
    else if(i == 1)//子进程2
    {
        close(fd[1]);//关闭写操作
        dup2(fd[0],STDIN_FILENO);
        execlp("grep","grep","bash",NULL);
        perror("grep");
    }
    else if(i == 2)//父进程
    {
    //关闭读写操作
        close(fd[0]);
        close(fd[1]);
        pid_t wpid;
        while((wpid = waitpid(-1,NULL,WNOHANG)) != -1)//循环回收子进程
        {
            if(wpid == 0)
            {
                continue;
            }
            printf("child died, pid = %d\n",wpid);
        }
    }
    return 0;
}

4、管道的读写行为

1、读操作

  • 有数据

read(fd) - 正常读,返回读出的字节数

  • 无数据

写端全部关闭时,read接触阻塞,返回0,相当于读文件读到了尾部

写端没有全部关闭时,read阻塞,等待写端写入数据。

2、写操作

  • 读端全部被关闭

管道破裂,进程被终止。读端全部被关闭时,内核会给进程发送信号SIGPIPE,进程收到该信号的默认处理是终止进程

  • 读端没有被全部关闭

缓冲区写满时,write阻塞,等待读出数据,缓冲区没满时,write继续写数据。

3、如何设置非阻塞

  • 默认读写两端都阻塞。
  • 设置读端为非阻塞:

fcntl - 变参函数  功能:(1)复制文件描述符 - dup

                                     (2)修改文件属性 - open的时候对应的flag属性

                          设置方法:(1)获取原来的flags:int flags = fcntl(fd[0],F_GETFL);

                                            (2)设置新的flags:flag |= O_NONBLOCK;

                                            (3)fcntl(fd[0], F_SETFL,flags);

5、查看管道缓冲区大小

  • 命令:ulimit -a
  • 函数:fpathconf

示例:

# include <stdio.h>
# include <errno.h>
# include <unistd.h>
# include <stdlib.h>
# include <sys/types.h>
# include <sys/stat.h>

int main()
{
    int fd[2];
    int ret = pipe(fd);


    if(ret == -1)
    {
        perror("pipe error");
        exit(0);
    }
    
    long size = fpathconf(fd[0],_PC_PIPE_BUF);//查看缓冲区大小
    printf("size = %d\n",size);//输出缓冲区大小

    close(fd[0]);
    close(fd[1]);
    return 0;
}

三、fifo(有名管道)

1、特点

  • 有名管道
  • 在磁盘上存在这样的文件
  • 伪文件,该文件在磁盘大小永远为0
  • 在内核中有一个对应的缓冲区
  • 半双工的通信方式

2、使用场景

  • 没有血缘关系的进程间通信

3、创建方式

  • 命令:mkfifo 管道名
  • 函数:mkfifo

4、fifo文件可以使用IO函数进行操作

  • open/close
  • read/write

5、进程间通信

  • 有名管道进程间的通信和文件操作相同。将管道看做是文件即可。

示例:

1、首先新建一个管道:mkfifo myfifo

2、向管道写入数据

# include <stdio.h>
# include <stdlib.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
# include <string.h>
# include <fcntl.h>

int main()
{
    char *p = "hello world";
    int fd = open("/home/FUJIA/cy1706/practice/myfifo",O_WRONLY);
    if(fd == -1)
    {
        printf("open error");
        exit(0);
    }
    while(1)
    {
        write(fd,p,strlen(p) + 1);
        sleep(1);
    }
    close(fd);
}

3、从管道读出数据

# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>

int main()
{
    int fd = open("/home/FUJIA/cy1706/practice/myfifo",O_RDONLY);
    if(fd == -1)
    {
        printf("open error");
        exit(0);
    }
    char buf[20];
    while(1)
    {
        read(fd,buf,sizeof(buf));
        printf("buf = %s\n",buf);
        char buf[20] = {};
    }
    close(fd);
    return 0;
}

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
弗洛伊德算法找出超市最佳选址位置 #include #include using namespace std; const int maxv=20; #include #include #include #include #include struct point { char name[10]; int id; int x; int y; //int dst[5]; }; struct node { char src[10]; char dst[10]; int cost; }; /////最小生成树 struct mintree//结构体记录最小生成树的相关信息 { int begin,end;//起点 终点 int length;//边的权值 }; const int maxweight=9999; class Graphm//图的矩阵表式 { public: int map[maxv][maxv]; //某点到某点两点间的的距离 int mark[maxv]; //加入进来的点的集合 point Vert[maxv];//顶点信息 int Edge[maxv][maxv];//存放边的信息 int numV;//顶点数 int numE;//边数 int path[maxv][maxv];//每对顶点之间路劲 int dist[maxv][maxv];//c每对顶点之间的路径 void shortpath(int n); mintree tree[maxv]; Graphm(); char* findbyid(int i); int findbyname(char m[]); void CreatG();//创建无向图 void print();//矩阵显示图的信息 void draw(); void Prim(); node Node[maxv]; int edgenumE; int minx(int x,int y); }; int Graphm::minx(int x,int y) { if(x<y) return x; else return y; } Graphm::Graphm()//矩阵初始化 { for(int i=0;i<maxv;i++) for(int j=0;j<maxv;j++) { if(i==j) Edge[i][j]=0; else Edge[i][j]=maxweight;} numV=0; numE=0; } int Graphm::findbyname(char m[]) { int j; for(j=0;j<numV;j++) { if(strcmp(Vert[j].name,m)==0) return Vert[j].id; } } char* Graphm::findbyid(int i) { int j; for(j=0;j<numV;j++) { if(Vert[j].id==i) return Vert[j].name; } } void Graphm::CreatG()//创建无向图 { FILE *fp;//定义文件流指针,用于打开读取的文件 int i,k; if((fp=fopen("data.txt","rb")) == NULL ) { /* 打开文件 */ printf("Cannot open file !"); return ; }//只读方式打开文件text.txt fscanf(fp,"%d",&numV);//读入点数 fscanf(fp,"%d",&numE); cout<<numV<<endl; cout<<numE<<endl; char q,z; for(i=0;i<numV;i++)//从文件中读取信息 显示到屏幕 { fscanf(fp,"%s",&Vert[i].name);//读取d顶点信息 Vert[i].id=i+1; cout<<Vert[i].name<<endl; } for(k=0;k<numE;k++)//从文件中读取信息保存到矩阵中 { int dis; char i[10],j[10]; fscanf(fp,"%s""%s""%d",i,j,&d

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值