进程间通信第一课--管道

一个进程连接数据流到另一个进程--管道--pipe

进程管道

1 #include <stdio.h>
2 FILE * popen(const char * command, const char * open_mode)
3 int pclose(FILE * stream_to_close);

popen函数允许一个程序将另一个程序作为新进程来启动
并可以传递数据给它或者通过它接收数据
command是要运行的程序名和相应的参数
open_mode必须是r或者是w
r的情况是:被调用程序的输出可以由调用程序使用,调用程序可以利用流指针通过库函数读取被调用程序的输出
w的情况是:调用程序可以用fwrite向被调用程序发送命令,被调用程序可以在自己的标准输入上读取这些数据,
    不会意识到自己正在从另外一个进程读取数据
没有其他的open_mode

函数失败时候返回的是空指针
要是想实现双向管道,通常的解决方法是使用两个管道,每个管道负责一个方向的数据流

pclose调用只在popen启动的进程结束才返回,要是仍在运行的话,就等待进程的结束
返回的结果是关闭的文件流所在的进程的退出码
如果调用进程在调用pclose之前就执行一个wait语句,被调用的进程的退出状态就会丢失。
pclose将返回-1并设置为ECHILD

读取外部程序的输出例程:

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5
 6 int main()
 7 {
 8     FILE *read_fp;
 9     char buffer[BUFSIZ + 1];
10     int chars_read;
11     memset(buffer, ‘\0‘, sizeof(buffer));
12     read_fp = popen("uname -a", "r");//调用popen打开新的进程,返回流
13     if (read_fp != NULL) {
14         chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);//从流中读取缓冲到buffer
15         if (chars_read > 0) {
16             printf("Output was:-\n%s\n", buffer);//打印buffer
17         }
18         pclose(read_fp);//关闭打开的流
19         exit(EXIT_SUCCESS);//成功退出
20     }
21     exit(EXIT_FAILURE);
22 }

执行效果:

1 [email protected]:~/c_program/544977-blp3e/chapter13$ gcc popen1.c -o popen1
2 [email protected]:~/c_program/544977-blp3e/chapter13$ ./popen1
3 Output was:-
4 Linux t61 3.19.0-21-generic #21-Ubuntu SMP Sun Jun 14 18:34:06 UTC 2015 i686 i686 i686 GNU/Linux
5
6 [email protected]:~/c_program/544977-blp3e/chapter13$ 

将输出送往popen:

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4
 5 int main()
 6 {
 7     FILE *write_fp;
 8     char buffer[BUFSIZ + 1];
 9
10     sprintf(buffer, "Once upon a time, there was...\n");
11     write_fp = popen("od -c", "w");
12     if (write_fp != NULL) {
13         fwrite(buffer, sizeof(char), strlen(buffer), write_fp);//buffer写到文件描述符
14         pclose(write_fp);
15         exit(EXIT_SUCCESS);
16     }
17     exit(EXIT_FAILURE);
18 }

程序的效果等价于:

1 echo "Once upon a time, there was ..."|od -c
2 程序的执行效果是:
3 [email protected]:~/c_program/544977-blp3e/chapter13$ ./popen2
4 0000000   O   n   c   e       u   p   o   n       a       t   i   m   e
5 0000020   ,       t   h   e   r   e       w   a   s   .   .   .  \n
6 0000037

使用ASCII码进行格式化输出,注意其中包括转义字符
左侧的默认地址格式为八字节

传递更多的数据

我们上面实现的都是一次fread和fwrite调用来发送和接收
有时我们希望能以块的方式发送数据,或者根本不知道输出数据的长度
为了避免定义一个非常大的缓冲区,我们可以用多个fread和fwrite调用来按部分处理数据

通过管道读取大量数据:

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5
 6 int main()
 7 {
 8     FILE *read_fp;
 9     char buffer[BUFSIZ + 1];
10     int chars_read;
11
12     memset(buffer, ‘\0‘, sizeof(buffer));
13     read_fp = popen("ps -ax", "r");
14     if (read_fp != NULL) {
15         chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
16         while (chars_read > 0) {
17             buffer[chars_read - 1] = ‘\0‘;
18             printf("Reading:-\n %s\n", buffer);
19             chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
20         }
21         pclose(read_fp);
22         exit(EXIT_SUCCESS);
23     }
24     exit(EXIT_FAILURE);
25 }

