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》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值