1.编写bc(pipe、fork、dup、exec)
bc/dc程序对是客户/服务器模型程序设计的一个实例,bc/dc对被称之为协同进程。
(1)创建两个管道
(2)创建一个进程来运行dc
(3)在新创建的进程中,重定向标准输入和标准输出到管道,然后运行exec dc
(4)在父进程中,读取并分析用户的输入,将命令传给dc,dc读取响应,并把响应传给用户
/** tinybc.c * a tiny calculator that uses dc to do its work
** * demonstrates bidirectional pipes
** * input looks like number op number which
** tinybc converts into number \n number \n op \n p
** and passes result back to stdout
**
** +-----------+ +----------+
** stdin >0 >== pipetodc ====> |
** | tinybc | | dc - |
** stdout <1 <== pipefromdc ==< |
** +-----------+ +----------+
**
** * program outline
** a. get two pipes
** b. fork (get another process)
** c. in the dc-to-be process,
** connect stdin and out to pipes
** then execl dc
** d. in the tinybc-process, no plumbing to do
** just talk to human via normal i/o
** and send stuff via pipe
** e. then close pipe and dc dies
** * note: does not handle multiline answers
**/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#define oops(m,x) { perror(m); exit(x); }
void be_dc(int in[2], int out[2]);
void be_bc(int todc[2], int fromdc[2]);
void main()
{
int pid, todc[2], fromdc[2]; /* equipment */
/* make two pipes */
if ( pipe(todc) == -1 || pipe(fromdc) == -1 )
oops("pipe failed", 1);
/* get a process for user interface */
if ( (pid = fork()) == -1 )
oops("cannot fork", 2);
if ( pid == 0 ) /* child is dc */
be_dc(todc, fromdc);
else {
be_bc(todc, fromdc); /* parent is ui */
wait(NULL); /* wait for child */
}
}
void be_dc(int in[2], int out[2])
/*
* set up stdin and stdout, then execl dc
*/
{
/* setup stdin from pipein */
if ( dup2(in[0],0) == -1 ) /* copy read end to 0 */
oops("dc: cannot redirect stdin",3);
close(in[0]); /* moved to fd 0 */
close(in[1]); /* won't write here */
/* setup stdout to pipeout */
if ( dup2(out[1], 1) == -1 ) /* dupe write end to 1 */
oops("dc: cannot redirect stdout",4);
close(out[1]); /* moved to fd 1 */
close(out[0]); /* won't read from here */
/* now execl dc with the - option */
execlp("dc", "dc", "-", NULL );
oops("Cannot run dc", 5);
}
void be_bc(int todc[2], int fromdc[2])
/*
* read from stdin and convert into to RPN, send down pipe
* then read from other pipe and print to user
* Uses fdopen() to convert a file descriptor to a stream
*/
{
int num1, num2;
char operation[BUFSIZ], message[BUFSIZ];
FILE *fpout, *fpin;
/* setup */
close(todc[0]); /* won't read from pipe to dc */
close(fromdc[1]); /* won't write to pipe from dc */
fpout = fdopen( todc[1], "w" ); /* convert file desc- */
fpin = fdopen( fromdc[0], "r" ); /* riptors to streams */
if ( fpout == NULL || fpin == NULL )
perror("Error convering pipes to streams");
/* main loop */
while ( printf("tinybc: "), fgets(message,BUFSIZ,stdin) != NULL ){
/* parse input */
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 )
perror("Error writing");
fflush( fpout );
if ( fgets( message, BUFSIZ, fpin ) == NULL )
break;
printf("%d %c %d = %s", num1, *operation , num2, message);
}
fclose(fpout); /* close pipe */
fclose(fpin); /* dc will see EOF */
}
在程序中使用了fdopen,fdopen和fopen类似返回一个FILE *类型的值,不同的是此函数以文件描述符而非文件作为参数。
2.popen:让进程 看似文件
fopen打开一个指向文件的带缓存的连接:
FILE *fp;
fp = fopen("file1", "r");
c = getc(fp);
fgets(buf, len, fp);
fscanf(fp, "%d %d %s", &x, &y, &z);
fclose(fp);
popen打开一个指向进程的带缓冲的连接:
FILE *fp;
fp = popen("ls", "r");
fgets(buf, len, fp);
pclose(fp);
popen和fopen类似,popen的第一个参数是要打开的命令的名称,第二个参数可以是"r"或者是"w",但绝不会是"a"。popen和fopen以及fdopen的返回值类型都是FILE *,可以使用标准的缓存I/O操作来对其进行操作了。
/* popendemo.c
* demonstrates how to open a program for standard i/o
* important points:
* 1. popen() returns a FILE *, just like fopen()
* 2. the FILE * it returns can be read/written
* with all the standard functions
* 3. you need to use pclose() when done
*/
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *fp;
char buf[100];
int i = 0;
fp = popen( "who|sort", "r" ); /* open the command */
while ( fgets( buf, 100, fp ) != NULL ) /* read from command */
printf("%3d %s", i++, buf ); /* print data */
pclose( fp ); /* IMPORTANT! */
return 0;
}
实现popen:使用fdopen命令
/* popen.c - a version of the Unix popen() library function
* FILE *popen( char *command, char *mode )
* command is a regular shell command
* mode is "r" or "w"
* returns a stream attached to the command, or NULL
* execls "sh" "-c" command
* todo: what about signal handling for child process?
*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#define READ 0
#define WRITE 1
FILE *popen(const char *command, const char *mode);
void main()
{
FILE *fp;
char buf[BUFSIZ];
fp = popen("ls","r");
while( fgets(buf, BUFSIZ,fp) != NULL)
fputs(buf, stdout);
}
FILE *popen(const char *command, const char *mode)
{
int pfp[2], pid; /* the pipe and the process */
FILE *fdopen(), *fp; /* fdopen makes a fd a stream */
int parent_end, child_end; /* of pipe */
if ( *mode == 'r' ){ /* figure out direction */
parent_end = READ;
child_end = WRITE ;
} else if ( *mode == 'w' ){
parent_end = WRITE;
child_end = READ ;
} else return NULL ;
if ( pipe(pfp) == -1 ) /* get a pipe */
return NULL;
if ( (pid = fork()) == -1 ){ /* and a process */
close(pfp[0]); /* or dispose of pipe */
close(pfp[1]);
return NULL;
}
/* --------------- parent code here ------------------- */
/* need to close one end and fdopen other end */
if ( pid > 0 ){
if (close( pfp[child_end] ) == -1 )
return NULL;
return fdopen( pfp[parent_end] , mode); /* same mode */
}
/* --------------- child code here --------------------- */
/* need to redirect stdin or stdout then exec the cmd */
if ( close(pfp[parent_end]) == -1 ) /* close the other end */
exit(1); /* do NOT return */
if ( dup2(pfp[child_end], child_end) == -1 )
exit(1);
if ( close(pfp[child_end]) == -1 ) /* done with this one */
exit(1);
/* all set to run cmd */
execl( "/bin/sh", "sh", "-c", command, NULL );
exit(1);
}
3.socket:与远端进程相连
socket允许不同进程,甚至不同主机进行通信。
socket通信涉及4个方面:
(1)客户和服务器
(2)主机名和端口
(3)地址族
(4)协议
在文件/etc/services中定义了众所周知的服务端口列表
(1)编写timeserv.c: 时间服务器
/* timeserv.c - a socket-based time of day server
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<time.h>
#include<strings.h>
#include<stdlib.h>
#define PORTNUM 13000 /* our time service phone number */
#define HOSTLEN 256
#define oops(msg) { perror(msg) ; exit(1) ; }
void main(int ac, char *av[])
{
struct sockaddr_in saddr; /* build our address here */
struct hostent *hp; /* this is part of our */
char hostname[HOSTLEN]; /* address */
int sock_id,sock_fd; /* line id, file desc */
FILE *sock_fp; /* use socket as stream */
char *ctime(); /* convert secs to string */
time_t thetime; /* the time we report */
/*
* Step 1: ask kernel for a socket
*/
sock_id = socket( PF_INET, SOCK_STREAM, 0 ); /* get a socket */
if ( sock_id == -1 )
oops( "socket" );
/*
* Step 2: bind address to socket. Address is host,port
*/
bzero( (void *)&saddr, sizeof(saddr) ); /* clear out struct */
gethostname( hostname, HOSTLEN ); /* where am I ? */
printf("hostname: %s\n", hostname);
hp = gethostbyname( hostname ); /* get info about host */
/* fill in host part */
bcopy( (void *)hp->h_addr, (void *)&saddr.sin_addr, hp->h_length);
saddr.sin_port = htons(PORTNUM); /* fill in socket port */
saddr.sin_family = AF_INET ; /* fill in addr family */
if ( bind(sock_id, (struct sockaddr *)&saddr, sizeof(saddr)) != 0 )
oops( "bind" );
/*
* Step 3: allow incoming calls with Qsize=1 on socket
*/
if ( listen(sock_id, 1) != 0 )
oops( "listen" );
/*
* main loop: accept(), write(), close()
*/
while ( 1 ){
sock_fd = accept(sock_id, NULL, NULL); /* wait for call */
printf("Wow! got a call!\n");
if ( sock_fd == -1 )
oops( "accept" ); /* error getting calls */
sock_fp = fdopen(sock_fd,"w"); /* we'll write to the */
if ( sock_fp == NULL ) /* socket as a stream */
oops( "fdopen" ); /* unless we can't */
thetime = time(NULL); /* get time */
/* and convert to strng */
fprintf( sock_fp, "The time here is .." );
fprintf( sock_fp, "%s", ctime(&thetime) );
fclose( sock_fp ); /* release connection */
}
}
程序解读:
struct sockaddr {
unsigned short sa_family;
char sa_data[14];
};
//sa_family是通信类型,最常用的值是 "AF_INET"
//sa_data14字节,包含套接字中的目标地址和端口信息
struct sockaddr 是一个通用地址结构,这是为了统一地址结构的表示方法,统一接口函数,使不同的地址结构可以被bind() , connect() 等函数调用;sa_data把目标地址和端口信息混在一起了
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
struct in_addr {
unsigned long s_addr;
}
unsigned char sin_zero[8];
}
struct sockaddr_in中的in 表示internet,就是网络地址,这只是我们比较常用的地址结构,属于AF_INET地址族,他非常的常用
sockaddr_in结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中。
sin_port和sin_addr都必须是网络字节序,一般可视化的数字都是主机字节序。
网络字节顺序与本地字节顺序之间的转换函数:
htonl()--"Host to Network Long"
ntohl()--"Network to Host Long"
htons()--"Host to Network Short"
ntohs()--"Network to Host Short"
int gethostname(char *name, size_t len); //返回本地主机的标准主机名
struct hostent *gethostbyname(const char *name); //用域名或主机名获取IP地址
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
}
复制函数
void bcopy(const void *src, void *dest, size_t n);
步骤1:向内核申请一个socket
socket常用的协议族(domain)有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk ddp(7)
AF_PACKET Low level packet interface packet(7)
AF_ALG Interface to kernel crypto API
参数type用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK_DGRAM(数据包套接字)等
第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
步骤2:绑定地址到socket上,地址包括主机、端口
步骤3:在socket上,允许接入呼叫并设置队列长度为1
步骤4:等待/接收呼叫
步骤5:传输数据
步骤6:关闭连接
开启两个终端,程序运行的结果如下
(2)编写timecln.c:时间服务器客户端
/* timeclnt.c - a client for timeserv.c
* usage: timeclnt hostname portnumber
*/
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<stdlib.h>
#include<string.h>
#define oops(msg) { perror(msg); exit(1); }
main(int ac, char *av[])
{
struct sockaddr_in servadd; /* the number to call */
struct hostent *hp; /* used to get number */
int sock_id, sock_fd; /* the socket and fd */
char message[BUFSIZ]; /* to receive message */
int messlen; /* for message length */
/*
* Step 1: Get a socket
*/
sock_id = socket( AF_INET, SOCK_STREAM, 0 ); /* get a line */
if ( sock_id == -1 )
oops( "socket" ); /* or fail */
/*
* Step 2: connect to server
* need to build address (host,port) of server first
*/
bzero( &servadd, sizeof( servadd ) ); /* zero the address */
hp = gethostbyname( av[1] ); /* lookup host's ip # */
if (hp == NULL)
oops(av[1]); /* or die */
bcopy(hp->h_addr, (struct sockaddr *)&servadd.sin_addr, hp->h_length);
servadd.sin_port = htons(atoi(av[2])); /* fill in port number */
servadd.sin_family = AF_INET ; /* fill in socket type */
/* now dial */
if ( connect(sock_id,(struct sockaddr *)&servadd, sizeof(servadd)) !=0)
oops( "connect" );
/*
* Step 3: transfer data from server, then hangup
*/
messlen = read(sock_id, message, BUFSIZ); /* read stuff */
if ( messlen == - 1 )
oops("read") ;
if ( write( 1, message, messlen ) != messlen ) /* and write to */
oops( "write" ); /* stdout */
close( sock_id );
}
步骤1:向内核请求建立socket
步骤2:与服务器相连
步骤3和4:传送数据和挂断
运行结果
(3)另一种服务器:远程的ls
原理
实现rls系统的三要素:协议,客户端程序,服务器程序
协议
协议包含请求和应答。首先客户端发送一行包含有目录名称的请求。服务器读取该目录名之后打开并读取该目录,然后把文件发送到客户端。客户端循环地读取文件列表,直到服务器挂断连接产生结尾标志。
客户端程序:rls.c
/* rls.c - a client for a remote directory listing service
* usage: rls hostname directory
*/
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<stdlib.h>
#include<string.h>
#define oops(msg) { perror(msg); exit(1); }
#define PORTNUM 15000
void main(int ac, char *av[])
{
struct sockaddr_in servadd; /* the number to call */
struct hostent *hp; /* used to get number */
int sock_id, sock_fd; /* the socket and fd */
char buffer[BUFSIZ]; /* to receive message */
int n_read; /* for message length */
if ( ac != 3 ) exit(1);
/** Step 1: Get a socket **/
sock_id = socket( AF_INET, SOCK_STREAM, 0 ); /* get a line */
if ( sock_id == -1 )
oops( "socket" ); /* or fail */
/** Step 2: connect to server **/
bzero( &servadd, sizeof(servadd) ); /* zero the address */
hp = gethostbyname( av[1] ); /* lookup host's ip # */
if (hp == NULL)
oops(av[1]); /* or die */
bcopy(hp->h_addr, (struct sockaddr *)&servadd.sin_addr, hp->h_length);
servadd.sin_port = htons(PORTNUM); /* fill in port number */
servadd.sin_family = AF_INET ; /* fill in socket type */
if ( connect(sock_id,(struct sockaddr *)&servadd, sizeof(servadd)) !=0)
oops( "connect" );
/** Step 3: send directory name, then read back results **/
if ( write(sock_id, av[2], strlen(av[2])) == -1)
oops("write");
if ( write(sock_id, "\n", 1) == -1 )
oops("write");
while( (n_read = read(sock_id, buffer, BUFSIZ)) > 0 )
if ( write(1, buffer, n_read) == -1 )
oops("write");
close( sock_id );
}
服务器程序:rlsd.c
/* rlsd.c - a remote ls server - with paranoia
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<time.h>
#include<strings.h>
#include<stdlib.h>
#define PORTNUM 15000 /* our remote ls server port */
#define HOSTLEN 256
#define oops(msg) { perror(msg) ; exit(1) ; }
void sanitize(char *str);
int main(int ac, char *av[])
{
struct sockaddr_in saddr; /* build our address here */
struct hostent *hp; /* this is part of our */
char hostname[HOSTLEN]; /* address */
int sock_id,sock_fd; /* line id, file desc */
FILE *sock_fpi, *sock_fpo; /* streams for in and out */
FILE *pipe_fp; /* use popen to run ls */
char dirname[BUFSIZ]; /* from client */
char command[BUFSIZ]; /* for popen() */
int dirlen, c;
/** Step 1: ask kernel for a socket **/
sock_id = socket( PF_INET, SOCK_STREAM, 0 ); /* get a socket */
if ( sock_id == -1 )
oops( "socket" );
/** Step 2: bind address to socket. Address is host,port **/
bzero( (void *)&saddr, sizeof(saddr) ); /* clear out struct */
gethostname( hostname, HOSTLEN ); /* where am I ? */
hp = gethostbyname( hostname ); /* get info about host */
bcopy( (void *)hp->h_addr, (void *)&saddr.sin_addr, hp->h_length);
saddr.sin_port = htons(PORTNUM); /* fill in socket port */
saddr.sin_family = AF_INET ; /* fill in addr family */
if ( bind(sock_id, (struct sockaddr *)&saddr, sizeof(saddr)) != 0 )
oops( "bind" );
/** Step 3: allow incoming calls with Qsize=1 on socket **/
if ( listen(sock_id, 1) != 0 )
oops( "listen" );
/*
* main loop: accept(), write(), close()
*/
while ( 1 ){
sock_fd = accept(sock_id, NULL, NULL); /* wait for call */
if ( sock_fd == -1 )
oops("accept");
printf("get a call\n");
/* open reading direction as buffered stream */
if( (sock_fpi = fdopen(sock_fd,"r")) == NULL )
oops("fdopen reading");
if ( fgets(dirname, BUFSIZ-5, sock_fpi) == NULL )
oops("reading dirname");
printf("dirname: %s\n", dirname);
sanitize(dirname);
/* open writing direction as buffered stream */
if ( (sock_fpo = fdopen(sock_fd,"w")) == NULL )
oops("fdopen writing");
sprintf(command,"ls %s", dirname);
if ( (pipe_fp = popen(command, "r")) == NULL )
oops("popen");
/* transfer data from ls to socket */
while( (c = getc(pipe_fp)) != EOF )
putc(c, sock_fpo);
pclose(pipe_fp);
fclose(sock_fpo);
fclose(sock_fpi);
}
}
void sanitize(char *str)
/*
* it would be very bad if someone passed us an dirname like
* "; rm *" and we naively created a command "ls ; rm *"
*
* so..we remove everything but slashes and alphanumerics
* There are nicer solutions, see exercises
*/
{
char *src, *dest;
for ( src = dest = str ; *src ; src++ )
if ( *src == '/' || isalnum(*src) ) //isalnum判断字符变量c是否为字母或数字
*dest++ = *src;
*dest = '\0';
}
程序运行结果
4.精灵程序
unix中很多服务器程序以d结尾,这里d表示精灵的意思,在你的系统中输入命令ps -el或者ps -ax就可以看到以字符d结尾的进程。大部分精灵进程都是在系统启动后就处于运行状态了。位于类似于/etc/rc.d目录中的shell脚本在后台启动了这些服务,它们的运行与终端相分离,时刻准备提供数据或者服务。