zoukankan      html  css  js  c++  java
  • Unix环境高级编程(十六)进程间通信

      进程间通信(IPC)是指能在两个进程间进行数据交换的机制。现代OS都对进程有保护机制,因此两个进程不能直接交换数据,必须通过一定机制来完成。
      IPC的机制的作用:
      (1)一个软件也能更容易跟第三方软件或内核进行配合的集成,或移植.如管道,在shell 下执行 ps –aux | grep bash。
      (2)简化软件结构, 可以把一个软件划分多个进程或线程,通过IPC,集成在一起工作.如消息队列。
      (3)让操作系统各个模块交换数据,包括内核与应用程序机制。
      (4)提供进程之间或同一进程之间多线程的同步机制,如信号量。

    1、管道

      管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

      数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
      管道的创建:int pipe(int fd[2]) ;
      管道的读写:管道文件也是一种文件,用write,read 即可完成读写。管道两端可分别用描述字fd[0]以及fd[1]来描述,需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。
      管道的关闭:管道文件也是一种文件,因此用close关闭即可。
      管道的局限:(1)只支持单向数据流; (2)只能用于具有亲缘关系的进程之间; (3)没有名字; (4)管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小); (5)管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等。

    现在使用管道实现进程的同步,父进程读取子进程输入的数据、子进程读取父进程恢复的数据。实现TELL_WAIT、TELL_PARENT、TELL_CHILD、TELL_PARENT及WAIT_CHILD函数。程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <errno.h>
     5 #include <sys/types.h>
     6 
     7 static int fd1[2],fd2[2];
     8 
     9 void TELL_WAIT()
    10 {
    11     pipe(fd1);
    12     pipe(fd2);
    13 }
    14 
    15 void TELL_PARENT(pid_t pid)
    16 {
    17     write(fd2[1],"c",1);
    18 }
    19 void WAIT_PARENT(void)
    20 {
    21     char c;
    22     read(fd1[0],&c,1);
    23     if(c!='p')
    24     {
    25         printf("WAIT_PARENT: Incorretc data");
    26         exit(0);
    27     }
    28     else
    29       printf("Read from parent.
    ");
    30 }
    31 void TELL_CHILD(pid_t pid)
    32 {
    33     write(fd1[1],"p",1);
    34 }
    35 void WAIT_CHILD()
    36 {
    37     char c;
    38     read(fd2[0],&c,1);
    39     if(c!='c')
    40     {
    41         printf("WAIT_CHILD: Incorretc data");
    42         exit(0);
    43     }
    44     else
    45         printf("Read from child.
    ");
    46 }
    47 
    48 int main()
    49 {
    50     pid_t pid;
    51     TELL_WAIT();
    52     pid =fork();
    53     if(pid == -1)
    54     {
    55         perror("fork() error");
    56         exit(-1);
    57     }
    58     if(pid == 0)
    59     {
    60         printf("child process exec.
    ");
    61         WAIT_PARENT();
    62         TELL_PARENT(getppid());
    63     }
    64     else
    65     {
    66         printf("Parent process exec.
    ");
    67         TELL_CHILD(pid);
    68         WAIT_CHILD();
    69 
    70     }
    71     exit(0);
    72 }

    程序执行结果如下:

    popen和pclose函数

      常见的操作时创建一个管道连接到另外一个进程,然后读取其输出或向其输入端发送数据。popen和pcolse函数实现的操作是:创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,执行一个shell以运行命令,然后等待命令终止。函数原型如下:

    FILE *popen(const char *command, const char *type);
    int pclose(FILE *stream);

    函数popen先执行fork,然后调用exec执行cmdstring,并且返回一个标准I/O文件指针。如果type是“r”,则文件指针连接到cmdstring的标准输出,如果type是“w”,则文件指针连接到cmdstring的标准输入。popen特别适用于构造简单的过滤程序,它变换运行命令的输入或输出。写一个程序,将标准输入复制到标准输出,复制的时候将所有的大写字母变换为小写字母,程序分为两部分,转换程序如下:

     1 #include <stdio.h>
     2 #include <ctype.h>
     3 #include <stdlib.h>
     4 int main()
     5 {
     6     int c;
     7     while((c = getchar()) != EOF)
     8     {
     9         if(isupper(c))
    10             c= tolower(c);
    11         if(putchar(c) == EOF)
    12             printf("output error");
    13         if(c=='
    ')
    14             fflush(stdout);
    15     }
    16     exit(0);
    17 }

    将可执行文件保存为change。输入输出程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <errno.h>
     5 #include <errno.h>
     6 
     7 #define MAXLINE 1024
     8 
     9 int main()
    10 {
    11     char    line[MAXLINE];
    12     FILE    *fpin;
    13     if((fpin = popen(".//change","r")) == NULL)
    14     {
    15         perror("popen() error");
    16         exit(-1);
    17     }
    18     for(; ;)
    19     {
    20         fputs("prompt> ",stdout);
    21         fflush(stdout);
    22         if(fgets(line,MAXLINE,fpin) == NULL)
    23             break;
    24         if(fputs(line,stdout) == EOF)
    25         {
    26             perror("fputs error to pipe");
    27         }
    28     }
    29     if(pclose(fpin) == -1)
    30     {
    31         perror("pclose() error");
    32         exit(-1);
    33     }
    34     putchar('
    ');
    35     exit(0);
    36 }

    程序执行结果如下

      协同进程:当一个进程产生某个过滤程序的输入,同时又读取该过滤程序的输出。popen只提供链接到另一个进程的标准输入或标准输出的一个单向管道,对于协同进程,则连接到另一个进程的两个单向管道,一个接到标准输入,一个接标准输出。写个程序展示一下协同进程,程序从标准输入读入两个整数,调用程序计算它们的和,然后将结果输出到标准输出。过滤程序即求和程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <fcntl.h>
     5 #include <unistd.h>
     6 #define MAXLINE 1024
     7 
     8 int main()
     9 {
    10     int n,int1,int2;
    11     char line[MAXLINE];
    12     while((n=read(STDIN_FILENO,line,MAXLINE)) > 0)
    13     {
    14         line[n] = '';
    15         if(sscanf(line,"%d%d",&int1,&int2) == 2)
    16         {
    17             sprintf(line,"%d
    ",int1+int2);
    18             n = strlen(line);
    19             if(write(STDOUT_FILENO,line,n) != n)
    20             {
    21                 perror("write() error");
    22                 exit(-1);
    23             }
    24         }
    25         else if(write(STDOUT_FILENO,"invalid arg
    ",13) != 13)
    26         {
    27             perror("write() error");
    28             exit(-1);
    29         }
    30     }
    31     exit(0);
    32 }

    编译执行保存为可执行文件为add。

    协同程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <errno.h>
     5 #include <signal.h>
     6 #include <string.h>
     7 #define MAXLINE 1024
     8 
     9 static void sig_pipe(int);
    10 
    11 int main()
    12 {
    13     int n,fd1[2],fd2[2];
    14     pid_t   pid;
    15     char line[MAXLINE];
    16 
    17     if(signal(SIGPIPE,sig_pipe) ==SIG_ERR)
    18     {
    19         perror("signal() error");
    20         exit(-1);
    21     }
    22     if(pipe(fd1) == -1||pipe(fd2) == -1)
    23     {
    24         perror("pipe() error");
    25         exit(-1);
    26     }
    27     if((pid =fork()) == -1)
    28     {
    29         perror("fork() error");
    30         exit(-1);
    31     }
    32     if(pid == 0)
    33     {
    34         close(fd1[1]);
    35         close(fd2[0]);
    36         if(fd1[0] != STDIN_FILENO)
    37             if(dup2(fd1[0],STDIN_FILENO) != STDIN_FILENO)
    38             {
    39                 perror("dup2 error in stdin");
    40                 close(fd1[0]);
    41                 exit(-1);
    42             };
    43         if(fd2[1] != STDOUT_FILENO)
    44             if(dup2(fd2[1],STDOUT_FILENO) != STDOUT_FILENO)
    45             {
    46                 perror("dup2 error in stdout");
    47                 close(fd2[1]);
    48                 exit(-1);
    49             };
    50         if(execl(".//add","add",(char *)0) == -1)
    51         {
    52             perror("execl() error");
    53             exit(-1);
    54         }
    55     }
    56     else
    57     {
    58         close(fd1[0]);
    59         close(fd2[1]);
    60         printf("Enter two number: ");
    61         while(fgets(line,MAXLINE,stdin) != NULL)
    62         {
    63             n = strlen(line);
    64             if(write(fd1[1],line,n) != n)
    65             {
    66                 perror("write errot to pipe");
    67                 exit(-1);
    68             }
    69             if((n=read(fd2[0],line,MAXLINE)) ==-1)
    70             {
    71                 perror("read error to pipe");
    72                 exit(-1);
    73             }
    74             if(n== 0)
    75             {
    76                 printf("child close pipe.
    ");
    77                 break;
    78             }
    79             line[n] = '';
    80             printf("The result is: ");
    81             fputs(line,stdout);
    82         }
    83     }
    84 }
    85 
    86 static void sig_pipe(int signo)
    87 {
    88     printf("SIGPIPE caught
    ");
    89     exit(1);
    90 }

    程序执行结果如下:

    2、FIFO

      FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。

    命名管道的命名管道创建:int mkfifo(const char * pathname, mode_t mode) 。
    命名管道的打开:命名管道比管道多了一个打开操作:open ,在open时,用O_NONBLOCK 标志表示非阻塞模式,如fd=open(“/tmp/fifo”,O_RDONLY|O_NONBLOCK,0)。
    命名管道的读入:read 读取管道数据,读取分为阻塞和非阻塞模式,阻塞模式下,如果没有数据被入,进程会在read处停下来.直到有新数据被写入,或管道被关闭,才会继续。
    命名管道的写入:write 写入管道数据,PIPE_BUF表示一次触发管道读操作最大长度.如果每次写入数据长于PIPE_BUF ,write将会多次触发read 操作。
    命名管道的关闭:管道文件也是一种文件,因此用close关闭即可。

    FIFO的两种用途:

    (1)FIFO有shell命令使用以便将数据从一条管道线传送到另一条,为此无需创建中间临时文件。

    (2)FIFO用于客户进程—服务器进程应用程序中,以在客户进程和服务器进程之间传递数据。

    3、XSI IPC

     消息队列、信号量、共享存储区相似的特征如下:具有标识符和键,标识符是IPC对象的内部名,每个IPC对象都与一个键相关联,创建IPC结构需要指定一个键,键的数据类型为key_t。每个IPC都设置了权限结构。

      优点及缺点:IPC结构是在系统范围内起作用,没有访问计数。在文件系统中没有名字,不使用文件描述符,不能对它们使用多路转接I/O函数。优点:可靠、流是受控的,面向记录、可以用非先进先出方式处理。

      消息队列(Messge Queue):消息队列是消息的链接表,包括Posix消息队列SystemV消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。
      信号量(Semaphore):主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。
      共享内存(Shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。
     
    建议:要学会使用管道和FIFO,因为大量应用程序中仍可以有效地使用这两种技术,在新的应用程序中,要尽可能避免使用消息队列及信号量,应当考虑全双工管道和记录锁。
  • 相关阅读:
    Java实现 蓝桥杯VIP 算法提高 阮小二买彩票
    Java实现 蓝桥杯VIP 算法提高 传染病控制
    Java实现 蓝桥杯VIP 算法提高 传染病控制
    Java实现 蓝桥杯VIP 算法提高 传染病控制
    Java实现 蓝桥杯VIP 算法提高 传染病控制
    Java实现 蓝桥杯VIP 算法提高 传染病控制
    Java实现 蓝桥杯VIP 算法提高 企业奖金发放
    Java实现 蓝桥杯VIP 算法提高 企业奖金发放
    让程序后台隐藏运行
    只要你喜欢,并且可以养家糊口,就是好的
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8466148.html
Copyright © 2011-2022 走看看