Linux 进程通信(System V)管道

来源:互联网 发布:excel2010软件下载 编辑:程序博客网 时间:2024/06/05 09:39
1、管道概述及相关API应用

1.1 管道相关的关键概念

管道是Linux支持的最初Unix IPC形式之一,具有以下特点:

管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;

单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

 

管道(Pipe)是一种具有两个端点的通信通道:有一端句柄的进程可以和有另一端句柄的进程通信。管道可以是单向-一端是只读的,另一端点是只写的;也可以是双向的一管道的两端点既可读也可写。
  匿名管道(Anonymous Pipe)是在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。通常由父进程创建管道,然后由要通信的子进程继承通道的读端点句柄或写端点句柄,然后实现通信。父进程还可以建立两个或更多个继承匿名管道读和写句柄的子进程。这些子进程可以使用管道直接通信,不需要通过父进程。
  匿名管道是单机上实现子进程标准I/O重定向的有效方法,它不能在网上使用,也不能用于两个不相关的进程之间。

2、有名管道概述及相关API应用

2.1 有名管道相关的关键概念

管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(named pipe或FIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。命名管道提供了相对简单的编程接口,使通过网络传输数据并不比同一计算机上两进程之间通信更困难,不过如果要同时和多个进程通信它就力不从心了。

命名管道:可用于网络通信;可通过名称引用;支持多客户端连接;支持双向通信;支持异步重叠I/O

匿名管道:只能本地使用.

 

#include

int pipe(int fd[2]); //!> 注意参数是fd[0]是读的文件描述符,fd[1]是用来写的文件描述符

一般用于 “父子进程” 之间的通信!因为pipe是没有标志的,所以只能在一个进程集中运作!

“单向pipe”:
父进程创建好 pipe 后,同时通过 fork() 创建一个子进程,然后父进程就可以关闭自己这端的 “读进程”,因为 父进程就是将数据写入子进程的,所以无须 “读”,然后子进程就关闭自己的 “写”,这样就形成一个 “单向” 的 pipe。

“双向pie”:( 可以用于CS )
创建两个pipe就可以了,其实也就是相当于加一个 “单向” pipe

二.
关于 fork函数
对于fork的返回值:对于父进程返回的是子进程的ID号(肯定是大于0的),对于子进程返回的是0
所以可以通过 if( pid = fork() > 0 ) 和 if( pid == 0 ) 来判断是父进程还是子进程在执行

三.
其他
对于 pipe 而言,创建 ok 后,在子进程和父进程中都会有一个此管道(pipe)的读和写的接口!操作value是相同的fd[0]和fd[1]

对于 test.c 中的为例:
由于是多进程编程,那么对于test.c代码而言,应该是有“子进程”和“父进程”都可以执行的!也就是说在fork后程序就是分成两部分,主进程和子进程。
我们可以测试:
如果是printf(" on ");那么可以输出两次 on,分别是父进程和子进程输出的,但是if是printf(" on \n"); 那么只输出一次。
原因:printf的机制是:遇到"\n"就会直接输出,if没有,那么只是储存在缓存中,然后一起输出。
所以if有"\n",那么就是父进程先让其输出了,那么在父进程的空间空就没有保存printf中的缓存数据了!!!所以子进程就没有继承到,所以就不会输出!!!

也就是说:父进程的 printf 空间缓存区也被继承!!!!!!!!!!!!!!!!!!!!!

getpid(): 获得本进程的ID
getppid(): 获得父进程的ID 

四.参考代码:

//!>
//!> 单向 pipe 实例 
//!>

#include
#include
#include
#include

int g_num = 0; //!> 全局变量:用来测试父进程和子进程的独立空间
//!> 我们可以知道全局变量在子进程和父进程中是有自己的独立空间的!
int main()
{
int n, fd[2]; //!> fd 是描述符
pid_t pid; //!> 保存的是创建的进程的 ID
char line[100]; //!> 相当于是一个缓存

int num = 0; //!> 也是独立的~~~

printf(" on "); //!> ATTENTION printf(" on \n");

if( pipe( fd ) < 0 ) //!> 此处就是创建pipe,成功则返回0,if失败则返回-1
{
exit(0);
}


if( ( pid = fork() ) < 0 ) //!> 创建子进程
{
exit(0);
}
else if( pid > 0 )
{
close(fd[0]);
write(fd[1], "I am your father...\n", 19);

g_num++;
num++;
printf("\nFather g_num: %d num: %d \n", g_num, num); 

printf("\nMY ID : %d Parent ID : %d \n", getpid(), getcpid());
}
else //!> == 0 当前进程(也就是刚刚创建的子进程)
{
close( fd[1] );
n = read( fd[0], line, 100 );
write( STDOUT_FILENO, line, n ); //

g_num++;
num++;
printf("\nChild g_num: %d num: %d \n", g_num, num);

printf("\nMY ID : %d Parent ID : %d \n", getpid(), getppid());
}

printf(" ok "); 

return 0;
}


//!>
//!> 双向 pipe 实例
//!>

#include
#include
#include
#include
#include

int main()
{
int fd_1[2], fd_2[2]; //!> 双向 pipe 的 values、
char cData[100];
pid_t pid;

if( pipe( fd_1 ) < 0 ) //!> create the pipe_1
{
printf("\n创建第一个 pipe 失败!\n");
exit( 0 );
}

if( pipe( fd_2 ) < 0 ) //!> create the pipe_2
{
printf("\n创建第二个 pipe 失败!\n");
exit( 0 );
}

if( ( pid = fork() ) < 0 ) //!> false to create a new process
{
printf("\n创建进程失败!\n");
exit( 0 );
}
else if( pid == 0 ) //!> 也就是fork返回的子进程...
{
//!> 子进程也需要发送 data 到 pipe
close( fd_2[0] );
char str[30] = "I am your child!";
write( fd_2[1], str, strlen( str ) ); //!> 第二个pipe是子进程发送data,父进程接受data

//!> 子进程也需要接受父进程的 data
close( fd_1[1] );
int n = read( fd_1[0], cData, 100 ); //!> 第一个pipe是父进程发送data,子进程接受data
write( STDOUT_FILENO, cData, n );
}
else //!> fork 返回的是子进程的ID,肯定是大于0的,所以此处执行的是父进程的code
{
//!> 父进程也需要发送 data 到 pipe
close( fd_1[0] );
char str[30] = "I am your father!";
write( fd_1[1], str, strlen( str ) ); //!> 第二个pipe是子进程发送data,父进程接受data

//!> 父进程也需要接受父进程的 data
close( fd_2[1] );
int n = read( fd_2[0], cData, 100 ); //!> 第一个pipe是父进程发送data,子进程接受data
write( STDOUT_FILENO, cData, n );
}

return 0;
}

 

 

一些简单理解:
我们知道管道是没有标志的,所以只能是在同一个进程组中进行通信,不同的祖先产生的进程之间是不可以的!!所以此处引入FIFO机制
同样也只能是单流的!不同的是FIFO是有标志的!每个FIFO都有一个路径名与之相关!
FIFO也称为 “有名管道”

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo( const char* pathname, mode_t mode );

mode 与 open函数中的一样,默认为:O_CREAT | O_EXCL

if FIFO 已经存在,那么返回EEXIST错误!if 不存在那么就是创建新的FIFO。if仅仅是打开FIFO,那么就可以使用open()函数就可以了!

对于一个已经创建好的FIFO来说,只能是读或者写,不能同时进行。( 半双工 )

对于FIFO来说:先进先出,所以从开头读,从尾部写

查看所创建的管道:
ls -lF /tmp/my_fifo

> prwxr-xr-x 1 pt gf 0 2012-01-13 10:43 /tmp/my_fifo|

注意:ls命令的输出结果中的第一个字符为p,表示这是一个管道。最后的|符号是由ls命令的-F选项添加的,它也表示是这是一个管道。


.

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/stat.h> 

int main() 

int res;

if ( ( res = mkfifo("/tmp/my_fifo", 0777) ) == 0 ) //!> 对于mkfifo来说:成功则返回0,否则返回-1
//!> 注意全名就是 /tmp/my_fifo 其实就是tmp路径下的 my_fifo 文件
printf("FIFO created\n"); 


return 0;
//!> exit(EXIT_SUCCESS);
}

