1 TCP测试客户程序
下面给出的客户程序用于测试我们的服务器程序的各个变体。
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 1024
#define MAXN 16384 // max # bytes to request from server
int tcp_connect( const char *host, const char *serv );
int main( int argc, char **argv )
{
int i, j, fd, nchildren, nloops, nbytes;
pid_t pid;
ssize_t n;
char request[ MAXLINE ], reply[ MAXN ];
// 每次运行本客户程序时,我们指定服务器的主机名或IP地址、服务器的端口、由客户fork的子进程数(以允许并发地
// 向同一个服务器发起多个连接)、每个子进程发送给服务器的请求数,以及每个请求服务器返回的数据字节数
if( argc != 6 )
{
printf( " usage: client <hostname or IPaddr> <port> <#children> <#loops/child> <#byte/request> " );
exit( 1 );
}
nchildren = atoi( argv[ 3 ] );
nloops = atoi( argc[ 4 ] );
nbytes = atoi( argv[ 5 ] );
snprintf( request, sizeof( request ), " %d\n ", nbytes ); // nweline at end
// 父进程调用fork派生指定个数的子进程,每个子进程再与服务器建立指定数目的连接。每个建立连接之后,子进程
// 就在该连接上向服务器发送一行文本,指出需由服务器返回多少字节的数据,然后在该连接上读入这个数量的数据,
// 最后关闭该连接。父进程只是调用wait等待所有子进程都终止。需注意的是,这里关闭每个TCP连接的是客户端,
// 因而TCP的TIME_WAIT状态发生在客户端而不是服务端。这是与通常HTTP连接的差别之一。
for( i = 0; i < nchildren; i++ )
{
if( ( pid = fork() ) == 0 ) // child
{
for( j = 0; j < nloops; j++ )
{
fd = tcp_connect( argv[ 1 ], argv[ 2 ] );
write( fd, request, strlen( request ) );
if( ( n = read( fd, reply, nbytes ) ) != nbytes )
{
printf( " server returned %d bytes ", n );
exit( 1 );
}
close( fd ); // TIME_WAIT on client, not server
}
printf( " child %d done\n ", i );
exit( 0 );
}
// parent loops around to fork() again
}
while( wait( NULL ) > 0 ) // now parent waits for all children
;
if( errno != ECHILD )
{
printf( " wait error " );
exit( 1 );
}
exit( 0 );
}
2 TCP并发服务器程序,每个客户一个子进程
下面我们给出并发服务器程序的main函数。
int main( int argc, int **argv )
{
int listenfd, connfd;
pid_t childpid;
void sig_chld( int ), sig_int( int ), web_child( int );
socklen_t clilen, addrlen;
struct sockaddr *cliaddr;
if( argc == 2 )
listenfd = tcp_listen( NULL, argv[ 1 ], &addrlen );
else if( argc == 3 )
listenfd = tcp_listen( argv[ 1 ], argv[ 2 ], &addrlen );
else
{
printf( " usage: serv01 [ <host> ] <port#> " );
exit( 1 );
}
cliaddr = malloc( addrlen );
signal( SIGCHLD, sig_chld );
signal( SIGINT, sig_int );
for( ; ; )
{
clilen = addrlen;
if( ( connfd = accept( listenfd, cliaddr, &clilen ) ) < 0 )
{
if( errno == EINTR )
continue; // back to for()
else
{
printf( " accpet error " );
exit( 1 );
}
}
if( ( childpid = fork() ) == 0 ) // child process
{
close( listenfd ); //close listening socket
web_child( connfd ); // process request
exit( 1 );
}
close( connfd ); // parent closes connected socket
}
}
下面给出SIGINT信号处理函数。
viod sig_int( int signo )
{
void pr_cpu_time( void );
pr_cpu_time();
exit( 0 );
}
下面给出由SIGINT信号处理函数调用的pr_cpu_time函数。
#include <sys/resource.h>
#ifndef HAVE_GETRUSAGE_PROTO
int getusage( int, struct rusage * );
#endif
void pr_cpu_time( void )
{
double user, sys;
struct rusage myusage, childusage;
if( getrusage( RUSAGE_SELF, &myusage ) < 0 )
{
printf( " getrusage error " );
exit( 1 );
}
if( getrusage( RUSAGE_CHILDREN, &childusage ) < 0 )
{
printf( " getrusage error " );
exit( 1 );
}
user = ( double ) myusage.ru_utime.tv_sec + myusage.ru_utime.tv_usec / 1000000.0;
user += ( double ) childusage.ru_utime.tv_sec + childusage.ru_utime.tv_usec / 1000000.0;
sys = ( double ) myusage.ru_stime.tv_sec + myusage.ru_stime.tv_usec / 1000000.0;
sys += ( double ) childusage.ru_stime.tv_sec + childusage.ru_stime.tv_usec / 1000000.0;
printf( " \nuser time = %g, sys time = %g\n ", user, sys );
}
下面是web_child函数。
void web_child( int sockfd )
{
int ntowrite;
ssize_t nread;
char line[ MAXLINE ], result[ MAXN ];
for( ; ; )
{
if( ( nread = readline( sockfd, line, MAXLINE ) ) == 0 )
return; // connection closed by other end
// line from client specifies #bytes to write back
ntowrite = atol( line );
if( ( ntowrite <= 0 ) || ( ntowrite > MAXN ) )
{
printf( " client request for %d bytes ", ntowrite );
exit( 1 );
}
write( sockfd, result, ntowrite );
}
}
3 TCP预先派生子进程服务器程序,accept无上锁保护
下面给出预先派生子进程服务器第一个版本的main函数。
static int nchildren;
static pid_t *pids;
int main( int argc, char **argv )
{
int listenfd, i;
socklen_t addrlen;
void sig_int( int );
pid_t child_make( int, int, int );
// 增设一个命令行参数供用户指定预先派生的子进程数。分配一个存放各个子进程ID的数组,用于在父进程即将终止时
// 由main函数终止所有进程
if( argc == 3 )
listenfd = tcp_listen( NULL, argv[ 1 ], &addrlen );
else if( argc == 4 )
listenfd = tcp_listen( argv[ 1 ], argv[ 2 ], &addrlen );
else
{
printf( " usage: serv02 [ <host> ] <port#> <#children> \n" );
exit( 1 );
}
nchildren = atoi( argv[ atgc - 1 ] );
pids = calloc( nchildren, sizeof( pid_t ) );
// 调用child_make函数创建各个子进程
for( i = 0; i < nchildren; i++ )
pids[ i ] = child_make( i, listenfd, addrlen ); // parent returns
signal( SIGINT, sig_int );
for( ; ; )
pause(); // everything done by children
}
下面是SIGINT信号处理函数。
void sig_int( int signo )
{
int i;
void pr_cpu_time( void );
// 既然getrusage汇报的是已终止子进程的资源利用统计,在调用pr_cpu_time之前就必须终止所有子进程。我们
// 通过给每个子进程发送SIGTERM信号终止它们,并通过调用wait汇集所有子进程的资源利用统计。
// terminate all children
for( i = 0; i < nchildren; i++ )
kill( pids[ i ], SIGTERM );
while( wait( NULL ) > 0 ) // wait for all children
;
if( errno != ECHILD )
{
printf( " wait error " );
exit( 1 );
}
pr_cpu_time();
exit( 0 );
}
下面是child_make函数,它由main函数调用以派生各个子进程。
pid_t child_make( int i, int listenfd, int addrlen )
{
pid_t pid;
void child_main( int, int, int );
// 调用fork派生子进程后只有父进程返回。子进程调用child_main函数,它是一个无限循环
if( ( pid = fork() ) > 0 )
return( pid ); // parent
child_main( i, listenfd, addrlen ); // never returns
}
void child_main( int i, int listenfd, int addrlen )
{
int connfd;
void web_child( int );
socklen_t clilen;
struct sockaddr *cliaddr;
cliaddr = malloc( addrlen );
printf( " child %ld starting\n ", ( long ) getpid() );
// 每个子进程调用accept返回一个已连接套接字,然后调用web_child处理客户请求,最后关闭连接。子进程一直在
// 这个循环中反复,知道被父进程终止
for( ; ; )
{
clilen = addrlen;
connfd = accept( listends, cliaddr, &clilen );
web_child( connfd ); // process the request
close( connfd );
}
}
4 TCP预先派生子进程服务器程序,传递描述符
对于当前预先派生子进程例子,我们必须为每个子进程维护一个信息结构以便管理。下面给出我们的child结构。
typedef struct
{
pid_t child_pid; // process ID
int child_pipefd; // parent's stream pipe to/from child
int child_status; // 0 = ready
long child_count; // # connections handled
} child;
child *cptr; // array of child structures; calloc'ed
下面是改进过后的child_make函数。
pid_t child_make( int i, int listenfd, int addrlen )
{
int sockfd[ 2 ];
pid_t pid;
void child_main( int, int, int );
// 创建一个字节流管道,它是一对Unix域字节流套接字
socketpair( AD_LOCAL, SOCK_STREAM, 0, sockfd );
if( ( pid = fork() ) > 0 )
{
// 父进程关闭其中写描述符
close( sockfd[ 1 ] );
cptr[ i ].child_pid = pid;
cptr[ i ].child_pipefd = sockfd[ 0 ];
cptr[ i ].child_status = 0;
return( pid ); // parent
}
// 子进程还把流管道的自身拥有端复制到标准错误输出,这样每个子进程就通过读写标准错误输出和父进程通信
dup2( sockfd[ 1 ], STDERR_FILENO ); // child's tream pipe to parent
close( sockfd[ 0 ] );
close( sockfd[ 1 ] );
close( listenfd ); // child does not need this open;
child_main( i, listenfd, addrlen ); // never returns
}
下面是main函数。相比以前的版本的变动在于:分配描述符集,打开与监听套接字以及到各个子进程的字节流管道对应的位;计算最大描述符值;分配child结构数组的内存空间;主循环由一个select调用驱动。
static int nchildren;
int main( int argc, char **argv )
{
int listenfd, i, navail, maxfd, nsel, connfd, rc;
void sig_int( int );
pid_t child_make( int, int, int );
ssize_t n;
fd_set rset, masterset;
socklen_t addrlen, clilen;
struct sockaddr *cliaddr;
if( argc == 3 )
listenfd = tcp_listen( NULL, argv[ 1 ], &addrlen );
else if( argc == 4 )
listenfd = tcp_listen( argv[ 1 ], argv[ 2 ], &addrlen );
else
{
printf( " usage: serv05 [ <host> ] <port#> <#children> " );
exit( 1 );
}
FD_ZERO( &masterset );
FD_SET( listenfd, &masterset );
maxfd = listenfd;
cliaddr = malloc( addrlen );
nchildren = atoi( argv[ argc - 1 ] );
navail = nchildren;
cptr = calloc( nchildren, sizeof( child ) );
// prefork all the children
for( i = 0; i < nchildren; i++ )
{
child_make( i, listenfd, addrlen ); // parent returns
FD_SET( cptr[ i ].child_pipefd, &masterset );
maxfd = max( maxfd, cptr[ i ].child_pipefd );
}
signal( SIGINT, sig_int );
for( ; ; )
{
rset = masterset;
// 计数器navail用于跟踪当前可用的子进程数。如果其值为0,那就从select的读描述符集中关掉与监听套接字对应
// 的位。这么做防止父进程在无可用子进程的情况下accept新连接。内核仍然将这些外来连接排入队列,直到达到
// listen的backlog数为止,不过我我们在没有得到已准备好处理客户的子进程之前不想accept它们。
if( navail <= 0 )
FD_CLR( listenfd, &rset ); // turn off if no available children
nsel = select( maxfd + 1, &rset, NULL, NULL, NULL );
// check for new connections
if( FD_ISSET( listenfd, &rset ) )
{
clilen = addrlen;
connfd = accept( listenfd, cliaddr, &clilen );
for( i = 0; i < nchildren; i++ )
if( cptr[ i ].child_status == 0 )
break; // available
if( i == nchildren )
{
printf( " no available children " );
exit( 1 );
}
cptr[ i ].child_status = 1; // mark child as busy
cptr[ i ].child_count++;
navail--;
n = write_fd( cptr[ i ].child_pipefd, "", 1, connfd );
close( connfd );
if( --nsel == 0 )
continue; // all done with select() results
}
// find any newly-available children
for( i = 0; i < nchildren; i++ )
{
if( FD_ISSET( cptr[ i ].child_pipefd, &rset ) )
{
if( ( n = read( cptr[ i ].child_pipefd, &rc, 1 ) ) == 0 )
{
printf( " child %d terminated unexpectedly ", i );
exit( 1 );
}
cptr[ i ].child_status = 0;
navail++;
if( --nsel == 0 )
break; // all done with select() results
}
}
}
}