进程间通信第一课--管道
2015-07-01 17:07
429 查看
一个进程连接数据流到另一个进程--管道--pipe
进程管道
popen函数允许一个程序将另一个程序作为新进程来启动
并可以传递数据给它或者通过它接收数据
command是要运行的程序名和相应的参数
open_mode必须是r或者是w
r的情况是:被调用程序的输出可以由调用程序使用,调用程序可以利用流指针通过库函数读取被调用程序的输出
w的情况是:调用程序可以用fwrite向被调用程序发送命令,被调用程序可以在自己的标准输入上读取这些数据,
不会意识到自己正在从另外一个进程读取数据
没有其他的open_mode
函数失败时候返回的是空指针
要是想实现双向管道,通常的解决方法是使用两个管道,每个管道负责一个方向的数据流
pclose调用只在popen启动的进程结束才返回,要是仍在运行的话,就等待进程的结束
返回的结果是关闭的文件流所在的进程的退出码
如果调用进程在调用pclose之前就执行一个wait语句,被调用的进程的退出状态就会丢失。
pclose将返回-1并设置为ECHILD
读取外部程序的输出例程:
执行效果:
将输出送往popen:
程序的效果等价于:
使用ASCII码进行格式化输出,注意其中包括转义字符
左侧的默认地址格式为八字节
传递更多的数据
我们上面实现的都是一次fread和fwrite调用来发送和接收
有时我们希望能以块的方式发送数据,或者根本不知道输出数据的长度
为了避免定义一个非常大的缓冲区,我们可以用多个fread和fwrite调用来按部分处理数据
通过管道读取大量数据:
函数的执行的结果:
pipe调用
函数的参数是一个由两个整数类型的文件描述符的组成的数组的指针
该函数在数组中填上两个新的文件描述符后返回0
失败的话返回-1设置errno
EMFILE->进程使用的文件描述符过多
ENPFILE->系统的文件表已满
EFAULT->文件描述符无效
写到file_descriptor[1]的所有的数据都可以通过file_descriptor[0]读出
加入写的是1,2,3那么读取的顺序也会是1,2,3遵循先进先出的FIFO原则
这里使用的是文件描述符而不是文件流
所以必须用底层的read和write调用来访问数据,而不是fread和fwrite
pipe函数:
程序的执行效果:
跨越fork调用的管道:
管道和exec函数的例程:
//pipe3.c
//pipe4.c
执行的效果:
管道关闭后的操作:
通常我们并不知道有多少数据需要读取,所以往往采取循环的方式,读取数据-处理数据-读取更多的数据
直到没有数据可读为止
read把读取一个无效的文件描述符看做是一个错误并返回-1
但是会把对一个关闭写数据的管道的read调用不当成错误而是返回0也就是不阻塞
把管道用作标准输入和标准输出
dup调用的目的是打开一个新的文件描述符这和open调用有些类似
dup调用创建的新的文件描述符与作为它的参数的那个已有的文件描述符指向同一个文件(或者管道)
对于dup函数来说,新的文件描述符总是去最小的可用值
对于dup2函数来说,它所创建的新的文件描述符或者与参数file_descriptor_two相同或者是第一个大于该参数的可用值
要是我们关闭文件描述符0然后调用dup那么新的文件描述符就是0
所以标准输入就会指向我们传递给dup函数的文件描述符所对应的文件或者管道
管道和dup函数例程:
程序的执行效果:
命名管道FIFO
命名管道是一种特殊类型的文件,在文件系统中以文件名的形式存在
创建命名管道:
查看运行效果
访问fifo文件
打开FIFO文件的程序:
执行的效果:
对FIFO进行读写操作:
对于一个完全阻塞FIFO的write调用将等待直到有数据可以被写入时才继续执行
如果FIFO不能接收所有写入的数据,它将按下面的规则执行
如果请求写入的数据的长度小于等于PIPE_BUF字节,调用失败,数据不能写入
如果请求写入的长度大于PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0
FIFO的数据长度是有限制的
在一个以O_WRONLY方式打开的FIFO中,如果写入的数据长度小于等于PIPE_BUF
那么或者写入全部字节或者一个字节都不写入
使用FIFO实现进程间通信
//fifo3.c
//fifo4.c
//fifo3是数据的生产者。它将阻塞以等待读取进程打开这个FIFO
//fifo4是数据的消费者。它将启动解除阻塞并开始向管道写数据,同时读取进程也从管道中读取数据
Linux会安排好两个进程的调度,使他们在可以运行的时候运行,在不能运行的时候阻塞
写进程将在管道满时阻塞,读进程将在管道空时阻塞
//执行的效果
使用fifo的客服/服务器(C/S架构)程序
//client.h
//server.c服务器版本的程序
//client.c
//执行效果:
参考文献:Linux 程序设计 Neil Matthew
date:2015年 07月 01日 星期三 17:06:13 CST
进程管道
#include <stdio.h> FILE * popen(const char * command, const char * open_mode) int pclose(FILE * stream_to_close);
popen函数允许一个程序将另一个程序作为新进程来启动
并可以传递数据给它或者通过它接收数据
command是要运行的程序名和相应的参数
open_mode必须是r或者是w
r的情况是:被调用程序的输出可以由调用程序使用,调用程序可以利用流指针通过库函数读取被调用程序的输出
w的情况是:调用程序可以用fwrite向被调用程序发送命令,被调用程序可以在自己的标准输入上读取这些数据,
不会意识到自己正在从另外一个进程读取数据
没有其他的open_mode
函数失败时候返回的是空指针
要是想实现双向管道,通常的解决方法是使用两个管道,每个管道负责一个方向的数据流
pclose调用只在popen启动的进程结束才返回,要是仍在运行的话,就等待进程的结束
返回的结果是关闭的文件流所在的进程的退出码
如果调用进程在调用pclose之前就执行一个wait语句,被调用的进程的退出状态就会丢失。
pclose将返回-1并设置为ECHILD
读取外部程序的输出例程:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { FILE *read_fp; char buffer[BUFSIZ + 1]; int chars_read; memset(buffer, '\0', sizeof(buffer)); read_fp = popen("uname -a", "r");//调用popen打开新的进程,返回流 if (read_fp != NULL) { chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);//从流中读取缓冲到buffer if (chars_read > 0) { printf("Output was:-\n%s\n", buffer);//打印buffer } pclose(read_fp);//关闭打开的流 exit(EXIT_SUCCESS);//成功退出 } exit(EXIT_FAILURE); }
执行效果:
jason@t61:~/c_program/544977-blp3e/chapter13$ gcc popen1.c -o popen1 jason@t61:~/c_program/544977-blp3e/chapter13$ ./popen1 Output was:- Linux t61 3.19.0-21-generic #21-Ubuntu SMP Sun Jun 14 18:34:06 UTC 2015 i686 i686 i686 GNU/Linux jason@t61:~/c_program/544977-blp3e/chapter13$
将输出送往popen:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> int main() { FILE *write_fp; char buffer[BUFSIZ + 1]; sprintf(buffer, "Once upon a time, there was...\n"); write_fp = popen("od -c", "w"); if (write_fp != NULL) { fwrite(buffer, sizeof(char), strlen(buffer), write_fp);//buffer写到文件描述符 pclose(write_fp); exit(EXIT_SUCCESS); } exit(EXIT_FAILURE); }
程序的效果等价于:
echo "Once upon a time, there was ..."|od -c 程序的执行效果是: jason@t61:~/c_program/544977-blp3e/chapter13$ ./popen2 0000000 O n c e u p o n a t i m e 0000020 , t h e r e w a s . . . \n 0000037
使用ASCII码进行格式化输出,注意其中包括转义字符
左侧的默认地址格式为八字节
传递更多的数据
我们上面实现的都是一次fread和fwrite调用来发送和接收
有时我们希望能以块的方式发送数据,或者根本不知道输出数据的长度
为了避免定义一个非常大的缓冲区,我们可以用多个fread和fwrite调用来按部分处理数据
通过管道读取大量数据:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { FILE *read_fp; char buffer[BUFSIZ + 1]; int chars_read; memset(buffer, '\0', sizeof(buffer)); read_fp = popen("ps -ax", "r"); if (read_fp != NULL) { chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp); while (chars_read > 0) { buffer[chars_read - 1] = '\0'; printf("Reading:-\n %s\n", buffer); chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp); } pclose(read_fp); exit(EXIT_SUCCESS); } exit(EXIT_FAILURE); }
函数的执行的结果:
Reading:- PID TTY STAT TIME COMMAND 1 ? Ss 0:02 /sbin/init splash 2 ? S 0:00 [kthreadd] 3 ? S 0:01 [ksoftirqd/0] . . . 2662 ? Sl 0:01 nm-applet 2663 ? Sl 0:00 /usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1 2670 ? S 0:00 /usr/lib/i386-linux-gnu/gconf/gconfd-2 2682 ? Sl 0:03 /usr/bin/chinese-calendar 2688 ? Sl 0:00 /usr/lib/gvfs/gvfs-udisks2-volume-monitor 2690 ? Ssl 0:03 / Reading:- sr/lib/udisks2/udisksd --no-debug 2701 ? Sl 0:00 /usr/lib/gvfs/gvfs-gphoto2-volume-monitor 2705 ? Sl 0:00 /usr/lib/gvfs/gvfs-mtp-volume-monitor 2713 ? Sl 0:00 /usr/lib/gvfs/gvfs-afc-volume-monitor 2728 ? Sl 0:00 /usr/lib/i386-linux-gnu/notify-osd 2743 ? Sl 1:55 sogou-qimpanel . . . //唯一要注意的是,linux会安排ps和popen3的读取,互相等待。。
pipe调用
#include <unistd.h> 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函数:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int data_processed; int file_pipes[2]; const char some_data[] = "123"; char buffer[BUFSIZ + 1]; memset(buffer, '\0', sizeof(buffer)); if (pipe(file_pipes) == 0) { data_processed = write(file_pipes[1], some_data, strlen(some_data)); printf("Wrote %d bytes\n", data_processed); data_processed = read(file_pipes[0], buffer, BUFSIZ); printf("Read %d bytes: %s\n", data_processed, buffer); exit(EXIT_SUCCESS); } exit(EXIT_FAILURE); }//程序用两个文件描述符创建一个管道,用第二个文件描述符写数据,从第一个文件描述符读取数据 //管道中有一些缓存区,在write和read调用之间保存数据
程序的执行效果:
jason@t61:~/c_program/544977-blp3e/chapter13$ ./pipe1 Wrote 3 bytes Read 3 bytes: 123
跨越fork调用的管道:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int data_processed; int file_pipes[2]; const char some_data[] = "123"; char buffer[BUFSIZ + 1]; pid_t fork_result; memset(buffer, '\0', sizeof(buffer)); if (pipe(file_pipes) == 0) {//创建管道成功 fork_result = fork();//创建子进程 if (fork_result == -1) { fprintf(stderr, "Fork failure"); exit(EXIT_FAILURE); }//说明子进程创建成功 if (fork_result == 0) {//说明在子进程中执行读取 data_processed = read(file_pipes[0], buffer, BUFSIZ); printf("Read %d bytes: %s\n", data_processed, buffer); exit(EXIT_SUCCESS); } else {//在父进程中执行写入 data_processed = write(file_pipes[1], some_data, strlen(some_data)); printf("Wrote %d bytes\n", data_processed); } } exit(EXIT_SUCCESS); }//函数中,首先创建一个管道,接着创建新进程,父进程给管道写数据,子进程从管道读取数据 //要是父进程在子进程之前退出的话,会在两部分内容之间看到shell提示符
管道和exec函数的例程:
//pipe3.c
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int data_processed; int file_pipes[2]; const char some_data[] = "123"; char buffer[BUFSIZ + 1]; pid_t fork_result; memset(buffer, '\0', sizeof(buffer)); if (pipe(file_pipes) == 0) {//创建管道成功 fork_result = fork(); if (fork_result == (pid_t)-1) { fprintf(stderr, "Fork failure"); exit(EXIT_FAILURE); }//创建进程成功 if (fork_result == 0) { sprintf(buffer, "%d", file_pipes[0]);//子进程从pipe的输出读取数据到buffer中 (void)execl("pipe4", "pipe4", buffer, (char *)0);//读到的数据换成由path=pipe4指定的路径新进程 exit(EXIT_FAILURE); } else {//父进程写数据到pipe的输入 data_processed = write(file_pipes[1], some_data, strlen(some_data)); printf("%d - wrote %d bytes\n", getpid(), data_processed); } } exit(EXIT_SUCCESS); }
//pipe4.c
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main(int argc, char *argv[])//参数[0]是程序名。参数[1]是文件描述符 { int data_processed; char buffer[BUFSIZ + 1]; int file_descriptor; memset(buffer, '\0', sizeof(buffer));//分配内存 sscanf(argv[1], "%d", &file_descriptor);//将[1]也就是buffer中的数据按照这个格式传递给这个文件描述符指针 data_processed = read(file_descriptor, buffer, BUFSIZ);//将这个指针的数据读取到buffer中 printf("%d - read %d bytes: %s\n", getpid(), data_processed, buffer);//打印buffer数据 exit(EXIT_SUCCESS); }
执行的效果:
jason@t61:~/c_program/544977-blp3e/chapter13$ gcc pipe3.c -o pipe3 jason@t61:~/c_program/544977-blp3e/chapter13$ gcc pipe4.c -o pipe4 jason@t61:~/c_program/544977-blp3e/chapter13$ ./pipe3 4585 - wrote 3 bytes 4586 - read 3 bytes: 123
管道关闭后的操作:
通常我们并不知道有多少数据需要读取,所以往往采取循环的方式,读取数据-处理数据-读取更多的数据
直到没有数据可读为止
read把读取一个无效的文件描述符看做是一个错误并返回-1
但是会把对一个关闭写数据的管道的read调用不当成错误而是返回0也就是不阻塞
把管道用作标准输入和标准输出
#include <unistd.h> int dup(int file_descriptor); int dup2(int file_descriptor_one, int file_descriptor_two);
dup调用的目的是打开一个新的文件描述符这和open调用有些类似
dup调用创建的新的文件描述符与作为它的参数的那个已有的文件描述符指向同一个文件(或者管道)
对于dup函数来说,新的文件描述符总是去最小的可用值
对于dup2函数来说,它所创建的新的文件描述符或者与参数file_descriptor_two相同或者是第一个大于该参数的可用值
要是我们关闭文件描述符0然后调用dup那么新的文件描述符就是0
所以标准输入就会指向我们传递给dup函数的文件描述符所对应的文件或者管道
管道和dup函数例程:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int data_processed; int file_pipes[2]; const char some_data[] = "123"; pid_t fork_result; if (pipe(file_pipes) == 0) {//创建管道成功 fork_result = fork();//fork出一个子进程 if (fork_result == (pid_t)-1) { fprintf(stderr, "Fork failure"); exit(EXIT_FAILURE); } if (fork_result == (pid_t)0) {//fork成功 close(0);//关闭子进程文件描述符0 dup(file_pipes[0]);//打开一个新的文件描述符,标准输入会指向这个管道 close(file_pipes[0]);//关闭管道输出 close(file_pipes[1]);//关闭管道输入 execlp("od", "od", "-c", (char *)0);//从path中找到文件,后面两个是参数,第一个是程序,第二个是输入 exit(EXIT_FAILURE);//会认为从标准输入读取数据,其实数据是父进程给管道写入,子进程从标换输入读取 } //按照字符串打印 else { close(file_pipes[0]);//关闭管道的输出 data_processed = write(file_pipes[1], some_data, strlen(some_data));//向管道输入端写数据 close(file_pipes[1]);//关闭管道的输入 printf("%d - wrote %d bytes\n", (int)getpid(), data_processed);//打印写入的数据 } } exit(EXIT_SUCCESS); }
程序的执行效果:
jason@t61:~/c_program/544977-blp3e/chapter13$ 0000000 1 2 3 0000003
命名管道FIFO
命名管道是一种特殊类型的文件,在文件系统中以文件名的形式存在
mknod filename -p//创建特殊的文件filename,-p保证是管道 mkfifo filename//实现在命令行上创建命名管道
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char * filename, node_t mode); int mknod(const char * filename, node_t mode|S_IFIFO, (dev_t) 0);
创建命名管道:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> int main() { int res=mkfifo("/tmp/wy_fifo",0777); if(res==0) printf("FIFO created\n"); exit(EXIT_SUCCESS); }
查看运行效果
jason@t61:~/c_program/544977-blp3e/chapter13$ ls -lF /tmp/my_fifo prwxrwxr-x 1 jason jason 0 7月 1 10:21 /tmp/my_fifo| jason@t61:~/c_program/544977-blp3e/chapter13$ umask 0002
访问fifo文件
open(const char * path, O_RDONLY); //此时open调用将阻塞,除非有一个进程以写的方式打开同一个FIFO否则不会返回 open(const char * path, O_RDONLY|O_NONBLOCK); //即使没有其他进程以写方式打开FIFO这个open调用也将成功并立即返回 open(const char * path, O_WRONLY); //此时open调用将阻塞,直到有一个进程以读方式打开一个FIFO为止 open(const char * path, O_WRONLY|O_NONBLOCK); //这个调用总是立刻返回,但是如果没有进程以读方式打开FIFO文件 //open调用将返回一个错误-1并且FIFO也不会被打开 //如果有一个进程以读方式打开FIFO文件,那么可以通过它返回的文件描述符对这个FIFO文件进行写操作
打开FIFO文件的程序:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #define FIFO_NAME "/tmp/my_fifo" int main(int argc, char *argv[]) { int res; int open_mode = 0; int i; if (argc < 2) {//输入参数的数目不对的时候提示一些信息 fprintf(stderr, "Usage: %s <some combination of\ O_RDONLY O_WRONLY O_NONBLOCK>\n", *argv); exit(EXIT_FAILURE); } for(i = 1; i < argc; i++) { if (strncmp(*++argv, "O_RDONLY", 8) == 0) open_mode |= O_RDONLY; if (strncmp(*argv, "O_WRONLY", 8) == 0) open_mode |= O_WRONLY; if (strncmp(*argv, "O_NONBLOCK", 10) == 0) open_mode |= O_NONBLOCK; }//根据命令行参数设置open_mode的值 if (access(FIFO_NAME, F_OK) == -1) {//要是这个特殊的文件不存在的话 res = mkfifo(FIFO_NAME, 0777);//按照给定的权限区创建这个fifo if (res != 0) { fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME); exit(EXIT_FAILURE); } } printf("Process %d opening FIFO\n", getpid()); res = open(FIFO_NAME, open_mode);//按照FIFO_MODE来打开这个FIFO printf("Process %d result %d\n", getpid(), res); sleep(5);//休眠程序 if (res != -1) (void)close(res);//关闭FIFO printf("Process %d finished\n", getpid()); exit(EXIT_SUCCESS); }
执行的效果:
jason@t61:~/c_program/544977-blp3e/chapter13$ ./fifo2 O_RDONLY & [1] 11845 jason@t61:~/c_program/544977-blp3e/chapter13$ Process 11845 opening FIFO jason@t61:~/c_program/544977-blp3e/chapter13$ jason@t61:~/c_program/544977-blp3e/chapter13$ ./fifo2 O_WRONLY Process 11972 opening FIFO Process 11845 result 3 Process 11972 result 3 Process 11845 finished Process 11972 finished [1]+ 已完成 ./fifo2 O_RDONLY jason@t61:~/c_program/544977-blp3e/chapter13$ //说明允许读先启动,并在open调用中等待,当第二个程序打开FIFO文件时,两个程序继续执行 读进程和写进程在open调用处取得同步,当一个Linux进程被阻塞时,并不消耗CPU资源,所以这种进程 同步方式对CPU来说是非常有效的
jason@t61:~/c_program/544977-blp3e/chapter13$ ./fifo2 O_RDONLY O_NONBLOCK& [1] 13745 jason@t61:~/c_program/544977-blp3e/chapter13$ Process 13745 opening FIFO Process 13745 result 3 jason@t61:~/c_program/544977-blp3e/chapter13$ ./fifo2 O_WRONLY Process 13764 opening FIFO Process 13764 result 3 Process 13745 finished Process 13764 finished [1]+ 已完成 ./fifo2 O_RDONLY O_NONBLOCK jason@t61:~/c_program/544977-blp3e/chapter13$
对FIFO进行读写操作:
对于一个完全阻塞FIFO的write调用将等待直到有数据可以被写入时才继续执行
如果FIFO不能接收所有写入的数据,它将按下面的规则执行
如果请求写入的数据的长度小于等于PIPE_BUF字节,调用失败,数据不能写入
如果请求写入的长度大于PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0
FIFO的数据长度是有限制的
在一个以O_WRONLY方式打开的FIFO中,如果写入的数据长度小于等于PIPE_BUF
那么或者写入全部字节或者一个字节都不写入
使用FIFO实现进程间通信
//fifo3.c
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #include <sys/stat.h> #define FIFO_NAME "/tmp/my_fifo" #define BUFFER_SIZE PIPE_BUF #define TEN_MEG (1024 * 1024 * 10) int main() { int pipe_fd; int res; int open_mode = O_WRONLY; int bytes_sent = 0; char buffer[BUFFER_SIZE + 1]; if (access(FIFO_NAME, F_OK) == -1) {//要是FIFO不存在 res = mkfifo(FIFO_NAME, 0777);//就按照这个权限去创建一个FIFO if (res != 0) { fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME); exit(EXIT_FAILURE); } } printf("Process %d opening FIFO O_WRONLY\n", getpid()); pipe_fd = open(FIFO_NAME, open_mode);//按照写权限去打开这个FIFO printf("Process %d result %d\n", getpid(), pipe_fd); if (pipe_fd != -1) {//打开成功 while(bytes_sent < TEN_MEG) {//执行条件 res = write(pipe_fd, buffer, BUFFER_SIZE);//将buffer处的管道长度的字节数据写到管道中 if (res == -1) { fprintf(stderr, "Write error on pipe\n"); exit(EXIT_FAILURE); } bytes_sent += res;//增加写入的长度,区改变while循环的条件 } (void)close(pipe_fd); //关闭这个管道 } else { exit(EXIT_FAILURE); } printf("Process %d finished\n", getpid()); exit(EXIT_SUCCESS); }
//fifo4.c
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #include <sys/stat.h> #define FIFO_NAME "/tmp/my_fifo" #define BUFFER_SIZE PIPE_BUF int main() { int pipe_fd; int res; int open_mode = O_RDONLY; char buffer[BUFFER_SIZE + 1]; int bytes_read = 0; memset(buffer, '\0', sizeof(buffer)); printf("Process %d opening FIFO O_RDONLY\n", getpid()); pipe_fd = open(FIFO_NAME, open_mode);//读取的方式打开管道 printf("Process %d result %d\n", getpid(), pipe_fd); if (pipe_fd != -1) { do { res = read(pipe_fd, buffer, BUFFER_SIZE);//从管道中读取到buffer中 bytes_read += res; } while (res > 0); (void)close(pipe_fd); } else { exit(EXIT_FAILURE); } printf("Process %d finished, %d bytes read\n", getpid(), bytes_read); exit(EXIT_SUCCESS); }
//fifo3是数据的生产者。它将阻塞以等待读取进程打开这个FIFO
//fifo4是数据的消费者。它将启动解除阻塞并开始向管道写数据,同时读取进程也从管道中读取数据
Linux会安排好两个进程的调度,使他们在可以运行的时候运行,在不能运行的时候阻塞
写进程将在管道满时阻塞,读进程将在管道空时阻塞
//执行的效果
jason@t61:~/c_program/544977-blp3e/chapter13$ ./fifo3 & [1] 22927 jason@t61:~/c_program/544977-blp3e/chapter13$ Process 22927 opening FIFO O_WRONLY jason@t61:~/c_program/544977-blp3e/chapter13$ time ./fifo4 Process 23009 opening FIFO O_RDONLY Process 23009 result 3 Process 22927 result 3 Process 22927 finished Process 23009 finished, 10485760 bytes read [1]+ 已完成 ./fifo3 real 0m0.016s user 0m0.004s sys 0m0.020s jason@t61:~/c_program/544977-blp3e/chapter13$ //读取程序读取了10M的数据,时间很短,说明程序之间传递数据是很有效率的
使用fifo的客服/服务器(C/S架构)程序
//client.h
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #include <sys/stat.h> #define SERVER_FIFO_NAME "/tmp/serv_fifo"//定义了server的fifo #define CLIENT_FIFO_NAME "/tmp/cli_%d_fifo"//定义了cli_i的fifo #define BUFFER_SIZE 20//缓冲区大小 struct data_to_pass_st {//定义一个结构体包含一个pit_t结构和一个结构数组 pid_t client_pid; char some_data[BUFFER_SIZE - 1]; };
//server.c服务器版本的程序
#include "client.h" #include <ctype.h> int main() { int server_fifo_fd, client_fifo_fd; struct data_to_pass_st my_data; int read_res; char client_fifo[256]; char *tmp_char_ptr; mkfifo(SERVER_FIFO_NAME, 0777);//新建fifo server_fifo_fd = open(SERVER_FIFO_NAME, O_RDONLY);//读取模式打开fifo,立即阻塞 if (server_fifo_fd == -1) { fprintf(stderr, "Server fifo failure\n"); exit(EXIT_FAILURE); } sleep(10); //一旦cli传递数据,阻塞解除,休眠让cli的数据排队等候 do { read_res = read(server_fifo_fd, &my_data, sizeof(my_data));//将服务器fifo的数据读取到buffer中 if (read_res > 0) { tmp_char_ptr = my_data.some_data; while (*tmp_char_ptr) { *tmp_char_ptr = toupper(*tmp_char_ptr); tmp_char_ptr++;//将客户端传递的数据转化为大写 } sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);//把格式化的数据写入到客户端fifo中 client_fifo_fd = open(client_fifo, O_WRONLY);//以写的方式打开客户端fifo if (client_fifo_fd != -1) {//将处理过的数据发送到客户端fifo write(client_fifo_fd, &my_data, sizeof(my_data)); close(client_fifo_fd); } } } while (read_res > 0); close(server_fifo_fd); unlink(SERVER_FIFO_NAME); exit(EXIT_SUCCESS); }
//client.c
#include "client.h" #include <ctype.h> int main() { int server_fifo_fd, client_fifo_fd; struct data_to_pass_st my_data; int times_to_send; char client_fifo[256]; server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY);//写的函数打开服务器的fifo if (server_fifo_fd == -1) { fprintf(stderr, "Sorry, no server\n"); exit(EXIT_FAILURE); } my_data.client_pid = getpid(); sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid); if (mkfifo(client_fifo, 0777) == -1) {//新建一个cli_fifo fprintf(stderr, "Sorry, can't make %s\n", client_fifo); exit(EXIT_FAILURE); } // For each of the five loops, the client data is sent to the server. // Then the client FIFO is opened (read-only, blocking mode) and the data read back. // Finally, the server FIFO is closed and the client FIFO removed from memory. for (times_to_send = 0; times_to_send < 5; times_to_send++) {//循环五次 sprintf(my_data.some_data, "Hello from %d", my_data.client_pid); //打印这个pid的招呼到自定义结构体 printf("%d sent %s, ", my_data.client_pid, my_data.some_data); write(server_fifo_fd, &my_data, sizeof(my_data));//将自定义结构体内容写到服务器fifo中 client_fifo_fd = open(client_fifo, O_RDONLY);//以读方式打开cli的fifo这时会阻塞等待服务器返回数据 if (client_fifo_fd != -1) {//将cli的fifo读到的数据写到自定义的数据结构中 if (read(client_fifo_fd, &my_data, sizeof(my_data)) > 0) { printf("received: %s\n", my_data.some_data); } close(client_fifo_fd); } } close(server_fifo_fd); unlink(client_fifo); exit(EXIT_SUCCESS); }
//执行效果:
jason@t61:~/c_program/544977-blp3e/chapter13$ ./server & [1] 32168 jason@t61:~/c_program/544977-blp3e/chapter13$ for i in 1 2 3 4 5 > do > ./client & > done [2] 32268 [3] 32269 [4] 32270 [5] 32271 [6] 32272 jason@t61:~/c_program/544977-blp3e/chapter13$ 32268 sent Hello from 32268, received: HELLO FROM 32268 32271 sent Hello from 32271, received: HELLO FROM 32271 32271 sent Hello from 32271, received: HELLO FROM 32271 32271 sent Hello from 32271, received: HELLO FROM 32271 32271 sent Hello from 32271, received: HELLO FROM 32271 32271 sent Hello from 32271, received: HELLO FROM 32271 32270 sent Hello from 32270, received: HELLO FROM 32270 32268 sent Hello from 32268, received: HELLO FROM 32268 32272 sent Hello from 32272, received: HELLO FROM 32272 32269 sent Hello from 32269, received: HELLO FROM 32269 32270 sent Hello from 32270, received: HELLO FROM 32270 32272 sent Hello from 32272, received: HELLO FROM 32272 32269 sent Hello from 32269, received: HELLO FROM 32269 32268 sent Hello from 32268, received: HELLO FROM 32268 32269 sent Hello from 32269, received: HELLO FROM 32269 32272 sent Hello from 32272, received: HELLO FROM 32272 32269 sent Hello from 32269, received: HELLO FROM 32269 32272 sent Hello from 32272, received: HELLO FROM 32272 32269 sent Hello from 32269, received: HELLO FROM 32269 32272 sent Hello from 32272, received: HELLO FROM 32272 32270 sent Hello from 32270, received: HELLO FROM 32270 32268 sent Hello from 32268, received: HELLO FROM 32268 32270 sent Hello from 32270, received: HELLO FROM 32270 32268 sent Hello from 32268, received: HELLO FROM 32268 32270 sent Hello from 32270, received: HELLO FROM 32270 [1] 已完成 ./server [2] 已完成 ./client [3] 已完成 ./client [4] 已完成 ./client [5]- 已完成 ./client [6]+ 已完成 ./client jason@t61:~/c_program/544977-blp3e/chapter13$
参考文献:Linux 程序设计 Neil Matthew
date:2015年 07月 01日 星期三 17:06:13 CST
相关文章推荐
- MQTT的学习研究(七)基于HTTP POST MQTT 发布消息服务端使用
- input()和raw_input()区别
- 适配较好的写法
- 题目1008:最短路径问题
- iOS开发之iOS程序偏好设置(Settings Bundle)的使用
- BaiduMap---百度地图官方Demo之基本地图功能(创建一张百度地图并管理地图的生命周期)
- 操作远程计算机文件
- iOS开发笔记24--第三方库JSONModel的使用
- XGBoost:参数解释
- 从github上导入dubbo项目
- Rails应用开发点滴
- Entityframework codefirst 数据库变更
- MQTT的学习研究(六) MQTT moquette 的 Blocking API 订阅消息客户端使用
- 登录织梦后台成功后又返回登录页面
- 轻松突破GRE阅读难句在于掌握语法结构
- ios数据库应用高级编程
- 【实用】Excel表格技巧合集,让你工作效率翻一倍!
- struts.xml的加载路径及配置问题
- 给openwrt移植一个其他项目的问题记录
- IE7,8,9兼容性处理