.

1.简介:
两个独立的程序:
1. 生产者程序,它在需要时创建管道,然后尽可能快地向管道中写入数据。
2. 消费者程序,它从FIFO中读取数据并丢弃它们。

2.代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "/tmp/Linux/my_fifo" //!> FIFO全名
#define BUFFER_SIZE 40 //!> PIPE_BUF,linux 2.6.11 以前,是4096,以后是65536。
#define TEN_MEG 36 * 10 //!> ( 1024 * 1024 * 10 ) 

int main()
{
int pipe_fd;
int res;
int open_mode = O_WRONLY; //!> 生产者:只是写data就可以了~

int bytes = 0;
char buffer[BUFFER_SIZE + 1] = "abcdefghijklmnopqrstuvwxyz"; //!> 36 个测试字符




if( access( FIFO_NAME, F_OK ) == -1 ) //!> if 文件不存在( if 文件存在,那么access返回值是0 )
{
res = mkfifo( FIFO_NAME, 0777 ); //!> 文件不存在就创建文件

if( res != 0 ) //!> 文件创建成功就返回0,否则返回-1,此处就是没有成功的情况
{
fprintf( stderr, "Can't 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 ); //!> 打开创建的文件是只写模式 ( 注意open返回的是文件描述符 )
//!> 从此处开始:生产者开始写文件,所以需要启动Device,那么就由“消费者”来执行CPU
printf( "Process %d ---> FILE ID: %d\n", getpid(), pipe_fd );

if( pipe_fd != -1 ) //!> 对于open来说,成功返回FILE_ID,失败返回-1 ( 此处是成功~ )

char test_char = '0'; 

while( bytes < TEN_MEG ) //!> 指定大小为TEN_MEG
{
//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
test_char++; //!> 作为测试字符( 标志每次读出来的是不是写入的还是仅仅是开头的数据 )
int len = strlen( buffer ); //!> 测试字符串处理( 仅仅是测试而已 )
buffer[len] = test_char; 
buffer[len+1] = '\0';
//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

res = write( pipe_fd, buffer, BUFFER_SIZE ); //!> 将数据写到文件中 ( 注意返回的是一次写入的字节数 )

if( res == -1 )
{
fprintf( stderr, "Write error on pipe\n" );
exit( EXIT_FAILURE );
}

printf("生产者写入:---> ");
puts( buffer );
bytes += res;
}
close( pipe_fd ); //!> 关闭文件描述符( 也就是关闭文件s )
}
else
{
exit( EXIT_FAILURE );
}

printf( "Process %d finish... \n", getpid() );

exit( 0 );
}



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "/tmp/Linux/my_fifo" 

//!> 注意消费者需要访问的路径就是“生产者”的写入路径文件(一个写,一个读)

#define BUFFER_SIZE 40 

//!>PIPE_BUF //!> PIPE_BUF,linux 2.6.11 以前,是4096,以后是65536。

int main()
{
int pipe_fd;
int res;
int open_mode = O_RDONLY; //!> 消费者只读
int bytes = 0;
char buffer[BUFFER_SIZE + 1];

memset( buffer, '\0', sizeof( buffer ) ); //!> 分配给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 )
{
res = read( pipe_fd, buffer, BUFFER_SIZE ); //!> 读取第一次( 以防没有数据 )
while( res > 0 )
{
bytes += res;

printf("消费者读出:---> ");
puts( buffer );

res = read( pipe_fd, buffer, BUFFER_SIZE ); //!> 从文件中读取data到buffer中 
}

close( pipe_fd );
}
else
{
exit( EXIT_FAILURE );
}

printf("Process %d finished, %d bytes read/n", getpid(), bytes); 
exit(EXIT_SUCCESS); 
}

3.运行及结果分析:

原理:消费者和生产者共用这个 有名pipe(FIFO),本质相当与是指针而已,
都往后只,取值也往后取值,所以每次取的顺序就是写入的顺序

编译这两个程序:
gcc –o c cons.c
gcc –o p per.c

运行这两个程序:
将生产者程序后台运行
./p &
再运行消费者程序
./c

终端COPY:

pt@ubuntu:~/桌面/net programming/进程通信/FIFO/生产者消费者$ ./p &

[1] 6055
Process 6055 opening FIFO O_WRONLY

pt@ubuntu:~/桌面/net programming/进程通信/FIFO/生产者消费者$ ./c

Process 6056 opening FIFO O_RDONLY
Process 6056 result 3
Process 6055 ---> FILE ID: 3
生产者写入:---> abcdefghijklmnopqrstuvwxyz1 //!> 说明:生产者的读出就是写入的内容
生产者写入:---> abcdefghijklmnopqrstuvwxyz12 //!> 也就是说:指针是跟着走的,并不是普通的文件
消费者读出:---> abcdefghijklmnopqrstuvwxyz1
生产者写入:---> abcdefghijklmnopqrstuvwxyz123
消费者读出:---> abcdefghijklmnopqrstuvwxyz12
生产者写入:---> abcdefghijklmnopqrstuvwxyz1234
消费者读出:---> abcdefghijklmnopqrstuvwxyz123
生产者写入:---> abcdefghijklmnopqrstuvwxyz12345
消费者读出:---> abcdefghijklmnopqrstuvwxyz1234
生产者写入:---> abcdefghijklmnopqrstuvwxyz123456
消费者读出:---> abcdefghijklmnopqrstuvwxyz12345
生产者写入:---> abcdefghijklmnopqrstuvwxyz1234567
消费者读出:---> abcdefghijklmnopqrstuvwxyz123456
生产者写入:---> abcdefghijklmnopqrstuvwxyz12345678
消费者读出:---> abcdefghijklmnopqrstuvwxyz1234567
生产者写入:---> abcdefghijklmnopqrstuvwxyz123456789
消费者读出:---> abcdefghijklmnopqrstuvwxyz12345678
消费者读出:---> abcdefghijklmnopqrstuvwxyz123456789
Process 6055 finish...
Process 6056 finished, 360 bytes read/n[1]+ Done ./p


.

1.简介
server 要以一个熟知路径创建一个 FIFO 来监听所有的 client 的请求!所以可以是开启读pipe口,关闭写口
对于子进程来说,就是要写入数据就可以了!
但是为了返回server的反馈数据,那么子进程还是需要pipe来接受data from server;可以以自己的ID来创建!

2.代码:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define SERVER_FIFO_NAME "/tmp/Linux/server_fifo"
#define CLIENT_FIFO_NAME "/tmp/Linux/client_%d_fifo" 
//!> 注意对于不同的client需要创建不同的自己的FIFO(可以根据自己唯一的pid来格式化)

#define BUFFER_SIZE PIPE_BUF
#define MESSAGE_SIZE 20
#define NAME_SIZE 256

typedef struct message //!> message数据结构
{
pid_t pid;
char data[MESSAGE_SIZE + 1];
}message;



#include "cs.h"

int main()
{
int server_fifo_fd; //!> 打开server FIFO的描述符
int client_fifo_fd; //!> 打开client FIFO的描述符

message msg; //!> 接收client的请求 de 信息

char * p; //!> 此value仅仅是为了简单处理data而已

char client_fifo_name[NAME_SIZE]; //!> 先获得client的msg,取出client的pid,拼接成client的pid,用于反馈信息

if( ( access( SERVER_FIFO_NAME, F_OK ) ) == -1 ) //!> if server的FIFO还不存在,就创建FIFO文件
{
if( ( mkfifo( SERVER_FIFO_NAME, 0777 ) ) == -1 ) //!> 创建server的FIFO
{
printf("Fail To Create SERVER FIFO ");
exit( EXIT_FAILURE ); //!> failure
}
}

server_fifo_fd = open( SERVER_FIFO_NAME, O_RDONLY );

if( server_fifo_fd == -1 ) //!> fail to open the fifo file
{
printf("Fail to open server_fifo_file");
exit( EXIT_FAILURE );


sleep(5); 

while( ( read( server_fifo_fd, &msg, sizeof( msg ) ) ) > 0 ) 
//!> exist the msg( 主要是为了获取data加以处理,还有:获取client的pid,来获得反馈的client的FIFO PIPE )

//!> 注意:此处一定是while不是if,因为有多个客户端,所以需要一个所谓死循环(也就是当最后一个客户都关闭就结束)
p = msg.data; //!> 指针处理(简单处理数据而已)
while( *p ) //!> 当字符串还没有结束
{
( *p ) = toupper( *p ); //!> 处理成大写字母而已
p++;
}

sprintf( client_fifo_name, CLIENT_FIFO_NAME, msg.pid ); //!> 将数据格式化成client的标准名称

client_fifo_fd = open( client_fifo_name, O_WRONLY );
if( client_fifo_fd == -1 ) //!> Fait to open
{
printf( "Fait to open client FIFO..." );
exit( EXIT_FAILURE );


write( client_fifo_fd, &msg, sizeof( msg ) ); //!> write into client fifo
close( client_fifo_fd ); 
}
close(server_fifo_fd); 
unlink(SERVER_FIFO_NAME); //!> 关闭链接

exit(EXIT_SUCCESS);
}



#include "cs.h"

int main()
{
int server_fifo_fd;
int client_fifo_fd;

char client_fifo_name[NAME_SIZE];

message msg;
msg.pid = getpid();
sprintf( client_fifo_name, CLIENT_FIFO_NAME, getpid() ); //!> 格式化此client的名称

if( ( access( client_fifo_name, F_OK ) ) == -1 ) //!> 没有此FIFO文件就创建 
{
if( ( mkfifo( client_fifo_name, 0777 ) ) == -1 ) //!> Fail to create client fifo
{
printf( "Fail to create client fifo..." );
exit( EXIT_FAILURE );
}
}

server_fifo_fd = open( SERVER_FIFO_NAME, O_WRONLY ); //!> 写入内容
if( server_fifo_fd == -1 )
{
printf("Fail to open server fifo...");
exit( EXIT_FAILURE );


sprintf( msg.data, "hello from %d...", msg.pid ); //!> 格式化到msg中等待发送

printf( "%d send the msg...", getpid() );
write( server_fifo_fd, &msg, sizeof( msg ) );
close( server_fifo_fd ); 

client_fifo_fd = open( client_fifo_name, O_RDONLY );
if( client_fifo_fd == -1 )
{
printf("Fail to open client fifo...");
exit( EXIT_FAILURE );


if( ( read( client_fifo_fd, &msg, sizeof( msg ) ) ) > 0 )
{
printf( "\nClient %d recived msg: ---> %s \n", msg.pid, msg.data );
}

close(client_fifo_fd); 
unlink(client_fifo_name); 

exit( EXIT_SUCCESS );
}

最后来说:其实本质就是 创建一个文件作为交换区而已!结果也就是创建文件,打开文件,操作文件。。。

0 0
原创粉丝点击