函数的执行的结果:

 1 Reading:-
 2    PID TTY      STAT   TIME COMMAND
 3     1 ?        Ss     0:02 /sbin/init splash
 4     2 ?        S      0:00 [kthreadd]
 5     3 ?        S      0:01 [ksoftirqd/0]
 6 .
 7 .
 8 .
 9  2662 ?        Sl     0:01 nm-applet
10  2663 ?        Sl     0:00 /usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1
11  2670 ?        S      0:00 /usr/lib/i386-linux-gnu/gconf/gconfd-2
12  2682 ?        Sl     0:03 /usr/bin/chinese-calendar
13  2688 ?        Sl     0:00 /usr/lib/gvfs/gvfs-udisks2-volume-monitor
14  2690 ?        Ssl    0:03 /
15 Reading:-
16  sr/lib/udisks2/udisksd --no-debug
17  2701 ?        Sl     0:00 /usr/lib/gvfs/gvfs-gphoto2-volume-monitor
18  2705 ?        Sl     0:00 /usr/lib/gvfs/gvfs-mtp-volume-monitor
19  2713 ?        Sl     0:00 /usr/lib/gvfs/gvfs-afc-volume-monitor
20  2728 ?        Sl     0:00 /usr/lib/i386-linux-gnu/notify-osd
21  2743 ?        Sl     1:55 sogou-qimpanel
22  .
23  .
24  .
25  //唯一要注意的是,linux会安排ps和popen3的读取,互相等待。。

pipe调用

1  #include <unistd.h>
2  int pipe(int file_descriptor[2]);

函数的参数是一个由两个整数类型的文件描述符的组成的数组的指针
 该函数在数组中填上两个新的文件描述符后返回0
 失败的话返回-1设置errno
     EMFILE->进程使用的文件描述符过多
     ENPFILE->系统的文件表已满
     EFAULT->文件描述符无效
写到file_descriptor[1]的所有的数据都可以通过file_descriptor[0]读出
加入写的是1,2,3那么读取的顺序也会是1,2,3遵循先进先出的FIFO原则
这里使用的是文件描述符而不是文件流
所以必须用底层的read和write调用来访问数据,而不是fread和fwrite
pipe函数:

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5
 6 int main()
 7 {
 8     int data_processed;
 9     int file_pipes[2];
10     const char some_data[] = "123";
11     char buffer[BUFSIZ + 1];
12
13     memset(buffer, ‘\0‘, sizeof(buffer));
14
15     if (pipe(file_pipes) == 0) {
16         data_processed = write(file_pipes[1], some_data, strlen(some_data));
17         printf("Wrote %d bytes\n", data_processed);
18         data_processed = read(file_pipes[0], buffer, BUFSIZ);
19         printf("Read %d bytes: %s\n", data_processed, buffer);
20         exit(EXIT_SUCCESS);
21     }
22     exit(EXIT_FAILURE);
23 }//程序用两个文件描述符创建一个管道,用第二个文件描述符写数据,从第一个文件描述符读取数据
24 //管道中有一些缓存区,在write和read调用之间保存数据

程序的执行效果:

1 [email protected]:~/c_program/544977-blp3e/chapter13$ ./pipe1
2 Wrote 3 bytes
3 Read 3 bytes: 123

跨越fork调用的管道:

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5
 6 int main()
 7 {
 8     int data_processed;
 9     int file_pipes[2];
10     const char some_data[] = "123";
11     char buffer[BUFSIZ + 1];
12     pid_t fork_result;
13
14     memset(buffer, ‘\0‘, sizeof(buffer));
15
16     if (pipe(file_pipes) == 0) {//创建管道成功
17         fork_result = fork();//创建子进程
18         if (fork_result == -1) {
19             fprintf(stderr, "Fork failure");
20             exit(EXIT_FAILURE);
21         }//说明子进程创建成功
22
23         if (fork_result == 0) {//说明在子进程中执行读取
24             data_processed = read(file_pipes[0], buffer, BUFSIZ);
25             printf("Read %d bytes: %s\n", data_processed, buffer);
26             exit(EXIT_SUCCESS);
27         }
28         else {//在父进程中执行写入
29             data_processed = write(file_pipes[1], some_data,
30                                    strlen(some_data));
31             printf("Wrote %d bytes\n", data_processed);
32         }
33     }
34     exit(EXIT_SUCCESS);
35 }//函数中,首先创建一个管道,接着创建新进程,父进程给管道写数据,子进程从管道读取数据
36 //要是父进程在子进程之前退出的话,会在两部分内容之间看到shell提示符

管道和exec函数的例程:

