管道通信
进程间管道通信方式可以通过man 7 pipe来查看;
匿名管道
单工管道
打开管道:使用popen(),关闭管道使用pclose(),打开文件使用fopen(),关闭文件使用pclose();打开管道使用的函数FILE *popen(const char *command,const char *open_mode);command:表示命令行字符串;open_mode:表示文件的权限,r:表示只读;w:表示只写;返回值,成功应该返回文件描述符,失败了返回NULL;
管道的操作:
读管道:size_t fread(void *buffer,size_t size,size_t count,FILE stream);buffer表示用于接收数据的内存地址;size:表示读取每个数据项的字节数;count:表示数据项个数;stream:表示输入流;返回值:当返回值>count时,函数出错;当返回值是整数时,表示真正读取的数据项的个数;
写管道:size_t fwrite(const void buffer,size_t size, size_t count,FILE *stream);buffer:表示的是写入数据的内存地址;size:表示的是写入数据项的字节数;count:表示的是写入数据项的个数;stream:表示的是写入目标文件指针;当返回值大于count时,表示出错;如果返回值是正数,表示实际写入数据项的个数;
关闭管道:int pclose(FILE *stream);stream表示的是文件描述符,返回值-1表示失败;返回值0表示成功;
管道的本质是启用shell和命令两个进程,从命令进程中读/写文件流,管道通信解决的是exec和system无法返回输出数据问题;
单工管道的特点:方便实用系统自带的功能,并且可以执行比较复杂的shell,默认启动两个基础进程,但是执行效率会比较低;
pipe.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
int fd[2];
pipe(fd);
char in[] = "Hello pipe";
write(fd[1],in,sizeof(in));
printf("write:%s\n",in);
char out[sizeof(in)]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("read:%s\n",out);
close(fd[0]);
close(fd[1]);
}
pipe01.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
char in[] = "Hello pipe";
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
}else{// parent
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
}
close(fd[0]);
close(fd[1]);
}
pipe02.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
char in[] = "Hello pipe";
sleep(3);
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
}else{// parent
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
}
close(fd[0]);
close(fd[1]);
}
pipe03.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
char in[] = "Hello pipe";
sleep(3);
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
}else{// parent
fcntl(fd[0],F_SETFL,O_NONBLOCK);
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
}
close(fd[0]);
close(fd[1]);
}
pipe04.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
close(fd[0]);
char in[] = "Hello pipe";
sleep(3);
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
close(fd[1]);
}else{// parent
close(fd[1]);
fcntl(fd[0],F_SETFL,O_NONBLOCK);
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
close(fd[0]);
}
}
read_cmd.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
FILE* fd = popen("ps -ef","r");
//FILE* fd = popen("ls -l","r");
char buf[BUFSIZ];
size_t count = 0;
printf("read data:\n");
do{
memset(buf,'\0',BUFSIZ);
size_t n = fread(buf,sizeof(char),BUFSIZ-1,fd);
if( n > BUFSIZ - 1 ){
perror("fread error");
exit(EXIT_FAILURE);
}
count += n;
printf("%s",n,buf);
}while(!feof(fd));
printf("total size:%ld\n",count);
pclose(fd);
}
write_cmd.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
FILE* fd = popen("wc","w");
//FILE* fd = popen("ls -l","r");
//char str[] = "123 456";
char str[] = "123 456\n";
size_t n = fwrite(str,sizeof(char),sizeof(str),fd);
if(n > sizeof(str)){
fprintf(stderr,"FILE:%d,LINE:%d-fwrite error",__FILE__,__LINE__);
exit(EXIT_FAILURE);
}
pclose(fd);
}
半双工管道
创建管道:int pipe(int filedes[2]),定义两个管道,0读1写;也就是使用filedes[0]表示读管道;filedes[1]表示写管道;返回值-1表示失败,返回值0表示成功;
读写管道:ssize_t write(int fd,const void *buf,size_t nbyte);其中fd表示问价描述符;buf:写入数据的内存单元;nbyte:写入文件指定的字节数;返回值-1表示管道写入失败;如果返回值为正数,表示写入的字节数;ssize_t read(int fd, void *buf,size_t count),其中fd表示文件描述符;buf表示写入数据的内存单元;返回值-2表示出错,0表示管道中没有数据,正数表示读取的字节数;
管道的控制:如果管道是空的,read()函数默认进入阻塞状态;函数fcntl(int fd, int cmd,long arg );fd表示文件描述符,cmd:F_GETFL表示获取文件描述符;F_SETFL表示设置文件描述符;arg:O_NONBLOCK表示非阻塞,O_BLOCK表示阻塞;fcntl(filedes,F_SETFL,O_NONBLOCK)表示将文件描述符改为非阻塞的;
半双工管道的本质:POSIX标准:文件描述符,整型:int 整数,常用的函数包括open、close、read、write、定位偏移lseek;ANSI C标准:常用的函数,fopen、fclose、fread、fwrite、fseek;文件流是文件系统之上的封装,文件流通过增加缓冲区域减少读写系统的调用次数来提高读写效率,在晋城的用户空间封装成FILE 结构,以提高可移植性和效率;
两个案例:
ps_self01.c:用于打印自身的进程信息,但是未打印子进程信息
#include <stdio.h>
#include <stdlib.h>
int main(){
char cmd[128]={0};
sprintf(cmd,"ps -p %d -o pid,ppid,stat,cmd",getpid());
system(cmd);
}
ps_self02.c:用于打印出自身和ps进程的信息,但是未打印出grep子进程信息
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
FILE* readfd = popen("ps -o pid,ppid,stat,cmd","r");
char buf[BUFSIZ]={0};
fread(buf,sizeof(char),sizeof(buf),readfd);
fclose(readfd);
char cmd[128]={0};
sprintf(cmd,"grep %d",getpid());
FILE* writefd = popen(cmd,"w");
fwrite(buf,sizeof(char),strlen(buf),writefd);
fclose(writefd);
}
ps_self03.c:用于打印出自身和ps以及grep子进程的信息
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){
dup2(fd[1],STDOUT_FILENO);
execlp("ps","ps","-o","pid,ppid,stat,cmd",0);
}else{
if(!fork()){
dup2(fd[0],STDIN_FILENO);
char buf[128] = {0};
sprintf(buf,"%d",getppid());
execlp("grep","grep",buf,0);
}else{
wait(NULL);
wait(NULL);
}
}
close(fd[0]);
close(fd[1]);
}
ps_self04.c:更加规范的关闭管道的方式:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){
close(fd[0]);
dup2(fd[1],STDOUT_FILENO);
execlp("ps","ps","-o","pid,ppid,stat,cmd",0);
close(fd[1]);
}else{
if(!fork()){
close(fd[1]);
dup2(fd[0],STDIN_FILENO);
char buf[128] = {0};
sprintf(buf,"%d",getppid());
execlp("grep","grep",buf,0);
close(fd[0]);
}else{
close(fd[0]);
close(fd[1]);
wait(NULL);
wait(NULL);
}
}
}
ps_pther.c:函数用于打印参数中命令相关的父子进程;
命名管道
创建命名管道:int mkfifo(pathname,mode);pathname:表示文件名称路径,文件必须不存在,否则就会出错;mode:表示模式;返回值:0表示成功;非0表示失败;
打开FIFO文件:int open(const char *path,int mode),pathname表示文件路径;mode:O_REONLY:表示阻塞只读;O_RDONLY|O_NONBLOCK表示非阻塞只读;O_WRONLY:表示阻塞只写;O_WRONLY|O_NONBLOCK:表示非阻塞只写;如果返回-1表示失败,否则返回的是文件描述符;
命名管道可以是非亲缘进程之间,读写必须同时执行,否则就会阻塞;
mkfifo.c:用于创建命名管道:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(){
if(-1 == mkfifo("/tmp/test",0644)){
perror("mkfifo error");
return 1;
}
}
fifocreate.c:将数据写入命名管道:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argv){
int c,mode=0644;
while((c = getopt(argc,argv,"t:"))!=1){
switch(c){
case 't':
mode = strtol(optarg,NULL,8);
break;
}
}
if(optind != argc - 1){
printf("usage:%s [-t <mode>] <pathname>\n",argv[0]);
return 1;
}
if(-1 == mkfifo(argv[optind],mode)){
perror("mkfifo error");
return 1;
}
}
fiforead.c:用于读取命名管道数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("/tmp/test2",O_RDONLY);
if(-1 == fd){
perror("open error");
return 1;
}
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
fcntl(fd,F_SETFL,O_NONBLOCK);
read(fd,buf,BUFSIZ);
printf("read: %s\n",buf);
}
需要注意的是fifowrite和fiforead要同时执行,才会传递数据,否则会陷入阻塞;
不恰当的非阻塞方式:
fifowrote01.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("/tmp/test",O_WRONLY);
if(-1 == fd){
perror("open error");
return 1;
}
char str[] = "Hello fifo";
write(fd,str,sizeof(str));
printf("write:%s\n",str);
}
不恰当的读取命名管道的方式:fiforead01.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("/tmp/test",O_RDONLY);
if(-1 == fd){
perror("open error");
return 1;
}
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
read(fd,buf,BUFSIZ);
printf("read:%s\n",buf);
}
正确的非阻塞方式:
写入命名管道:
fifowrite02.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("/tmp/test",O_WRONLY|O_NONBLOCK);
if(-1 == fd){
perror("open error");
return 1;
}
char str[] = "Hello fifo";
write(fd,str,sizeof(str));
printf("write:%s\n",str);
}
正确的读取命名管道的方式:
fiforead02.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("/tmp/test",O_RDONLY|O_NONBLOCK);
if(-1 == fd){
perror("open error");
return 1;
}
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
read(fd,buf,BUFSIZ);
printf("read:%s\n",buf);
}
需要注意的是:fcntl(fd,F_SETFL,O_NONBLOCK)不能控制命名管道与读写阻塞问题,必须在open命名管道时,指定非阻塞打开;
删除命名管道:
fiform.c:
#include <unistd.h>
int main(){
unlink("/tmp/test");
}
dup
dup01.c:用于复制标准输出,新文件描述符和就文件描述符不同,但是具备就文件描述符功能
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(){
int fd = dup(STDOUT_FILENO);
fprintf(fdopen(fd,"w"),"%d printf:Hello dup\n",fd);
}
dup02.c:复制文件描述符,新文件描述符与旧文件描述符不同,但是具有就文件描述符功能;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
int main(){
int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE);
char str[]="Hello dup\n";
write(fd,str,sizeof(str));
int cp_fd = dup(fd);
printf("copy %d to %d",fd,cp_fd);
write(cp_fd,str,sizeof(str));
//fprintf(fdopen(fd,"w"),"%d printf:Hello dup\n",fd);
close(fd);
}