Linux下的系统编程——进程间的通信(九)

简介: Linux下的系统编程——进程间的通信(九)

一、进程间通信常用方式

IPC方式:

       Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。

 

       在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:

1.管道(使用最简单)

2.FIFO(无血缘关系,单次读取)

3.共享映射区(无血缘关系,反复读取)

4.信号(开销最小)

5.本地套接字(最稳定)

二、管道

1.概念:

     

        管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:

(1).其本质是一个伪文件(实为内核缓冲区)

(2).由两个文件描述符引用,一个表示读端,一个表示写端,只能一次读取

(3).规定数据从管道的写端流入管道,从读端流出,单向流动

管道的原理:  管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现

管道的:

       1)数据不能进程自己写,自己读。·

       2)管道中数据不可反复读取。一旦读走,管道中不再存在。

       3)采用半双工通信方式,数据只能在单方向上流动

       4)只能在有公共祖先的进程间使用管道

常用的通信方式: 单工通信、半双工通信、全双工通信

创建管道文件:

(不占用磁盘空间)

2.pipe函数:

pipe函数:用于有血缘关系的进程间通信

函数功能:创建,并打开管道。

   int pipe(int fd[2]);

   参数:    

      fd[0]: 读端。

       fd[1]: 写端。

   返回值:

        成功: 0

        失败: -1 errno

管道通信:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
 
int main(int argc, char *argv[])
{
  int ret,re;
  int fd[2];
  pid_t pid;
  char *str = "hello pipe\n";
  char buf[1024];
 
  ret = pipe(fd);     //父进程先创建一个管道,持有管道的读端和写端
  if(ret == -1)
    sys_err("pipe error");
  
  pid = fork();        //子进程同样持有管道的读和写端
 
  if(pid > 0){            //父进程
    close(fd[0]);       //关闭读段
    write(fd[1],str,strlen(str));//写入数据
        sleep(1);
    close(fd[1]);            //关闭写段
  }else if(pid == 0){        //子进程
    close(fd[1]);          //关闭写段
    re = read(fd[0],buf,sizeof(buf)); //读取数据
    write(STDOUT_FILENO,buf,re);      //写到屏幕上
    close(fd[0]);        //关闭读段
  }
 
  return 0;
 
}

3.管道的读写行为:

读管道:

       1. 管道有数据,read返回实际读到的字节数。

       2. 管道无数据:    

                       1)无写端,read返回0 (类似读到文件尾)

                       2)有写端,read阻塞等待。

写管道:

       1. 无读端, 异常终止。 (SIGPIPE导致的)

       2. 有读端:    

                       1) 管道已满, 阻塞等待(少见)

                       2) 管道未满, 返回写出的字节个数。

