01进程间通信——管道(✔)

IPC (进程间通信):Internal  Process Communication

实质:信息 (数据)的交换

有人就说,这还不简单吗?

        我在一个进程里面定义一个全局变量a,然后再给a赋值一些有含义的值,然后再让另一个进程去读取这个a的值,不就实现通信了吗?

这个想法,可以吗?

        肯定是不可以的,因为进程的地址空间是独立的 (虚拟地址),解决进程间通信的问题,就需要借助双方都可以访问的第三方

所以,如果两个进程要通信,必须把数据放到一个大家都可以访问 (读,写)到的地方


文件?   一个文件可以被多个进程同时打开

        可以,文件在文件系统中,大家都可以访问,但是这种方式,有一个缺点:通信速度太慢了

================>

在操作系统内核中去开辟一段空间 (某一种机制),进程去访问它


IPC方式:

        管道

                pipe无名管道

                fifo有名管道

        信号

        消息队列    System V消息队列 / POSIX消息队列

        信号量        System V信号量 / POSIX信号量

        共享内存    System V共享内存 / POSIX共享内存

        网络            socket

管道

很久很久以前,进程间通信的方式,都是通过文件!

这种方式,他有一个缺点:

        它的效率太低了

但是这种方式,有一个非常大的好处:

        简单,不需要提供额外的API函数接口 (直接利用文件系统的API函数)


有人想,能不能改进一下?

        问题在哪里?文件内容在外设,文件系统上,造成访问效率低

能不能把文件的内容放到内核或者内存中去呢?

        管道:管道文件 (内容放到内核或者内存中),又可以利用文件系统的API 函数


PIPE  无名管道

        它在文件系统中是没有名字 (没有inode......)的,它的内容在内核中 (而不是存在于硬盘),访问pipe的方式是通过文件系统的 API (read / write)

        
        fd = open(文件名,)

        read(fd,) / write(fd, )

        close()

        它没有文件名,无法通过路径索引到相应的文件内容,不能使用open函数打开,但是read / write又需要一个文件描述符 !!!
        

        所以在创建这个pipe的时候,就必须要返回文件描述符 !!!

        
        pipe在创建的时候,在内核中开辟一块缓冲区,作为pipe文件的内容的存储空间,同时返回两个文件描述符 (一个用来读,一个用来写)  

       并且它还有如下特点:

        (1) pipe有两端,一端是用来写,一端是用来读

        (2) 内容读走了,就没有了 (不存在于pipe文件中)

        (3) 按顺序读,不支持lseek的

        (4) pipe (无名管道)随内核的持续性

        

无名管道的创建:pipe

NAME
    pipe, pipe2 - create pipe

SYNOPSIS
    #include <unistd.h>

// pipe是用来创建一个无名管道的,pipefd用来保存创建好的无名管道的两个文件描述符
// pipe创建的管道,默认是"阻塞模式"
int pipe(int pipefd[2]);

pipefd:数组,指向一段可用的空间
    pipefd[0]:保存读的文件描述符
    pipefd[1]:保存写的文件描述符

返回值:
    成功返回0
    失败返回-1,同时errno被设置
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
    int pipefd[2] = {0};
    int r = pipe(pipefd);
    if (r == -1) {
        perror("create pipe failed");
        return -1;
    }
    // printf("pipefd[0] = %d\n", pipefd[0]);
    // printf("pipefd[1] = %d\n", pipefd[1]);
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork error");
        return -1;
    } else if (pid == 0) {
        // 子进程
        char buf[32] = {"hello father"};
        write(pipefd[1], buf, 32);
        exit(0);
    } else {
        // 父进程
        wait(NULL); // 等待子进程结束
        // 无名管道,随内核持续性
        char buf[32] = {0};
        read(pipefd[0], buf, sizeof(buf));
        printf("message: %s\n", buf);
    }
    return 0;
}

思考:

        pipe(无名管道)是不是任意两个进程都可以使用pipe来通信呢?或者只能用于父子进程呢?

                只要两个进程能够获取同一个pipe的文件描述符就可以使用pipe来通信

                一般是用于有亲缘关系的进程

pipe本身是全双工的,但是两个进程使用一个管道去实现全双工的通信,就必须以某一种方式去同步,不然自己很容易读到自己写入的数据

通常在工程中,一般做成两个或者多个管道,一个用来读,一个用来写,人为的把pipe当成是半双工,同时收发容易造成自发自收


通信方式:

        全双工:通信双方可以同时进行数据接收和发送,比如:视频电话

        半双工:通信双方,同一时刻,只能有一方进行发送,另一方接收,比如:对讲机

        单工:通信双方中,一方固定为发送端,一方则固定为接收端,信息只能沿着一个方向                     传输,比如广播
------------------------------------------------------------------------------------------------------------------------

pipe (无名管道)只能用于有亲缘关系的进程间通信

        原因就是因为pipe在文件系统中没有名字 (inode),无法通过open去获取文件描述符


假如它在文件系统中有一个"名字",它是不是就可以用于任意两个进程间通信呢?

        可以,既然有名字,就可以通过open去获取文件描述符,而不是一定要通过"继承父进程"数据的方式 —— fifo:有名管道


FIFO  有名管道

fifo 是在 pipe 的基础上,给fifo在文件系统中创建一个 inode (它在文件系统中就会有一个文件名),但是文件的内容却在内核中 !!!

====================>

fifo的文件名是随文件系统的持续性 (不删除就存在)

fifo的内容存在于内核,随内核的持续性