//pipe3.c

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5
 6 int main()
 7 {
 8     int data_processed;
 9     int file_pipes[2];
10     const char some_data[] = "123";
11     char buffer[BUFSIZ + 1];
12     pid_t fork_result;
13
14     memset(buffer, ‘\0‘, sizeof(buffer));
15
16     if (pipe(file_pipes) == 0) {//创建管道成功
17         fork_result = fork();
18         if (fork_result == (pid_t)-1) {
19             fprintf(stderr, "Fork failure");
20             exit(EXIT_FAILURE);
21         }//创建进程成功
22
23         if (fork_result == 0) {
24             sprintf(buffer, "%d", file_pipes[0]);//子进程从pipe的输出读取数据到buffer中
25             (void)execl("pipe4", "pipe4", buffer, (char *)0);//读到的数据换成由path=pipe4指定的路径新进程
26             exit(EXIT_FAILURE);
27         }
28         else {//父进程写数据到pipe的输入
29             data_processed = write(file_pipes[1], some_data,
30                                    strlen(some_data));
31             printf("%d - wrote %d bytes\n", getpid(), data_processed);
32         }
33     }
34     exit(EXIT_SUCCESS);
35 }

//pipe4.c

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5
 6 int main(int argc, char *argv[])//参数[0]是程序名。参数[1]是文件描述符
 7 {
 8     int data_processed;
 9     char buffer[BUFSIZ + 1];
10     int file_descriptor;
11
12     memset(buffer, ‘\0‘, sizeof(buffer));//分配内存
13     sscanf(argv[1], "%d", &file_descriptor);//将[1]也就是buffer中的数据按照这个格式传递给这个文件描述符指针
14     data_processed = read(file_descriptor, buffer, BUFSIZ);//将这个指针的数据读取到buffer中
15
16     printf("%d - read %d bytes: %s\n", getpid(), data_processed, buffer);//打印buffer数据
17     exit(EXIT_SUCCESS);
18 }

执行的效果:

1 [email protected]:~/c_program/544977-blp3e/chapter13$ gcc pipe3.c -o pipe3
2 [email protected]:~/c_program/544977-blp3e/chapter13$ gcc pipe4.c -o pipe4
3 [email protected]:~/c_program/544977-blp3e/chapter13$ ./pipe3
4 4585 - wrote 3 bytes
5 4586 - read 3 bytes: 123

管道关闭后的操作:
通常我们并不知道有多少数据需要读取,所以往往采取循环的方式,读取数据-处理数据-读取更多的数据
直到没有数据可读为止

read把读取一个无效的文件描述符看做是一个错误并返回-1
但是会把对一个关闭写数据的管道的read调用不当成错误而是返回0也就是不阻塞

把管道用作标准输入和标准输出

1 #include <unistd.h>
2 int dup(int file_descriptor);
3 int dup2(int file_descriptor_one, int file_descriptor_two);

dup调用的目的是打开一个新的文件描述符这和open调用有些类似
dup调用创建的新的文件描述符与作为它的参数的那个已有的文件描述符指向同一个文件(或者管道)
对于dup函数来说,新的文件描述符总是去最小的可用值
对于dup2函数来说,它所创建的新的文件描述符或者与参数file_descriptor_two相同或者是第一个大于该参数的可用值

要是我们关闭文件描述符0然后调用dup那么新的文件描述符就是0
所以标准输入就会指向我们传递给dup函数的文件描述符所对应的文件或者管道
管道和dup函数例程:

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5
 6 int main()
 7 {
 8     int data_processed;
 9     int file_pipes[2];
10     const char some_data[] = "123";
11     pid_t fork_result;
12
13     if (pipe(file_pipes) == 0) {//创建管道成功
14         fork_result = fork();//fork出一个子进程
15         if (fork_result == (pid_t)-1) {
16             fprintf(stderr, "Fork failure");
17             exit(EXIT_FAILURE);
18         }
19
20         if (fork_result == (pid_t)0) {//fork成功
21             close(0);//关闭子进程文件描述符0
22             dup(file_pipes[0]);//打开一个新的文件描述符,标准输入会指向这个管道
23             close(file_pipes[0]);//关闭管道输出
24             close(file_pipes[1]);//关闭管道输入
25
26             execlp("od", "od", "-c", (char *)0);//从path中找到文件,后面两个是参数,第一个是程序,第二个是输入
27             exit(EXIT_FAILURE);//会认为从标准输入读取数据,其实数据是父进程给管道写入,子进程从标换输入读取
28         }    //按照字符串打印
29         else {
30             close(file_pipes[0]);//关闭管道的输出
31             data_processed = write(file_pipes[1], some_data, strlen(some_data));//向管道输入端写数据
32             close(file_pipes[1]);//关闭管道的输入
33             printf("%d - wrote %d bytes\n", (int)getpid(), data_processed);//打印写入的数据
34         }
35     }
36     exit(EXIT_SUCCESS);
37 }

