首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

Linux系统编程学习札记(十)进程间通信IPC 1

2012-08-07 
Linux系统编程学习笔记(十)进程间通信IPC 1进程间通信IPC:我们以前介绍过进程控制原语,看到怎么创建多个进

Linux系统编程学习笔记(十)进程间通信IPC 1
进程间通信IPC:
我们以前介绍过进程控制原语,看到怎么创建多个进程。但是进程之间交互信息的方式只介绍了通过
fork或者exec继承父进程的打开文件或者通过文件系统。
经典的进程通信方式有:管道、FIFOs,消息队列,信号灯和共享内存。
1、管道:
管道是Unix系统IPC最古老的形式,PIPE有以下限制:
1)是半双工的,一些系统提供了全双工的管道,但是为了可移植性,我们最好不要作此假设。
2)管道只能在有共同祖先的进程之间。一般一个进程创建一个管道,然后进程调用fork,
然后管道在父进程和子进程之间使用。
FIFO摆脱了第二个限制,Unix domain socket和具名STREAMS-based管道可以摆脱这两者的限制。
尽管有以上限制,半双工的管道仍然是最常使用的IPC。
1)创建管道:

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

调用成功之后,通过filedes返回了两个文件描述符:filedes[0]被打开可以读取,filedes[1]被打开可以写,
filedes[1]的输出时filedes[0]的输入,fstat函数返回这两个文件描述符是FIFO,通过宏S_ISFIFO来判断
是否是一个管道。
一个进程的管道几乎没有用处,通常一个进程调用pipe创建管道,然后调用fork,在父子进程之间创建IPC通道。
从父进程到子进程的管道,父进程关闭读端的管道(filedes[0]),子进程关闭写端的管道(filedes[1]);从
子进程到父进程的管道,父进程关闭filedes[1],子进程关闭filedes[2].
当一端的管道关闭:
1)如果我们从一个写端已经关闭的管道读取,当所有的数据都已经读完,read返回0,指示文件结束。
2)如果我们从一个读段已经关闭的管道进行写操作,将产生SIGPIPE信号。当我们忽略或者处理它时,write返回
-1,errno设置成EPIPE。
当我们往管道里面写数据的时候,常量PIPE_BUF指示了内核管道的buffer大小,当有多个进程向该管道写时,写小
于等于PIPE_BUF大小的数据,不会交错,大于PIPE_BUF会有可能导致数据的交错。我们可以使用sysconf来查看
PIPE_BUF的大小。
例子:
#include <unistd.h>#include <sys/types.h>#include <stdlib.h>int main(void){int n;int fd[2];pid_t pid;char line[MAXLINE];if(pipe(fd) < 0){perror("pipe");exit(1);}if((pid = fork()) < 0){perror("fork");exit(1);}else if(pid > 0){close(fd[0]);write(fd[1],"hello,world\n",12);}else{close(fd[1]);n = read(fd[0],line,MAXLINE);write(STDOUT_FILENO,line,n);}exit(0);}

这个例子创建了一个从父进程到子进程的一个pipe,并发送 了一些数据。
我们复制pipe的文件描述符到标准输入输出,这个会比较有意思,我们经常从运行我们的程序,从标准输入读或者写到
标准输出。
例子:
我们考虑写一个程序,显示一些输出,一次一页,我们可以使用系统自带的分页程序,我们避免把数据写到临时文件中或者
使用system,我们想建立从标准输入到分页的管道来实现。
#include <unistd.h>#include <sys/types.h>#include <stdlib.h>#include <sys/wait.h>#include <stdio.h>#define DEF_PAGER "/bin/more"int main(int argc,char *argv[]){int n;int fd[2];pid_t pid;char *pager, *argv0;char line[MAXLINE];FILE *fp;if(argc != 2){printf("Usage: a.out <pathname>");exit(1);}if((fp = fopen(argv[1],"r")) == NULL){perror("fopen");exit(1);}if(pipe(fd) < 0){perror("pipe");exit(1);}if((pid = fork()) < 0){perror("fork");exit(1);}else if(pid > 0){/* parent */close(fd[0]);/* close read end */while(fgets(line,MAXLINE,fp) != NULL){n = strlen(line);if(write(fd[1],line,n) != n){perror("write");exit(1);}}if(ferror(fp)){perror("fgets");exit(1);}close(fd[1]); /* close write end of the pipe for reader */if(waitpid(pid,NULL,0) < 0){perror("waitpid");exit(1);}exit(0);}else{/* child */close(fd[1]);if(fd[0] != STDIN_FILENO){if(dup2(fd[0],STDIN_FILENO) != STDIN_FILENO){perror("dup2");exit(1);}close(fd[0]);/* don't need it after dup2}/* get arguments for execl() */if((pager = getenv("PAGER")) == NULL){pager = DEF_PAGER;}if((argv0 = strrchr(pager,'/')) != NULL)argv0++;/* step past rightmost slash */elseargv0 = pager;if(execl(pager,argv0,(char *)0) < 0){perror("execl");exit(1);}}exit(1);}

