服务器模型-之select多进程模型
 
比较好的一个将select与多进程结合的模型

可以利用select来监听多个类型的描符号,包括不同类型的socket描述符,如:TCP套接口描述符,UDP套接口描述符等,或文件描述符号。这 样,不管哪个描述符状态改变了,我们都能及时的知道,并处理。包括服务器端进程崩溃,或文件读产生了错误等等情况。  


服务器端编程时可以用select监视多个套接字的状况,可以有两种方式使用select:


1,单进程模型


2,子进程生成模型



单进程模型在UNP有源码实例,steven写道,该例子可能会出现一些问题,比如拒绝服务攻击等。最关键的地方,是因为它是单进程的,所以当它在服务器 一个客户端的时候,就不能为其他客户服务,其他客户端就只有等待,等到服务器把目前的客户的要求完成后才能为其他客户端服务。当然用select时,服务 器端知道客户端请求到来,但是它无能为力,因为它只有一个进程。


子进程生成模型,是为每个客户端生成一个子进程,来为之服务。然后利用select可以监听不同的套接字上的请求,这样可以避免单进程的问题。当然这种模型也不是最完善的,不过比起单进程来说,要好一些。


下面的代码是一个例子,我建立了两个tcp套接字,用select来处理发生在这两个套接字上的事件。这样我们就实现了,同时监听多个套接口的功能,用select实在很方便。


服务器代码:


/*
* filename : tcpsrvselectfork.c
* function : 用select实现了监视多个套接口的功能。
*/

#include "echosvr.h"
#include "util.h"

static void sig_chld2(int signo);
static int listen_local(int port);
static void str_echo(int sockfd);

int
main(void)
{
int lsfd1, lsfd2;
struct sockaddr_in cliaddr;
fd_set rset;
int maxfd1;
pid_t childpid;
socklen_t clilen;
int confd1, confd2;

signal(SIGCHLD, sig_chld2);

/* 建立2个监听socket */
lsfd1 = listen_local(SVR_PORT);    
lsfd2 = listen_local(SVR_PORT2);
clilen = sizeof(cliaddr);

FD_ZERO(&rset);    
for ( ; ; ) {
/*
* 这里要注意:select会在每次返回的时候把任何
* 与没有准备好的描述字相对应的位清成0。
* 所以,每次select返回后,位都要重新设置。
*/

FD_SET(lsfd1, &rset);     
FD_SET(lsfd2, &rset);    
maxfd1 = max(lsfd1, lsfd2) + 1;    
if (select(maxfd1, &rset, NULL, NULL, NULL) < 0) {
if (errno == EINTR)
continue;
else
exit(1);
}

/* 有连接请求到来 */
if (FD_ISSET(lsfd1, &rset)) {
if ((confd1 = accept(lsfd1,
(struct sockaddr *)&cliaddr, &clilen)) < 0) {
if (errno == EINTR)
continue;    
else
exit(1);        
}
/* 生成新的子进程 */
if ((childpid = fork()) == 0) {        /* child */
close(lsfd1);        /* 关闭多余的socket */
str_echo(confd1);
close(confd1);
exit(0);
} else if(childpid < 0) {
perror("fork()");
exit(1);
}
close(confd1); /* 父进程只保留监听socket */
}

if (FD_ISSET(lsfd2, &rset))    {
if ((confd2 = accept(lsfd2,
(struct sockaddr *)&cliaddr, &clilen)) < 0) {
if (errno == EINTR)
continue;
else
exit(1);
}
if ((childpid = fork()) == 0) { /* child */
close(lsfd2);
str_echo(confd2);
close(confd2);
exit(0);
} else if(childpid < 0) {
perror("fork()");
exit(1);
}
close(confd2);
}
}

return OK;
}


/* do all things for server */
static void str_echo(int sockfd)
{
size_t n;
char line[MAXLINE];    
for( ; ; ){
if((n = readline(sockfd, line, MAXLINE)) == 0)
return ;
writen(sockfd, line, n);
}
}


/*
* work correctly
* wait for every child process terminated.
* and free the zombie source.
*/

static void sig_chld2(int signo)
{
pid_t pid;

while((pid = waitpid(-1, NULL, WNOHANG)) > 0)
printf("child %d terminated\n", pid);    
return ;
}


/*
* 创建一个在本地监听任何接口的socket的套接口
*/

static int listen_local(int port)
{
struct sockaddr_in svraddr;
int ls, on = 1;

if (port <= 0)
return FAIL;

if((ls= socket(AF_INET, SOCK_STREAM, 0)) < 0){
perror("socket()");
exit(1);
}    
if(setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on))<0) {
perror("setsockopt (SO_REUSEADDR)");
/* Ignore the error if any */
}
bzero(&svraddr, sizeof(svraddr));
svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
svraddr.sin_port = htons(port);
if(bind(ls, (struct sockaddr *)&svraddr, sizeof(svraddr)) < 0){
perror("bind()");
exit(1);
}
if(listen(ls, LISTENQ) < 0){
perror("listen()");
exit(1);
}
return ls;    
}