程序的执行效果:

1 [email protected]:~/c_program/544977-blp3e/chapter13$ 0000000   1   2   3
2 0000003

命名管道FIFO

命名管道是一种特殊类型的文件,在文件系统中以文件名的形式存在

1 mknod filename -p//创建特殊的文件filename,-p保证是管道
2 mkfifo filename//实现在命令行上创建命名管道
1 #include <sys/types.h>
2 #include <sys/stat.h>
3 int mkfifo(const char * filename, node_t mode);
4 int mknod(const char * filename, node_t mode|S_IFIFO, (dev_t) 0);

创建命名管道:

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 int main()
 7 {
 8     int res=mkfifo("/tmp/wy_fifo",0777);
 9     if(res==0) printf("FIFO created\n");
10     exit(EXIT_SUCCESS);
11 }

查看运行效果

1 [email protected]:~/c_program/544977-blp3e/chapter13$ ls -lF /tmp/my_fifo
2 prwxrwxr-x 1 jason jason 0  7月  1 10:21 /tmp/my_fifo|
3 [email protected]:~/c_program/544977-blp3e/chapter13$ umask
4 0002

访问fifo文件

 1 open(const char * path, O_RDONLY);
 2 //此时open调用将阻塞,除非有一个进程以写的方式打开同一个FIFO否则不会返回
 3
 4 open(const char * path, O_RDONLY|O_NONBLOCK);
 5 //即使没有其他进程以写方式打开FIFO这个open调用也将成功并立即返回
 6
 7 open(const char * path, O_WRONLY);
 8 //此时open调用将阻塞,直到有一个进程以读方式打开一个FIFO为止
 9
10 open(const char * path, O_WRONLY|O_NONBLOCK);
11 //这个调用总是立刻返回,但是如果没有进程以读方式打开FIFO文件
12 //open调用将返回一个错误-1并且FIFO也不会被打开
13 //如果有一个进程以读方式打开FIFO文件,那么可以通过它返回的文件描述符对这个FIFO文件进行写操作

打开FIFO文件的程序:

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <fcntl.h>
 6 #include <sys/types.h>
 7 #include <sys/stat.h>
 8
 9 #define FIFO_NAME "/tmp/my_fifo"
10
11 int main(int argc, char *argv[])
12 {
13     int res;
14     int open_mode = 0;
15     int i;
16
17     if (argc < 2) {//输入参数的数目不对的时候提示一些信息
18         fprintf(stderr, "Usage: %s <some combination of\
19                O_RDONLY O_WRONLY O_NONBLOCK>\n", *argv);
20         exit(EXIT_FAILURE);
21     }
22
23
24     for(i = 1; i < argc; i++) {
25         if (strncmp(*++argv, "O_RDONLY", 8) == 0)
26              open_mode |= O_RDONLY;
27         if (strncmp(*argv, "O_WRONLY", 8) == 0)
28              open_mode |= O_WRONLY;
29         if (strncmp(*argv, "O_NONBLOCK", 10) == 0)
30              open_mode |= O_NONBLOCK;
31      }//根据命令行参数设置open_mode的值
32
33     if (access(FIFO_NAME, F_OK) == -1) {//要是这个特殊的文件不存在的话
34         res = mkfifo(FIFO_NAME, 0777);//按照给定的权限区创建这个fifo
35         if (res != 0) {
36             fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
37             exit(EXIT_FAILURE);
38         }
39     }
40
41     printf("Process %d opening FIFO\n", getpid());
42     res = open(FIFO_NAME, open_mode);//按照FIFO_MODE来打开这个FIFO
43     printf("Process %d result %d\n", getpid(), res);
44     sleep(5);//休眠程序
45     if (res != -1) (void)close(res);//关闭FIFO
46     printf("Process %d finished\n", getpid());
47     exit(EXIT_SUCCESS);
48 }

执行的效果:

 1 [email protected]:~/c_program/544977-blp3e/chapter13$ ./fifo2 O_RDONLY &
 2 [1] 11845
 3 [email protected]:~/c_program/544977-blp3e/chapter13$ Process 11845 opening FIFO
 4
 5 [email protected]:~/c_program/544977-blp3e/chapter13$
 6 [email protected]:~/c_program/544977-blp3e/chapter13$ ./fifo2 O_WRONLY
 7 Process 11972 opening FIFO
 8 Process 11845 result 3
 9 Process 11972 result 3