2)popen和pclose函数:
既然一些常见的操作:比如创建一个从当前进程到另一个进程的pipe,去读它的输出或者向它的输入发送数据,标准的I/O支持popen和pclose
这两个函数为我们帮我们做了很多的脏活累活:创建一个管道,fork一个子进程,关闭没有用的管道的端,执行shell去运行命令,最后等待
命令的执行的终止。
#include <stdio.h>FILE *popen(const char *cmdstring, const char *type);int pclose(FILE *fp);

popen执行一个fork和exec去执行cmdstring,返回标准的I/O文件指针。如果type是"r",文件指针和cmdstring的标准输出相连。
如果type是"w",则和cmdstring的标准输入相连。
一个好记的方法是和fopen对比,"r"表示可读,"w"表示可写。
pclose关闭标准I/O,等待命令执行终止,返回shell的执行状态。
我们使用popen来重写上面的例子:
#include <unistd.h>#include <sys/types.h>#include <stdlib.h>#include <sys/wait.h>#include <stdio.h>#define PAGER "${PAGER:-more}"int main(int argc, char *argv){char line[MAXLINE];FILE *fpin, *fpout;if(argc != 2){printf("usage: a.out <pathname>\n");exit(1);}if((fpin = fopen(argv[1],"r")) == NULL){perror("fopen");exit(1);}if((fout = popen(PAGER,"w")) = NULL){perror("popen");exit(1);}while(fgets(line,MAXLINE,fpin) != NULL){if(fputs(line,fpout) == EOF){perror("fputs");exit(1);}}if(ferror(fpin)){perror("fgets");exit(1);}if(pclose(fpout)){perror("pclose");exit(1);}exit(0);}

2、FIFOs:
FIFOs经常被称为具名管道。管道只能被有共同祖先的进程之间使用。FIFO可以在不相关的进程之间交换数据。
FIFO是一种文件类型,可以通过S_ISFIFO来测试它。
创建一个FIFO和创建一个文件很相似。
1)创建一个FIFO:
#include <sys/stat.h>int mkfifo(const char *path, mode_t mode;);

成功返回0,失败返回-1,并设置errno。
例子:
#define FIFO_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S _IROTH)if(mkfifo("myfifo",FIFO_PERMS) == -1)perror("Failed to create myfifo");

