进程间通信(1) 管道

进程间通信

首先我们先来了解一下进程间通信。

进程间通信目的:

    1.数据传输:一个进程需要将它的数据发送给另一个进程

    2.资源共享:多个进程之间共享同一份资源

    3.通知事件:一个进程需要向另一个或一组进程发送消息,通知他们发生了某种事件

    4.进程控制:有些进程希望完全控制另一给进程的执行(如Debug进程),此时控制希望能够拦截另一个进程的所有陷入和异常,并能够即使知道它的状态改变。

进程间通信分类:

    管道

            匿名管道

            命名管道

    System IPC

            System V 消息队列

            System V 共享内存

            System V 信号量

POSIX IPC

             消息队列

             共享内存

             信号量

             互斥量

             条件变量

             读写锁

而今天我们先学习一下管道。

     首先我们要了解一下什么是管道呢?管道它是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

匿名管道

#include<unistd.h>
功能:创建一个无名管道
原型
  int pipe(int fd[2]);
参数
  fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码

这个时候我们来看一个例题:

    例:从键盘读取数据,写⼊入管道,读取管道,写到屏幕

#include<stdio.h>  
#include<stdlib.h>  
#include<unistd.h>  
#include<string.h>  
  
int main(void)  
{  
    int fds[2];  
    char buf[100];  
    int len;  
    if (pipe(fds) == -1)  
        perror("make pipe"), exit(1);  
    //读取数据  
    while (fgets(buf, 100, stdin)){  
        len = strlen(buf);  
    //写入管道  
        if (write(fds[1], buf, len) != len){  
            perror("write to pipe");  
            break;  
        }  
        memset(buf, 0x00, sizeof(buf));  
    //读取管道  
        if ((len = read(fds[0], buf, 100)) == -1){  
            perror("read from pipe");  
            break;  
        }  
    //写到屏幕  
        if (write(1, buf, len) != len){  
            perror("write to stdout");  
            break;  
        }  
    }  
}  

(1)用fork来共享管理管道的原理




(2)站在文件描述符角度来深度理解管道

    1、父进程创建



    2、父进程fork出子进程


    3、父进程关闭fd[0],子进程关闭fd[1]


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int fd[2] = {0};
    if(pipe(fd)==-1)
    {
        perror("pipe");
        return 1;
    }
    pid_t pid = fork();
    if(pid==-1)
    {
        perror("fork");
        return 2;
    }
    else if(pid==0)
    {//child write
        close(fd[0]);
        char *msg="father,I am child";
        int i = 5;
        while(i--)
        {
            write(fd[1],msg,strlen(msg));
            sleep(1);
        }
    }
    else
    {//father read
        close(fd[1]);
        char buf[1024];
        while(1)
        {
            ssize_t s = read(fd[0],buf,sizeof(buf));
            if(s>0)
            {
                buf[s]=0;
                printf("father: %s\n",buf);
            }
            else if(s==0)
            {
                printf("child quit!!!\n");
                break;
            }
            else
            {
                perror("read");
                return 3;
            }
        }
    }
    return 0;
}

结果如下:


管道的特点

  • 1、只能用于具有亲缘关系的进程之间进行通信(常为父子);通常一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可用该管道。
       2、管道的生命周期随进程
       3、内核会对管道进行同步或异步机制

       4、管道是半双工的,数据只能向一个方向流动;需要双方通信时,需建立起两个管道

       5、管道通信基于字节流


   

                                               利用两个管道实现双向通信  如上图

管道的读写规则:

    1、写端的文件描述符关闭,读端一直读

    2、写端的文件描述符没关闭,但写端也不写数据,而读端一直读

    3、写端一直写数据,读端不读,也不关闭它的文件描述符

    4、写端写数据,而读端的文件描述符关闭

命名管道

    管道应用的一个限制就是只能在具有相同祖先(具有亲缘关系)的进程间通信。

    如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

    命名管道是一种特殊类型的文件。

首先我们要知道如何创建一个管道呢? 创建管道由两种方法,第一种是命令创建,第二种是函数创建;现在我们分别来看一下怎么创建一个管道:

命令行:

$ mkfifo filename


函数原型:

int mkfifo(const char *filename,mode_t mode);

创建成功返回0,创建失败返回-1;

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

int main()
{
    if(mkfifo("fifo",0644)==-1)
    {
        perror("mkfifo");
        return 1;
    }
    return 0;
}

用命名管道实现client/server通信:

直接看代码:

server.c

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


int main()
{
    if(mkfifo("mypipe",0644)==-1)//创建命名管道
    {
        perror("mkfifo");
        return 1;
    }
    int rfd = 0;
    if((rfd = open("mypipe",O_RDONLY))==-1)//以只读方式打开管道
    {
        perror("open");
        return 2;
    }
    char buf[1024];
    while(1)
    {
        ssize_t s = read(rfd,buf,sizeof(buf)-1);//从管道中读数据
        if(s>0)
        {
            buf[s-1]=0;
            printf("client say:%s\n",buf);
        }
        else if(s==0)
        {
            printf("client quit!!!\n");
            break;
        }
        else
        {
            perror("read");
            return 3;
        }
    }
    return 0;
}

client.c

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


int main()
{
    int wfd = 0;
    if((wfd = open("mypipe",O_WRONLY))==-1)//以只写方式打开管道
    {
        perror("open");
        return 1;
    }
    char buf[1024];
    while(1)
    {
        printf("Please Enter:");
        //从标准输入读取数据
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf)-1);
        if(s>0)
        {
            buf[s]=0;
            write(wfd,buf,strlen(buf));//将读取到的数据写进管道
        }
        else if(s==-1)
        {
            perror("write");
            return 2;
        }
    }
    return 0;
}

由于两个代码需要编译,这里我们写一个Makefile

代码如下:

.PHONY:all
all:client server
client:client.c
	gcc -o $@ $^
server:server.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm -f client server

测试结果如下:

1、首先我们先编译代码,输入make即可


2、然后我们输入./server ,这时就会创建一个管道,然后再打开一个终端,输入ls,此时我们会发现管道已经创建好


3、现在我们再这个新的终端上输入./client,就可以进行通信了



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值