10 Process 11845 finished
11 Process 11972 finished
12 [1]+  已完成               ./fifo2 O_RDONLY
13 [email protected]:~/c_program/544977-blp3e/chapter13$
14 //说明允许读先启动,并在open调用中等待,当第二个程序打开FIFO文件时,两个程序继续执行
15 读进程和写进程在open调用处取得同步,当一个Linux进程被阻塞时,并不消耗CPU资源,所以这种进程
16 同步方式对CPU来说是非常有效的
 1 [email protected]:~/c_program/544977-blp3e/chapter13$ ./fifo2 O_RDONLY O_NONBLOCK&
 2 [1] 13745
 3 [email protected]:~/c_program/544977-blp3e/chapter13$ Process 13745 opening FIFO
 4 Process 13745 result 3
 5
 6 [email protected]:~/c_program/544977-blp3e/chapter13$ ./fifo2 O_WRONLY
 7 Process 13764 opening FIFO
 8 Process 13764 result 3
 9 Process 13745 finished
10 Process 13764 finished
11 [1]+  已完成               ./fifo2 O_RDONLY O_NONBLOCK
12 [email protected]:~/c_program/544977-blp3e/chapter13$ 

对FIFO进行读写操作:

对于一个完全阻塞FIFO的write调用将等待直到有数据可以被写入时才继续执行

如果FIFO不能接收所有写入的数据,它将按下面的规则执行
    如果请求写入的数据的长度小于等于PIPE_BUF字节,调用失败,数据不能写入
    如果请求写入的长度大于PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0

FIFO的数据长度是有限制的
在一个以O_WRONLY方式打开的FIFO中,如果写入的数据长度小于等于PIPE_BUF
那么或者写入全部字节或者一个字节都不写入

使用FIFO实现进程间通信
//fifo3.c

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <fcntl.h>
 6 #include <limits.h>
 7 #include <sys/types.h>
 8 #include <sys/stat.h>
 9
10 #define FIFO_NAME "/tmp/my_fifo"
11 #define BUFFER_SIZE PIPE_BUF
12 #define TEN_MEG (1024 * 1024 * 10)
13
14 int main()
15 {
16     int pipe_fd;
17     int res;
18     int open_mode = O_WRONLY;
19     int bytes_sent = 0;
20     char buffer[BUFFER_SIZE + 1];
21
22     if (access(FIFO_NAME, F_OK) == -1) {//要是FIFO不存在
23         res = mkfifo(FIFO_NAME, 0777);//就按照这个权限去创建一个FIFO
24         if (res != 0) {
25             fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
26             exit(EXIT_FAILURE);
27         }
28     }
29
30     printf("Process %d opening FIFO O_WRONLY\n", getpid());
31     pipe_fd = open(FIFO_NAME, open_mode);//按照写权限去打开这个FIFO
32     printf("Process %d result %d\n", getpid(), pipe_fd);
33
34     if (pipe_fd != -1) {//打开成功
35         while(bytes_sent < TEN_MEG) {//执行条件
36             res = write(pipe_fd, buffer, BUFFER_SIZE);//将buffer处的管道长度的字节数据写到管道中
37             if (res == -1) {
38                 fprintf(stderr, "Write error on pipe\n");
39                 exit(EXIT_FAILURE);
40             }
41             bytes_sent += res;//增加写入的长度,区改变while循环的条件
42         }
43         (void)close(pipe_fd); //关闭这个管道
44     }
45     else {
46         exit(EXIT_FAILURE);
47     }
48
49     printf("Process %d finished\n", getpid());
50     exit(EXIT_SUCCESS);
51 }

//fifo4.c

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <fcntl.h>
 6 #include <limits.h>
 7 #include <sys/types.h>
 8 #include <sys/stat.h>
 9
10 #define FIFO_NAME "/tmp/my_fifo"
11 #define BUFFER_SIZE PIPE_BUF
12
13 int main()
14 {
15     int pipe_fd;
16     int res;
17     int open_mode = O_RDONLY;
18     char buffer[BUFFER_SIZE + 1];
19     int bytes_read = 0;
20
21     memset(buffer, ‘\0‘, sizeof(buffer));
22
23     printf("Process %d opening FIFO O_RDONLY\n", getpid());
24     pipe_fd = open(FIFO_NAME, open_mode);//读取的方式打开管道
25     printf("Process %d result %d\n", getpid(), pipe_fd);
26
27     if (pipe_fd != -1) {
28         do {
29             res = read(pipe_fd, buffer, BUFFER_SIZE);//从管道中读取到buffer中
30             bytes_read += res;
31         } while (res > 0);
32         (void)close(pipe_fd);
33     }
34     else {
35         exit(EXIT_FAILURE);
36     }
37
38     printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
39     exit(EXIT_SUCCESS);
40 }

