首页 > 系统相关 >Linux系统编程——进程间通信:管道(pipe)

Linux系统编程——进程间通信:管道(pipe)

时间:2022-09-28 21:35:55浏览次数:49  
标签:pipe pid 间通信 管道 fd Linux 进程 include


管道的概述

管道也叫无名管道,它是是 UNIX 系统 IPC(进程间通信) 的最古老形式,所有的 UNIX 系统都支持这种通信机制。


无名管道有如下特点:

1、半双工,数据在同一时刻只能在一个方向上流动。

2、数据只能从管道的一端写入,从另一端读出。

3、写入管道中的数据遵循先入先出的规则。

4、管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。

5、管道不是普通的文件,不属于某个文件系统,其只存在于内存中。

6、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。

7、从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据。

8、管道没有名字,只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。 


对于无名管道特点的理解,我们可以类比现实生活中管子,管子的一端塞东西,管子的另一端取东西。


无名管道是一种特殊类型的文件,在应用层体现为两个打开的文件描述符。

Linux系统编程——进程间通信:管道(pipe)_多任务编程


管道的操作

所需头文件:

#include <unistd.h>


int pipe(int filedes[2]);

功能:

创建无名管道。

参数:

filedes: 为 int 型数组的首地址,其存放了管道的文件描述符 filedes[0]、filedes[1]。


当一个管道建立时,它会创建两个文件描述符 fd[0] 和 fd[1]。其中 fd[0] 固定用于读管道,而 fd[1] 固定用于写管道。一般文件 I/O 的函数都可以用来操作管道( lseek() 除外)。

返回值:

成功:0

失败:-1


下面我们写这个一个例子,子进程通过无名管道给父进程传递一个字符串数据:


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

int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;

if( pipe(fd_pipe) < 0 ){// 创建无名管道
perror("pipe");
}

pid = fork(); // 创建进程
if( pid < 0 ){ // 出错
perror("fork");
exit(-1);
}

if( pid == 0 ){ // 子进程
char buf[] = "I am mike";
// 往管道写端写数据
write(fd_pipe[1], buf, strlen(buf));

_exit(0);
}else if( pid > 0){// 父进程
wait(NULL); // 等待子进程结束,回收其资源

char str[50] = {0};

// 从管道里读数据
read(fd_pipe[0], str, sizeof(str));

printf("str=[%s]\n", str); // 打印数据
}

return 0;
}


运行结果如下:


Linux系统编程——进程间通信:管道(pipe)_无名管道_02


管道的特点

每个管道只有一个页面作为缓冲区,该页面是按照环形缓冲区的方式来使用的。这种访问方式是典型的“生产者——消费者”模型。当“生产者”进程有大量的数据需要写时,而且每当写满一个页面就需要进行睡眠等待,等待“消费者”从管道中读走一些数据,为其腾出一些空间。相应的,如果管道中没有可读数据,“消费者” 进程就要睡眠等待,具体过程如下图所示:

Linux系统编程——进程间通信:管道(pipe)_Linux_03



默认的情况下,从管道中读写数据,最主要的特点就是阻塞问题(这一特点应该记住),当管道里没有数据,另一个进程默认用 read() 函数从管道中读数据是阻塞的。


测试代码如下:


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

int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;

if( pipe(fd_pipe) < 0 ){// 创建无名管道
perror("pipe");
}

pid = fork(); // 创建进程
if( pid < 0 ){ // 出错
perror("fork");
exit(-1);
}

if( pid == 0 ){ // 子进程

_exit(0);
}else if( pid > 0){// 父进程

wait(NULL); // 等待子进程结束,回收其资源

char str[50] = {0};

printf("before read\n");

// 从管道里读数据,如果管道没有数据, read()会阻塞
read(fd_pipe[0], str, sizeof(str));

printf("after read\n");

printf("str=[%s]\n", str); // 打印数据
}

return 0;
}


运行结果如下:


Linux系统编程——进程间通信:管道(pipe)_#include_04

当然,我们编程时可通过 fcntl() 函数设置文件的阻塞特性。

设置为阻塞:fcntl(fd, F_SETFL, 0);

设置为非阻塞:fcntl(fd, F_SETFL, O_NONBLOCK);


测试代码如下:


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

int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;

if( pipe(fd_pipe) < 0 ){// 创建无名管道
perror("pipe");
}

pid = fork(); // 创建进程
if( pid < 0 ){ // 出错
perror("fork");
exit(-1);
}

if( pid == 0 ){ // 子进程

sleep(3);

char buf[] = "hello, mike";
write(fd_pipe[1], buf, strlen(buf)); // 写数据

_exit(0);
}else if( pid > 0){// 父进程

fcntl(fd_pipe[0], F_SETFL, O_NONBLOCK); // 非阻塞
//fcntl(fd_pipe[0], F_SETFL, 0); // 阻塞

while(1){
char str[50] = {0};
read( fd_pipe[0], str, sizeof(str) );//读数据

printf("str=[%s]\n", str);
sleep(1);
}
}