fifo 和 pipe 一样,但是 fifo 在文件系统中有一个文件名

操作 fifo 就和操作普通文件类型一样:

        open

        read / write

        close


fifo 文件是如何创建的呢? 

        注意:不要在共享文件夹中创建管道文件,因为共享文件夹只是挂载在windows下,而windows不支持管道文件


有名管道的创建:

        (1) 用户命令   mkfifo   —— 查看:mkfifo --help

                例如:mkfifo  ~/SC/mkfifo.txt

                

        (2) linux  API 函数接口  mkfifo

NAME
    mkfifo, mkfifoat - make a FIFO special file (a named pipe)
SYNOPSIS
    #include <sys/types.h>
    #include <sys/stat.h>

// mkfifo用来在文件系统中创建一个 fifo(有名字的管道)的入口(inode)
int mkfifo(const char *pathname, mode_t mode);

    pathname:路径名,要创建的有名管道,在文件系统中的名字(一般带路径)
    
    mode:权限,创建的有名管道的权限,有两种方式指定:
        a. S_IRUSR ...
        b. 0664 ...
    
    返回值:
        成功返回0
        
        失败返回-1,同时errno被设置
        errno == EEXIST   路径名已经存在
NAME
    fifo - first-in  first-out  special file, named pipe
DESCRIPTION
    FIFO(有名管道)和 PIPE(无名管道)类似,只不过 FIFO 在文件系统中存在一个 
inode,它可以被多个进程打开用来读和写,当进程用 FIFO 来交换数据的时候,内核根本
就没有把数据写入到文件系统中去,而是保存在内核的内部,所以 FIFO 在文件系统中没有
内容,仅仅作为文件系统的一个引用入口,提供一个文件名给其他的进程去 open 它

    在数据交换前,FIFO的两端(read / write)必须都被打开,通常情况下,你打开FIFO
的一端,会阻塞,直到另外一端也被打开

    一个进程也可以以"非阻塞"(O_NONBLOCK)方式去打开,这种情况下(以"非阻塞"方式
去打开),只读打开总会成功,即便写端没有被打开
只写打开总会失败,并且errno == EENXIO,除非读端已打开

阻塞模式:
    阻塞的读或者写
        读的时候,如果没有数据,则read会阻塞(等待),如果管道的写端被关闭,
                                               read会立即返回,返回值为0
                
        写的时候,如果没有空间,则write会阻塞(等待)

非阻塞模式:
    非阻塞的读或者写
        读的时候,如果没有数据,则read会立即返回,返回值为0
        写的时候,如果没有空间,则write会立即返回
// gcc read.c -o read
// ./read /home/china/SC/fifo

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

int main(int argc, char *argv[]) {
	if (argc != 2) {
		printf("input error\n");
		return -1;
	}

	int r = mkfifo(argv[1], 0664);
	if (-1 == r) {
		if (errno != EEXIST) {
			perror("mkfifo error");
			return -1;
		}
	}
	printf("mkfifo success\n");

	printf("%s:%d\n", __FUNCTION__, __LINE__);

	// 操作管道 open read close
	int fifofd = open(argv[1], O_RDONLY); // 只读打开
	if (-1 == fifofd) {
		perror("open fifo error");
		return -1;
	}

	printf("%s:%d\n", __FUNCTION__, __LINE__);

	char buf[10] = {0};
	r = read(fifofd, buf, 10);
	printf("r = %d\n",r);
	printf("buf = %s\n", buf);

	printf("%s:%d\n", __FUNCTION__, __LINE__);

    close(fifofd);
	return 0;
}

// mkfifo success
// main:27
// main:36
// r = 6
// buf = hello
// main:43
// gcc write.c -O write
// ./write ~/SC/fifo

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

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("input error\n");
        return -1;
    }

    int r = mkfifo(argv[1], 0664);
    if (r == -1) {
        if (errno != EEXIST) {
            perror("mkfifo error");
            return -1;
        }
    }
    printf("mkfifo success\n");
    
    printf("%s:%d\n", __FUNCTION__, __LINE__);

    int fifofd = open(argv[1], O_WRONLY); // 只写打开
    if (fifofd == -1) {
        perror("open fifo error");
        return -1;
    }

    printf("%s:%d\n", __FUNCTION__, __LINE__);

    char buf[10] = "hello";
    r = write(fifofd, buf, strlen(buf) + 1);
    printf("r = %d\n", r);

    printf("%s:%d\n", __FUNCTION__, __LINE__);

    close(fifofd);
    return 0;
}

// mkfifo success
// main:27
// main:35
// r = 6
// main:41

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

int main(int argc, char *argv[]) {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork error");
        return -1;
    } else if (pid == 0) { // 子进程
        int fifofd = open(argv[1], O_WRONLY); // 只写打开
        if (fifofd == -1) {
            perror("open fifo error");
            return -1;
        }
        char buf[10] = "hello";
        int w = write(fifofd, buf, strlen(buf) + 1);
        printf("w = %d\n", w);
        close(fifofd);
    } else { // 父进程
        // 操作管道 open read close
        int fifofd = open(argv[1], O_RDONLY); // 只读打开
        if (-1 == fifofd) {
            perror("open fifo error");
            return -1;
        }
        wait(NULL);
        char buf[10] = {0};
        int r = read(fifofd, buf, 10);
        printf("r = %d\n",r);
        printf("%s\n", buf);
        close(fifofd);
    }
    return 0;
}

// w = 6
// r = 6
// hello
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值