//fifo3是数据的生产者。它将阻塞以等待读取进程打开这个FIFO
//fifo4是数据的消费者。它将启动解除阻塞并开始向管道写数据,同时读取进程也从管道中读取数据
Linux会安排好两个进程的调度,使他们在可以运行的时候运行,在不能运行的时候阻塞
写进程将在管道满时阻塞,读进程将在管道空时阻塞
//执行的效果

 1 [email protected]:~/c_program/544977-blp3e/chapter13$ ./fifo3 &
 2 [1] 22927
 3 [email protected]:~/c_program/544977-blp3e/chapter13$ Process 22927 opening FIFO O_WRONLY
 4
 5 [email protected]:~/c_program/544977-blp3e/chapter13$ time ./fifo4
 6 Process 23009 opening FIFO O_RDONLY
 7 Process 23009 result 3
 8 Process 22927 result 3
 9 Process 22927 finished
10 Process 23009 finished, 10485760 bytes read
11 [1]+  已完成               ./fifo3
12
13 real    0m0.016s
14 user    0m0.004s
15 sys    0m0.020s
16 [email protected]:~/c_program/544977-blp3e/chapter13$
17 //读取程序读取了10M的数据,时间很短,说明程序之间传递数据是很有效率的

使用fifo的客服/服务器(C/S架构)程序

//client.h

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <fcntl.h>
 6 #include <limits.h>
 7 #include <sys/types.h>
 8 #include <sys/stat.h>
 9
10 #define SERVER_FIFO_NAME "/tmp/serv_fifo"//定义了server的fifo
11 #define CLIENT_FIFO_NAME "/tmp/cli_%d_fifo"//定义了cli_i的fifo
12
13 #define BUFFER_SIZE 20//缓冲区大小
14
15 struct data_to_pass_st {//定义一个结构体包含一个pit_t结构和一个结构数组
16     pid_t  client_pid;
17     char   some_data[BUFFER_SIZE - 1];
18 };

//server.c服务器版本的程序

 1 #include "client.h"
 2 #include <ctype.h>
 3
 4 int main()
 5 {
 6     int server_fifo_fd, client_fifo_fd;
 7     struct data_to_pass_st my_data;
 8     int read_res;
 9     char client_fifo[256];
10     char *tmp_char_ptr;
11
12     mkfifo(SERVER_FIFO_NAME, 0777);//新建fifo
13     server_fifo_fd = open(SERVER_FIFO_NAME, O_RDONLY);//读取模式打开fifo,立即阻塞
14     if (server_fifo_fd == -1) {
15         fprintf(stderr, "Server fifo failure\n");
16         exit(EXIT_FAILURE);
17     }
18
19     sleep(10); //一旦cli传递数据,阻塞解除,休眠让cli的数据排队等候
20
21     do {
22         read_res = read(server_fifo_fd, &my_data, sizeof(my_data));//将服务器fifo的数据读取到buffer中
23         if (read_res > 0) {
24             tmp_char_ptr = my_data.some_data;
25             while (*tmp_char_ptr) {
26                 *tmp_char_ptr = toupper(*tmp_char_ptr);
27                 tmp_char_ptr++;//将客户端传递的数据转化为大写
28             }
29             sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);//把格式化的数据写入到客户端fifo中
30
31             client_fifo_fd = open(client_fifo, O_WRONLY);//以写的方式打开客户端fifo
32             if (client_fifo_fd != -1) {//将处理过的数据发送到客户端fifo
33                 write(client_fifo_fd, &my_data, sizeof(my_data));
34                 close(client_fifo_fd);
35             }
36         }
37     } while (read_res > 0);
38     close(server_fifo_fd);
39     unlink(SERVER_FIFO_NAME);
40     exit(EXIT_SUCCESS);
41 }