return 0;
}


运行结果如下:


Linux系统编程——进程间通信:管道(pipe)_Linux_05


默认的情况下,从管道中读写数据,还有如下特点(知道有这么回事就够了,不用刻意去记这些特点):

1)调用 write() 函数向管道里写数据,当缓冲区已满时 write() 也会阻塞。


测试代码如下:


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

int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;

char buf[1024] = {0};
memset(buf, 'a', sizeof(buf)); // 往管道写的内容
int i = 0;

if( pipe(fd_pipe) < 0 ){// 创建无名管道
perror("pipe");
}

pid = fork(); // 创建进程
if( pid < 0 ){ // 出错
perror("fork");
exit(-1);
}

if( pid == 0 ){ // 子进程
while(1){
write(fd_pipe[1], buf, sizeof(buf));
i++;
printf("i ======== %d\n", i);
}

_exit(0);
}else if( pid > 0){// 父进程

wait(NULL); // 等待子进程结束,回收其资源
}

return 0;
}


运行结果如下:

Linux系统编程——进程间通信:管道(pipe)_系统编程_06


2)通信过程中,别的进程先结束后,当前进程读端口关闭后,向管道内写数据时,write() 所在进程会(收到 SIGPIPE 信号)退出,收到 SIGPIPE 默认动作为中断当前进程。


测试代码如下:


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

int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;

if( pipe(fd_pipe) < 0 ){// 创建无名管道
perror("pipe");
}

pid = fork(); // 创建进程
if( pid < 0 ){ // 出错
perror("fork");
exit(-1);
}

if( pid == 0 ){ // 子进程
//close(fd_pipe[0]);

_exit(0);
}else if( pid > 0 ){// 父进程

wait(NULL); // 等待子进程结束,回收其资源

close(fd_pipe[0]); // 当前进程读端口关闭

char buf[50] = "12345";

// 当前进程读端口关闭
// write()会收到 SIGPIPE 信号,默认动作为中断当前进程
write(fd_pipe[1], buf, strlen(buf));

while(1); // 阻塞
}

return 0;
}


运行结果如下:


Linux系统编程——进程间通信:管道(pipe)_系统编程_07



标签:pipe,pid,间通信,管道,fd,Linux,进程,include
From: https://blog.51cto.com/u_3002289/5720963

相关文章

  • Linux高级网络开发奇妙之旅
    一、基础理论篇​​01、网络协议入门​​​​02、LAN、WAN、WLAN、VLAN和VPN的区别​​​​03、IP地址介绍​​​​04、广播地址介绍​​​​05、无连接和面向连接协议......
  • 一步步学习Linux开发环境搭建与使用
    ​​00、Linux开发环境搭建与使用1——Linux简史​​​​01、Linux开发环境搭建与使用2——Linux系统(ubuntu)安装方案​​​​02、Linux开发环境搭建与使用3——通过虚拟机......
  • Linux用户管理
    Linux用户管理基本介绍Linux系统是一个多用户多任务的操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统添加用户......
  • windows像linux般使用gcc,make等工具
    ​​需要安装MinGW编译器。​​MinGW是一个可自由使用和自由发布的Windows特定头文件和使用GNU工具集导入库的集合,允许你在GNU/Linux和Windows平台生成本地的Windows程......
  • Linux 网络编程——UDP编程
    概述UDP是UserDatagramProtocol的简称,中文名是用户数据报协议,是一个简单的面向数据报的运输层协议,在网络中用于处理数据包,是一种​​无连接的协议​​。UDP不提供可......
  • Linux 网络编程——套接字的介绍
    套接字是一种通信机制(通信的两方的一种约定),凭借这种机制,不同主机之间的进程可以进行通信。我们可以用套接字中的相关函数来完成通信过程。套接字的特性有三个属性确定,它们是......
  • Linux 线程浅析
    进程和线程的区别与联系在许多经典的操作系统教科书中,总是把进程定义为程序的执行实例,它并不执行什么,只是维护应用程序所需的各种资源,而线程则是真正的执行实体。为了让进......
  • Linux 网络编程—— libnet 使用指南
    概述​​通过《原始套接字实例:发送UDP数据包》的学习​​,我们组UDP数据包时常考虑字节流顺序、校验和计算等问题,有时候会比较繁琐,那么,有没有一种更简单的方法呢?答案是:借......
  • Linux 网络编程——网络字节序、地址转换
    网络字节序故事的起源“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中......
  • Linux 进程调度浅析
    概述操作系统要实现多进程,进程调度必不可少。有人说,进程调度是操作系统中最为重要的一个部分。我觉得这种说法说得太绝对了一点,就像很多人动辄就说“某某函数比某某函数效率......