1)读管道,管道无数据(无写端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
 
int main(int argc, char *argv[])
{
  int ret,re;
  int fd[2];
  pid_t pid;
  char *str = "hello pipe\n";
  char buf[1024];
 
  ret = pipe(fd);    //父进程先创建一个管道,持有管道的读端和写端
  if(ret == -1)
    sys_err("pipe error");
  
  pid = fork();        //子进程同样持有管道的读和写端
 
  if(pid > 0){            //父进程
    close(fd[0]);       //关闭读段
  //  write(fd[1],str,strlen(str));//写入数据
    close(fd[1]);            //关闭写段
  }else if(pid == 0){        //子进程
    close(fd[1]);          //关闭写段
    re = read(fd[0],buf,sizeof(buf)); //读取数据
    
    printf("child read ret =%d\n",ret);
    
    write(STDOUT_FILENO,buf,re);      //写到屏幕上
    close(fd[0]);        //关闭读段
  }
 
  return 0;
 
}

read返回0

4.父子间进程通信 :

       使用管道实现父子进程间通信,完成:ls | wc -l。假定父进程实现ls,子进程实现wc

ls | wc -l命令:

实现流程:

(1)父进程创建管道 pipe()

(2)父进程创建子进程 fork()

(3)设置父进程执行ls命令,子进程执行wc命令 execlp()

(4)设置父子进程通过管道的单项流动(设置指向标准输出的指向管道)  dup2()

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
int main(int argc,char *argv[])
{
  /***************
    dup2();
    fork();
    pipe();
    execlp();
  ****************/
  int fd[2]; 
  int ret;
  pid_t pid;
 
    //父进程创建管道
  ret = pipe(fd);     //父进程先创建一个管道,持有管道的读端和写端
  if(ret == -1){
    sys_err("pipe error");
  }
 
    //父进程创建子进程 
  pid = fork();        //子进程同样持有管道的读和写端
 
  if(pid == -1){
    sys_err("fork error");
  }else if(pid > 0){                     //父进程 读,关闭写端
    close(fd[1]);                      //关闭写,设置单项流动
    dup2(fd[0],STDIN_FILENO);          //设置读管道信息,重定向stdin 到管道读端
    execlp("wc","wc","-l",NULL);       //设置父进程wc命令
    sys_err("execlp wc error");
  }else if(pid == 0){
    
        close(fd[0]);                      //关闭读,设置单项流动
    dup2(fd[1],STDOUT_FILENO);         //设置写操作指向管道,重定向stdout 到管道写端
    execlp("ls","ls",NULL);            //设置子进程执行ls命令 
    sys_err("execlp ls error");
  }
 
  return 0;
}
 

5.兄弟间进程通信:

使用管道实现兄弟进程间通信,完成:ls | wc -l。假定父进程实现ls,子进程实现wc

实现流程:

(1)父进程创建管道 pipe()

(2)父进程创建俩个(兄弟)子进程 fork()

(3)设置兄进程执行ls命令,第进程执行wc命令 execlp()

(4)设置兄弟进程通过管道的单项流动(设置指向标准输出的指向管道)  dup2()

(5)回收父进程残余文件  wait()

刚创建出的兄弟进程:

设置兄弟进程通过管道的单项流动后

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
int main(int argc,char *argv[])
{
  /***************
    dup2();
    fork();
    pipe();
    execlp();
        wait();
  ****************/
  int fd[2]; 
  int ret;
  int i;
  pid_t pid;
 
    //父进程创建管道
  ret = pipe(fd);
  if(ret == -1){
    sys_err("pipe error");
  }
 
  for(i = 0;i < 2;i++){              //表达式2 出口,仅限父进程使用
    pid = fork();
    if(pid == -1){
      sys_err("fork error");
    }
    if(pid == 0)                   //子进程出口
      break;
  }
 
 
 
  if(i == 2){                        //父进程 不参与管道使用
        //不需要父进程所以需要关闭他管道的读写端
    close(fd[0]);          
    close(fd[1]);
        //回收子进程
    wait(NULL);
    wait(NULL);
  }else if(i == 0){                  //兄进程
    close(fd[0]);
    dup2(fd[1],STDOUT_FILENO);     //重定向stdout
    execlp("ls","ls",NULL);        //兄进程执行ls命令
    sys_err("ececlp ls error");
  }else if(i == 1){              //弟进程
    close(fd[1]);
    dup2(fd[0],STDIN_FILENO);      //重定向stdin
    execlp("wc","wc","-l",NULL);   //弟进程执行wc命令
    sys_err("ececlp wc error");
  }
 
  return 0;
}

6.多个读写端操作管道

实现一个pipe有一个写端,多个读端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
int main(int argc,char *argv[])
{
  /***************
    dup2();
    fork();
    pipe();
    execlp();
  ****************/
  int fd[2],i,n; 
  int ret;
  char buf[1024];
  pid_t pid;
 
    //父进程创建管道
  ret = pipe(fd);
  if(ret == -1){
    sys_err("pipe error");
    exit(1);
  }
 
  for(i = 0;i < 2;i++){
    pid = fork();
    if(pid == -1){
      sys_err("fork error");
      exit(1);
    }
    if(pid == 0)
      break;
  }
 
 
  if(i == 2){            //父进程
    close(fd[1]);       //父进程关闭写端,留读端读取数据
    sleep(1);
    n = read(fd[0],buf,1024);    //从管道中读取数据
    write(STDOUT_FILENO,buf,n); 
 
    for(i == 0;i < 2;i++)        //两个儿子wait两次
      wait(NULL);
 
  }else if(i == 0){        //兄进程
    close(fd[0]);
    write(fd[1],"1.hello\n",strlen("1.hello\n"));
  }else if(i == 1){    //弟进程
    close(fd[0]);
    write(fd[1],"2.world\n",strlen("2.world\n"));
  }
 
  return 0;
}

7.管道缓冲区大小:

可以使用 ulimIt -a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。通常为:

       pipe size               ......(512 bytes,-p)  8

也可以使用fpathconf函数,借助参数―选项来查看。使用该宏应引入头文件

       long fpathconf(int fd, int name);成功:返回管道的大小―失败:-1,设置errno

.8.管道的优劣

优点:简单,相比信号,套接字实现进程间通信,简单很多。

缺点:

               1.只能单向通信,双向通信需建立两个管道。

               2.只能用父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决)