//client.c

 1 #include "client.h"
 2 #include <ctype.h>
 3
 4 int main()
 5 {
 6     int server_fifo_fd, client_fifo_fd;
 7     struct data_to_pass_st my_data;
 8     int times_to_send;
 9     char client_fifo[256];
10
11     server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY);//写的函数打开服务器的fifo
12     if (server_fifo_fd == -1) {
13         fprintf(stderr, "Sorry, no server\n");
14         exit(EXIT_FAILURE);
15     }
16
17     my_data.client_pid = getpid();
18     sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);
19     if (mkfifo(client_fifo, 0777) == -1) {//新建一个cli_fifo
20         fprintf(stderr, "Sorry, can‘t make %s\n", client_fifo);
21         exit(EXIT_FAILURE);
22     }
23
24 // For each of the five loops, the client data is sent to the server.
25 // Then the client FIFO is opened (read-only, blocking mode) and the data read back.
26 // Finally, the server FIFO is closed and the client FIFO removed from memory.
27
28     for (times_to_send = 0; times_to_send < 5; times_to_send++) {//循环五次
29         sprintf(my_data.some_data, "Hello from %d", my_data.client_pid); //打印这个pid的招呼到自定义结构体
30         printf("%d sent %s, ", my_data.client_pid, my_data.some_data);
31         write(server_fifo_fd, &my_data, sizeof(my_data));//将自定义结构体内容写到服务器fifo中
32         client_fifo_fd = open(client_fifo, O_RDONLY);//以读方式打开cli的fifo这时会阻塞等待服务器返回数据
33         if (client_fifo_fd != -1) {//将cli的fifo读到的数据写到自定义的数据结构中
34             if (read(client_fifo_fd, &my_data, sizeof(my_data)) > 0) {
35                 printf("received: %s\n", my_data.some_data);
36             }
37             close(client_fifo_fd);
38         }
39     }
40     close(server_fifo_fd);
41     unlink(client_fifo);
42     exit(EXIT_SUCCESS);
43 }

//执行效果:

 1 [email protected]:~/c_program/544977-blp3e/chapter13$ ./server &
 2 [1] 32168
 3 [email protected]:~/c_program/544977-blp3e/chapter13$ for i in 1 2 3 4 5
 4 > do
 5 > ./client &
 6 > done
 7 [2] 32268
 8 [3] 32269
 9 [4] 32270
10 [5] 32271
11 [6] 32272
12 [email protected]:~/c_program/544977-blp3e/chapter13$ 32268 sent Hello from 32268, received: HELLO FROM 32268
13 32271 sent Hello from 32271, received: HELLO FROM 32271
14 32271 sent Hello from 32271, received: HELLO FROM 32271
15 32271 sent Hello from 32271, received: HELLO FROM 32271
16 32271 sent Hello from 32271, received: HELLO FROM 32271
17 32271 sent Hello from 32271, received: HELLO FROM 32271
18 32270 sent Hello from 32270, received: HELLO FROM 32270
19 32268 sent Hello from 32268, received: HELLO FROM 32268
20 32272 sent Hello from 32272, received: HELLO FROM 32272
21 32269 sent Hello from 32269, received: HELLO FROM 32269
22 32270 sent Hello from 32270, received: HELLO FROM 32270
23 32272 sent Hello from 32272, received: HELLO FROM 32272
24 32269 sent Hello from 32269, received: HELLO FROM 32269
25 32268 sent Hello from 32268, received: HELLO FROM 32268
26 32269 sent Hello from 32269, received: HELLO FROM 32269
27 32272 sent Hello from 32272, received: HELLO FROM 32272
28 32269 sent Hello from 32269, received: HELLO FROM 32269
29 32272 sent Hello from 32272, received: HELLO FROM 32272
30 32269 sent Hello from 32269, received: HELLO FROM 32269
31 32272 sent Hello from 32272, received: HELLO FROM 32272
32 32270 sent Hello from 32270, received: HELLO FROM 32270
33 32268 sent Hello from 32268, received: HELLO FROM 32268
34 32270 sent Hello from 32270, received: HELLO FROM 32270
35 32268 sent Hello from 32268, received: HELLO FROM 32268
36 32270 sent Hello from 32270, received: HELLO FROM 32270
37
38 [1]   已完成               ./server
39 [2]   已完成               ./client
40 [3]   已完成               ./client
41 [4]   已完成               ./client
42 [5]-  已完成               ./client
43 [6]+  已完成               ./client
44 [email protected]:~/c_program/544977-blp3e/chapter13$ 

参考文献:Linux 程序设计 Neil Matthew
date:2015年 07月 01日 星期三 17:06:13 CST

时间: 2024-10-07 09:50:04

进程间通信第一课--管道的相关文章

Linux进程间通信 -- 使用命名管道

