进程间通信之管道
规则分析:
适用范围:
管道(pipe)是unix最早的进程间通信方式之一,主要用于有亲缘关系的进程之间通信。
原型:
int pipe(int filedes[2]);(filedes以下简称fd)
使用方法:
其中fd[0]为可用作读取端的文件描述符,fd[1]为可用作写入端的文件描述符,这样一对标识符,用于标识一条管道。
步骤:
- 先用pipe创建一个管道
- 然后fork()出一个子进程,则父,子进程都有fd[0],fd[1]。
- 若父进程用作写入端,则close掉父进程中的fd[0],也就是读标识符。同理close掉子进程的fd[1]。
这样父子进程中就建立了一条通讯管道,可以由父进程写数据,然后由子进程进行读取。
怎么写?怎么读?使用i/o中的write,read就可以了。
读端规则:rtSize = read(readfd,buff,buffSize);阻塞情况下
定义:readfd为读端文件描述符,读取到的内容存在buff中,buffsize为请求的数据大小,返回值rtSize为读到的数据大小。
系统默认内核有一个PIPE_BUF(在include/linux/limits.h中定义),定义了管道缓冲区容量,也就是能放多大数据。
当前管道缓冲区中所有数据的大小定义为curSize;必有curSize <= PIPE_BUF。
- 写端本来就不存在,那么直接就返回rtSize = 0,不会阻塞。
- 写端本来存在后来全部都关闭了(writefd引用计数为0),那么读完管道中的curSize数据后,再去读就会返回rtSize = 0,不会阻塞。
- 如果有写端存在,请求读取管道数据时,如果管道有数据则执行下面的流程,如果没有数据,则会一直阻塞直到写端写入数据,或者写端关闭。
if( buffSize > PIPE_BUF )//请求数据大小大于缓冲区容量
{
rtSize = curSize;//返回全部的缓冲区数据
}
else
{
if( curSize >= buffSize )//缓冲区数据 大于等于 请求数据
{
rtSize = buffSize;//返回请求数据大小
}
else
rtSize = curSize;//返回当前缓冲区所有数据
}
写端规则 :rtSize = write(writefd,buff,buffSize);阻塞情况下
定义:writefd为写端文件描述符,要写入的内容放在buff中,buffSize为请求写入的数据大小,返回值rtSize为写入的实际大小。
- 如果管道的读端不存在时,写入数据会产生SIGPIPE信号,默认终止当前写进程。
- 如果管道读端存在时,执行以下流程。
- 如果buffSize < PIPE_BUF 则能够保证数据的原子性(也就是连续性),否则不能。
if(buffSize + curSize > PIPE_BUF)//写入后总数据如果将大于管道缓冲则阻塞,知道有管道读端读走数据
else
rtSize = buffSize;//否则就全部写入
第三条原子性解释:
也就是如果PIPE_BUF = 10,而写端A要写入AAAAAAAAAA(10个),写端B要写入BBBBB。
那么经过管道的数据肯定是 AAAAAAAAAABBBBB 或者BBBBBAAAAAAAAAA。
但是如果要写入AAAAAAAAAAAAAAA(15个)
那么数据就可能是AAAAABBBBBAAAAAAAAAA 当然还有别的可能,就无法保证数据的原子性
实例:
这是《unix网络编程第2卷》中的一个例子,书上是原型是子进程充当服务器,父进程充当客户端
之间创建两条管道A,B用于通信,首先由客户端读入用户的输入的文件名,然后有客户端写入管道A,服务器从管道A读出文件名,
打开文件读出文件中的内容,写入管道B,有客户端从管道B中读取出来并输出。
下面是改写了一下的例子:
客户端主线程循环写入管道多次文件名,并创建个线程去读管道B的数据,而服务端基本不变。
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <errno.h>
#include <string>
#define MAXLINE 100
#define TIMES 10
#define SAFE_DELETE(x) if(x){delete (x);(x) = NULL;}
typedef unsigned int uint32;
typedef struct stARG
{
int fd;
}ARG;
void client(int readfd,int writefd)
{
printf("startClient\n");
uint32 times = TIMES;
size_t len;
char buff[MAXLINE];
memset(buff,0,MAXLINE);
printf("输入文件名:");
fgets(buff,MAXLINE,stdin);
len = strlen(buff);
if(buff[len-1]=='\n')
len--;
while(times--)
{
write(writefd,buff,len);
printf("enter times:%u\n",TIMES - times);
sleep(1);
}
}
void server(int readfd,int writefd)
{
printf("startServer\n");
FILE *file;
ssize_t n;
size_t len = 0;
char *buff = new char(MAXLINE);
while((n=read(readfd,buff,MAXLINE)) > 0)
{
if(n>0)
{
buff[n]='\0';
}
if((file = fopen(buff,"r")) == NULL)
{
printf("文件%s读出失败\n",buff);
snprintf(buff+n,sizeof(buff)-n,"can't open,%s/n",strerror(errno));
n = strlen(buff);
write(writefd,buff,n);
}
else
{
printf("文件%s读出开始\n",buff);
while((n = getline(&buff,&len,file))>0)
{
buff[n] = '\0';
//printf("文件读出%s\n",buff);
write(writefd,buff,n);
}
}
}
SAFE_DELETE(buff);
}
void *function(void * arg)
{
int n = 0;
uint32 times = 0;
ARG *info = (ARG*)arg;
char buffRead[MAXLINE];
memset(buffRead,0,MAXLINE);
while((n=read(info->fd,buffRead,MAXLINE)) > 0)
{
buffRead[n] = '\0';
printf("管道客户端读出times:%u\n%s\n",++times,buffRead);
}
pthread_exit(NULL);
printf("管道客户端读出线程退出");
}
int main(int argc,char** argv)
{
int pipe1[2],pipe2[2];
pid_t childpid;
pipe(pipe1);
pipe(pipe2);
if((childpid=fork())==0)
{
close(pipe1[1]);
close(pipe2[0]);
server(pipe1[0],pipe2[1]);
printf("子进程退出");
exit(0);
}
else
{
close(pipe1[0]);
close(pipe2[1]);
/*创建一个单独的线程来读取*/
pthread_t tid = 0;;
ARG arg;
arg.fd = pipe2[0];
pthread_create(&tid,NULL,function,(void*)&arg);
client(pipe2[0],pipe1[1]);
waitpid(childpid,NULL,0);
}
return 0;
}