删除FIFO和删除普通文件一样。
例子:
父进程读取子进程写到具名管道的数据:
#include <errno.h>#include <fcntl.h>#include <stdio.h>#include <string.h>#include <unistd.h>#include <sys/stat.h>#include <sys/wait.h>#define BUFSIZE 256#define FIFO_PERM (S_IRUSR | S_IWUSR)int dofifochild(const char *fifoname, const char *idstring);int dofifoparent(const char *fifoname);int main(int argc,char *argv[]){pid_t childpid;if(argc != 2){fprintf(stderr,"Usage: %s pipename\n",argv[0]);return 1;}if(mkfifo(argv,FIFO_PERM) == -1){if(errno != EEXIT){fprintf(stderr,"[%ld]": failed to create name pipe %s: %s\n",(long)getpid(),argv[1],strerror(errno));return 1;}}if(childpid = fork()) == -1){perror("Failed to fork");return 1;}if(childpid == 0){return dofifochild(argv[1],"this was written by the child");}else{return dofifochild(argv[1]);}}int dofifochild(const char *fifoname, const char *idstring){char buf[BUFSIZE];int fd;int rval;ssize_t strsize;fprintf(stderr,"[%ld]:(child) about to open FIFO %s...\n",(long)getpid(),fifoname);if(fd = open(fifoname,O_WRONLY) == -1){fprintf(stderr,"[%ld]:failed to open name pipe %s for write: %s\n",(long)getpid(),fifoname,strerror(errno));return 1;}rval = snprintf(buf,BUFSIZE,"[%ld]:%s\n",(long)getpid(),idstring);if(rval < 0){fprintf(stderr,"[%ld]": failed to make the string:\n",(long)getpid());return 1;}strsize = strlen(buf)+1;fprintf(stderr,[%ld]:about to write...\n",(long)getpid());rval = write(fd,buf,strsize);if(rval != strsize){fprintf(stderr,"[%ld]:failed to write to pipe: %s\n",(long)getpid(),strerror(errno));return 1;}fprintf(stderr,"[%ld]:finishing...\n",(long)getpid());return 0;}int dofifoparent(const char *fifoname) {   char buf[BUFSIZE];   int fd;   int rval;   fprintf(stderr, "[%ld]:(parent) about to open FIFO %s...\n",                       (long)getpid(), fifoname);      if ((fd = open(fifoname, FIFO_MODES)) == -1) {      fprintf(stderr, "[%ld]:failed to open named pipe %s for read: %s\n",             (long)getpid(), fifoname, strerror(errno));      return 1;   }   fprintf(stderr, "[%ld]:about to read...\n", (long)getpid());   rval = read(fd, buf, BUFSIZE);   if (rval == -1) {      fprintf(stderr, "[%ld]:failed to read from pipe: %s\n",             (long)getpid(), strerror(errno));      return 1;   }   fprintf(stderr, "[%ld]:read %.*s\n", (long)getpid(), rval, buf);   return 0;}

2)使用FIFO客户端服务端通信:
我们使用简单的协议进行来进行客户端服务端通信,客户端将log信息写到命名管道中,服务端从命名管道中读取
然后写入文件中:
服务端:
#include <fcntl.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include<sys/stat.h>#define FIFOARG 1#define BLKSIZE 1024#define FIFO_PERMS (S_IRWXU | S_IWGRP | S_IWOTH)int main(int argc, char *argv[]){int requestfd;if(argc != 2){fprintf(stderr,"Usage: %s fifoname > logfile\n",argv[0]);return 1;}if((mkfifo(argv[FIFOARG],FIFO_PERMS) == -1) && (errno != EEXIST)){perror("Server failed to create FIFO");return 1;}if((requestfd = open(argv[FIFOARG],O_RDWR)) == -1){perror("Server failed to open its FIFO");return 1;}copyfile(requestfd,STDOUT_FILENO);return 1;}int copyfile(int fromfd, int tofd) {   char *bp;   char buf[BLKSIZE];   int bytesread, byteswritten;   int totalbytes = 0;   for (  ;  ;  ) {      while (((bytesread = read(fromfd, buf, BLKSIZE)) == -1) &&             (errno == EINTR)) ;         /* handle interruption by signal */      if (bytesread <= 0)          /* real error or end-of-file on fromfd */         break;      bp = buf;      while (bytesread > 0) {         while(((byteswritten = write(tofd, bp, bytesread)) == -1 ) &&              (errno == EINTR)) ;        /* handle interruption by signal */         if (byteswritten <= 0)                     /* real error on tofd */            break;         totalbytes += byteswritten;         bytesread -= byteswritten;         bp += byteswritten;      }      if (byteswritten == -1)                       /* real error on tofd */          break;   }   return totalbytes;}

客户端:
#include <errno.h>#include <fcntl.h>#include <limits.h>#include <stdio.h>#include <stdlib.h>#include <strings.h>#include <time.h>#include <unistd.h>#include <sys/stat.h>#define FIFOARG 1int main(int argc, char *argv[]){time_t curtime;int len;char requestbuf[PIPE_BUF];int requestfd;if(argc != 2){fprintf(stderr,"Usage %s fifoname", argv[0]);return 1;}if((requestfd = open(argv[FIFOARG],O_WRONLY)) == -1){perror("Client failed to open log fifo for writing");return 1;}curtime = time(NULL)snprintf(requestbuf,PIPE_BUF,"%d:%s",(int)getpid(),ctime(&curtime));len = strlen(requestbuf);if(write(requestfd,requestbuf,len) != len){perror("Client failed to write");return 1;}close(requestfd);return 0;}

参考:
1、《Unix system programming》
2、《Advanced Programming in the Unix Environment》

热点排行