在前一篇文章—— Linux进程间通信 -- 使用匿名管道 中,我们看到了如何使用匿名管道来在进程之间传递数据,同时也看到了这个方式的一个缺陷,就是这些进程都由一个共同的祖先进程启动,这给我们在不相关的的进程之间交换数据带来了不方便.这里将会介绍进程的另一种通信方式——命名管道,来解决不相关进程间的通信问题. 一.什么是命名管道 命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似. 由于Linux中所有

【Linux探索之旅】第三部分第一课:数据处理,慢条斯理

内容简介 1.第三部分第一课:数据处理,慢条斯理 2.第三部分第二课预告:流.管道.重定向,三管齐下 数据处理,慢条斯理 哈哈,终于到了第三部分了.不知不觉两个部分已经学完了,可喜可贺,掌声给自己! 此时读者内心独白:"我想静静,也不要问我小编是谁." 好了好了,小编重回淡定.咳咳,看到今天的标题应该会对这一课的内容很有兴趣吧,毕竟我们每天都在跟各种数据打交道. Linux中的文件里也是各种数据,所以数据处理就显得尤为重要. 之前的课中已经介绍过:大部分Linux的命令是基于Unix操

Linux进程间通信 -- 使用匿名管道

在前面,介绍了一种进程间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是一个信号值.这里将介绍另一种进程间通信的方式——匿名管道,通过它进程间可以交换更多有用的数据. 一.什么是管道 如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号“|"来使用管道,但是管理的真正定义是什么呢?管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入. 举个例子,在shell中输入命令:ls -l

sql第一课笔记

这是我看了imooc的视频教程之后重新写的笔记. 虽然之前也是学习过SQL Server数据库,但是也是忘记得差不多了.现在重新捡起来,安装一次数据库练习,使用的是mysql. 第一课是最简单的创建,修改,查看,删除数据库: mysql 有密码之后在命令行登陆 用的是 shell>mysql -u root -p; 提示输入密码: 登陆成功之后,把提示符mysql变成以当前计算机帐户名@主机名 当前数据库的格式:prompt \[email protected]\h \d> prompt命令下

OpenCV 第一课(安装与配置)

OpenCV 第一课(安装与配置) win10,opencv-2.4.13, 安装, vs2013, 配置 下载安装软件 官网OpenCV下载地址下载最新版本,我下载的是opencv.2.4.13,然后解压安装,我写的路径是D:\Program Files.注意本文中绿色标注的要换成你自己的安装路径. 这里得说一点,可能是因为网速太差的原因,昨天晚上下载了几次安装时都提示说"cannott open file'opencv-2.4.13.exe' as archive".我当时一直不明

读书笔记 - 《格鲁夫给经理人的第一课》

这本书对我的启发远远超过其它的企业管理类图书,不愧是资深前辈写的书.虽然名为第一课,实际上对于中层经理人来说,已经不再需要第二课了.这本书从简单的早餐店开始,讲解了管理杠杆率.开会.决策.规划.矩阵组织.激励.绩效.招人.薪酬.培训,以浅显的语言讲解了几乎是一个中层经理人所需理解的全部工作,使我对工作的认识有了大幅度提高,已经基本可以摆脱漫无头绪的状态!这本书准备丢在公司,没事就翻看思考一下,在实践中继续深入领悟!

VC++编程之第一课笔记

第一课 Windows程序内部运行原理 API 操作系统把它所能够完成的功能以函数的形式提供给应用程序使用,应用程序对这些函数的调用就叫做系统调用.这些函数的集合就是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称Windows API. 如Create Window就是一个API函数,应用程序调用这个函数,操作系统就会按照该函数提供的参数信息产生一个相应的窗口. MSG(消息结构体) 结构体定义如下: typedef s

EasyUI入门第一课

首先下载easyUI,最好是最新的,然后新建一个空web程序或是网站,不废话,代码如下: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="JqueryEasyUI.WebForm1" %> <!DOCTYPE html> <html xmlns="http://ww

第一课 C语言简明教程

1序言: 1与Java.C#等高级语言相比,C语言却非常简单,学习简单,使用也简单,但是也非常重要,到目前为止基本上操作系统的内核代码超过百分之九十使用C语言完成,因此学好C语言是学好计算机这门课程的基础,特别是进入系统编程尤为明显. 今天是本人复习C语言课程的第一课,主要重新记录一下C语言的基础知识,这节课涉及到C语言的结构.变量以及类型.输入输出.条件判断以及循环知识. 2知识点: 2.1 C语言的结构 2.1.1 通常情况下C语言程序是由: 1.相关的代码注释,使用/* ··· */可注释