三、FIFO:  

fifo管道:可以用于无血缘关系的进程间通信。

   命名管道:  mkfifo

   无血缘关系进程间通信:

                       读端,open fifo O_RDONLY

                       写端,open fifo O_WRONLY

   

1.命名管道fifo的创建和原理:

使用命令:myfifo myfifo

使用myfifo创建

#include<stdio.h>
#include<sys/stat.h>
#include<errno.h>
#include<pthread.h>
#include<stdlib.h>
 
 
void sys_err(const char *str){
  perror(str);
  exit(1);
}
 
int main(int argc,char *str)
{
  int ret = mkfifo("mytestfifo",0664);
  if(ret == -1)
    sys_err("mkfifo error");
 
  return 0;
  
}

2.非血缘关系进程间通信avi:

向管道写数据fifo_w.c

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
 
void sys_err(char *str)
{
    perror(str);
    exit(-1);
}
 
int main(int argc, char *argv[])
{
    int fd, i;
    char buf[4096];
 
    if (argc < 2) {
        printf("Enter like this: ./a.out fifoname\n");
        return -1;
    }
    fd = open(argv[1], O_WRONLY);       //打开管道文件
    if (fd < 0) 
        sys_err("open");
 
    i = 0;
    while (1) {
        sprintf(buf, "hello itcast %d\n", i++);
 
        write(fd, buf, strlen(buf));    // 向管道写数据
        sleep(1);
    }
    close(fd);
 
    return 0;
}

向管道读数据fifo_r.c

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
 
void sys_err(char *str)
{
    perror(str);
    exit(1);
}
 
int main(int argc, char *argv[])
{
    int fd, len;
    char buf[4096];
 
    if (argc < 2) {
        printf("./a.out fifoname\n");
        return -1;
    }
 
 
    fd = open(argv[1], O_RDONLY);   // 打开管道文件
    if (fd < 0) 
        sys_err("open");
    while (1) {
        len = read(fd, buf, sizeof(buf));   // 从管道的读端获取数据
        write(STDOUT_FILENO, buf, len);
        sleep(3);           //多個读端时应增加睡眠秒数,放大效果.
    }
    close(fd);
 
    return 0;
}

一方写,一方读:

一个写,多个读:管道特性,不能反复读取


目录
相关文章
|
1天前
|
Linux 数据安全/隐私保护
Linux系统忘记密码的三种解决办法
这篇博客介绍了三种在Linux忘记密码时重置登录密码的方法:1) 使用恢复模式,通过控制台界面以管理员权限更改密码;2) 利用Linux Live CD/USB启动,挂载硬盘分区并使用终端更改密码;3) 进入单用户模式,自动以管理员身份登录后重置密码。每个方法都提供了详细步骤,提醒用户在操作前备份重要数据。
|
1天前
|
JSON Unix Linux
Linux系统之jq工具的基本使用
Linux系统之jq工具的基本使用
12 1
|
1天前
|
数据采集 监控 安全
linux系统被×××后处理经历
linux系统被×××后处理经历
|
1天前
|
Ubuntu Linux
Linux(Ubuntu)系统临时IP以及静态IP配置(关闭、启动网卡等操作)
请注意,以上步骤是在临时基础上进行配置的。如果要永久保存静态IP地址,通常还需要修改 `/etc/network/interfaces`文件,以便在系统重启后保持配置。同时,确保备份相关配置文件以防止出现问题。
7 1
|
2天前
|
监控 安全 Linux
Linux系统之安装ServerBee服务器监控工具
【4月更文挑战第22天】Linux系统之安装ServerBee服务器监控工具
39 2
|
2天前
|
缓存 Linux
linux系统缓存机制
linux系统缓存机制
|
2天前
|
NoSQL Linux 程序员
【linux进程信号(一)】信号的概念以及产生信号的方式
【linux进程信号(一)】信号的概念以及产生信号的方式
|
2天前
|
Linux
【linux进程间通信(一)】匿名管道和命名管道
【linux进程间通信(一)】匿名管道和命名管道
|
2天前
|
Java Shell Linux
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
|
2天前
|
算法 Linux Shell
【linux进程(二)】如何创建子进程?--fork函数深度剖析
【linux进程(二)】如何创建子进程?--fork函数深度剖析