`

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 */
		else
			argv0 = 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 1

int 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》
1
3
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics