Chinaunix首页 | 论坛 | 博客
  • 博客访问: 826115
  • 博文数量: 91
  • 博客积分: 2544
  • 博客等级: 少校
  • 技术积分: 1885
  • 用 户 组: 普通用户
  • 注册时间: 2006-12-12 09:08
文章存档

2016年(10)

2014年(2)

2013年(4)

2012年(23)

2011年(23)

2010年(13)

2009年(14)

2007年(2)

分类: LINUX

2009-07-13 15:07:48

服务器模型-之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;    
}


下面是客户端的代码:

/*
 * echoclt_v3.c
 */


#include "echoclt.h"
#include "util.h"

void str_clt(FILE *fp, int sockfd);

int
main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in svraddr;
    int i;
    
    if(argc != 2){
        fprintf(stderr, "usage: %s \n", argv[0]);
        exit(1);
    }
    
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        perror("socket()");
        exit(1);
    }
    bzero(&svraddr, sizeof(svraddr));
    svraddr.sin_family = AF_INET;
    svraddr.sin_port = htons(SVR_PORT);
    inet_pton(AF_INET, argv[1], &svraddr.sin_addr);
    if(connect(sockfd,
                (struct sockaddr *)&svraddr,
                sizeof(svraddr)) < 0){
        perror("connect()");
        exit(1);
    }
    
    str_clt(stdin, sockfd);
    exit(0);
}


void
str_clt_old(FILE *fp, int sockfd)
{
    char sendline[MAXLINE], rcvline[MAXLINE];

    while(fgets(sendline, MAXLINE, fp) != NULL){
        writen(sockfd, sendline, strlen(sendline));
        if(readline(sockfd, rcvline, MAXLINE) == 0){
            perror("readline()");
            exit(1);
        }
        fputs(rcvline, stdout);
    }
}

void
str_clt(FILE *fp, int sockfd)
{
    int maxfd;
    fd_set rset;
    char sendline[MAXLINE], rcvline[MAXLINE];
    
    FD_ZERO(&rset);
    for( ; ; ){
        FD_SET(fileno(fp), &rset); /* set two fd */
        FD_SET(sockfd, &rset);
        maxfd = max(fileno(fp), sockfd) + 1;
        if(select(maxfd, &rset, NULL, NULL, NULL) < 0){
            fprintf(stderr, "select(): %s\n", strerror(errno));
            return;
        }
        if(FD_ISSET(sockfd, &rset)){ /* socket is readable */
            if(readline(sockfd, rcvline, MAXLINE) == 0) {
                fprintf(stderr, " server terminated prematurely\n");
                return ;
            }
            fputs(rcvline, stdout);
        }
        if(FD_ISSET(fileno(fp), &rset)){ /* input is readable */
            if(fgets(sendline, MAXLINE, fp) == NULL)
                return;
            writen(sockfd, sendline, strlen(sendline));
        }
    }
}

阅读(3441) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~