1、不同数据源
Unix提供一个接口来处理可能来自不同数据源的数据,总共包括4种类型的数据源,分别是磁盘文件、设备、管道、sockets。磁盘/设备文件用open命令连接,用read和write传递数据;管道用pipe命令创建,用fork分享,用read和write传递数据;sockets用socket、listen和connect连接,用read和write传递数据。
2、bc
(1)bc的思想
- 客户/服务器模型:dc提供服务用来计算,bc提供用户界面,bc被称为dc的客户。dc与bc之间通过标准输入stdin和标准输出stdout进行通信。
- 双向通信:客户/服务器模型要求一个进程既跟另一个进程的标准输入也要和它的标准输出进行通信。Unix的管道只是单向地传送数据,bc和dc之间有两个管道,一个管道把一些计算命令传给dc的标准输入,另一个管道把dc的标准输出传给bc。
- 永久性服务:bc只是让单一的dc进程处于运行状态,bc程序持续不断地和dc的同一个实例进行通信,把用户的输入转换成命令传给dc。
(2)bc的实现——pipe、fork、dup、exec
内核将用户连接到bc并将bc连接到dc,具体实现如下:
- 创建两个管道
- 新创建一个进程运行dc
- 在新创建的进程中,重定向标准输入和标准输出到管道,然后运行exec dc
- 在父进程中,读取并分析用户的输入,将命令传给dc,dc读取响应,并把响应传给用户
3、fdopen
fdopen与fopen类似,返回一个FILE*类型的值,不同的是此函数以文件描述符而非文件作为参数。如果只知道文件描述符而不清楚文件名的时候可以使用fdopen命令。把一个通向管道的连接转换成FILE*类型值之后,就可以使用标准缓存的IO操作来对其进行操作了。使用fdopen使得对远端的进程的处理就如同处理常规文件一样。
4、popen
popen运行一个程序并返回指向该程序标准输入或标准输出的连接。使用fork命令运行一个新的进程,需要一个指向该进程的连接,因此需要使用管道,并且使用fdopen命令将一个文件描述符定向到缓冲流中,最后在该进程中要能够运行任何shell命令所以用到exec。
popen打开一个指向进程的带缓冲的连接,popen和fopen看上去很类似,fopen从文件获得数据,popen从进程获得数据,两者使用相同的语法格式,并具有相同的返回值类型,foepn("file","r") popen("ls","r"),fopen使得进程连接文件,popen使得进程连接进程,fdopen使得进程连接文件描述符对应的通道。
当完成对popen所打开连接的读写后,必须使用pclose关闭连接,进程在产生之后必须等待退出运行,否则它将成为僵尸进程,而pclose中调用wait函数来等待进程的结束。
5、访问数据的三种方式——文件、API、服务器
数据访问的三种实现方法比较:
- 从文件获取数据:基于文件的信息服务并不是很完美。客户端程序依赖于特定的文件格式和结构体中的特定成员名称。
- 从函数获取数据:一个库函数用标准的函数接口来封装数据的格式和位置,如unix提供了读取utmp文件的函数接口,getutent描述了读取utmp数据库函数的细节。使用基于应用程序接口(API)的信息服务也并不一定是最好的方法。有两种方法可以使用库函数。一个程序可以使用静态连接来包含实际的函数代码,但这些函数可能包含的并不是正确的文件名或文件格式。另一方面,一个程序可以调用共享库中的函数,但是这些共享库也不是安装在所有的系统上。
- 从进程获取数据:调用独立的程序获得数据还有其他好处。服务器程序可以使用任何程序设计语言编写都可以,以独立程序的方式实现系统服务的最大好处是客户端程序和服务器端程序可以运行在不用的机器上,所有要做的只是和不同机器上的进程相连接。
6、具体程序实现
tinybc.c
使用两个管道实现简单的bc计算器程序
#include<stdio.h>
#include<unistd.h>
#include <stdlib.h>
#define oops(m,x) {perror(m); exit(x);}
void be_dc(int todc[2],int fromdc[2])
{
if(dup2(todc[0],0)==-1)
oops("dc:cannot redirect stdin",3);
close(todc[0]);
close(todc[1]);
if(dup2(fromdc[1],1)==-1)
oops("dc:cannot redirect stdout",4);
close(fromdc[0]);
close(fromdc[1]);
execlp("dc","dc","-",NULL);
oops("Cannot run dc",5);
}
void be_bc(int todc[2],int fromdc[2])
{
int num1,num2;
char operation[BUFSIZ],message[BUFSIZ];
FILE *fpout,*fpin;
close(todc[0]);
close(fromdc[1]);
fpout=fdopen(todc[1],"w");
fpin=fdopen(fromdc[0],"r");
if(fpout==NULL || fpin==NULL)
fatal("Error convering pipes to streams");
while(printf("tinybc:"),fgets(message,BUFSIZ,stdin)!=NULL)
{
if(sscanf(message,"%d%[-+*/^]%d",&num1,operation,&num2)!=3)
{
printf("syntax error\n");
continue;
}
if(fprintf(fpout,"%d\n%d\n%c\np\n",num1,num2,*operation)==EOF)
fatal("Error writing");
fflush(fpout);
if(fgets(message,BUFSIZ,fpin)==NULL)
break;
printf("%d %c %d = %s",num1,*operation,num2,message);
}
fclose(fpout);
fclose(fpin);
}
fatal(char *mess[])
{
fprintf(stderr,"Error:%s\n",mess);
exit(1);
}
int main()
{
int pid,todc[2],fromdc[2];
if(pipe(todc)==-1||pipe(fromdc)==-1)
oops("pipe failed",1);
if((pid=fork())==-1)
oops("fork error",2);
if(pid==0)
be_dc(todc,fromdc);
else
{
be_bc(todc,fromdc);
wait(NULL);
}
return 0;
}
popen.c
使用fdopen实现popen,其中涉及到的系统调用有pipe、fork、dup2、exec
#include<stdio.h>
#include<unistd.h>
#include <stdlib.h>
#include<signal.h>
#define READ 0
#define WRITE 1
FILE *popen(const char *command,const char *mode)
{
int pfp[2],pid;
FILE *fp;
int parent_end,child_end;
if(*mode=='r')
{
parent_end=READ;
child_end=WRITE;
}
else if(*mode=='w')
{
parent_end=WRITE;
child_end=READ;
}
else
return NULL;
if(pipe(pfp)==-1)
return NULL;
if((pid=fork())==-1)
{
close(pfp[0]);
close(pfp[1]);
return NULL;
}
if(pid>0)
{
if(close(pfp[child_end])==-1)
return NULL;
return fdopen(pfp[parent_end],mode);
}
if(close(pfp[parent_end])==-1)
exit(1);
if(dup2(pfp[child_end],child_end)==-1)
exit(1);
if(close(pfp[child_end])==-1)
exit(1);
execl("/bin/sh","sh","-c",command,NULL);
exit(1);
}
int main()
{
FILE *fp;
char buf[100];
int i=0;
fp=popen("who|sort","r");
while(fgets(buf,100,fp)!=NULL)
printf("%3d %s",i++,buf);
pclose(fp);
return 0;
}