Linux高性能服务器之多路转接(1)----select模型实现

来源:互联网 发布:淘宝手机卖场 编辑:程序博客网 时间:2024/06/05 02:50

本文将讨论如下几个问题:

什么是多路转接

如何用select模型搭建高性能服务器(select基本使用)

select和多线程各有什么优缺点


一、什么是多路转接

在Linux中,有五种I/O模型,阻塞式、非阻塞式、多路转接、信号驱动、异步,其中前四种又叫同步I/O。

要理解select,必须先理解I/O; 我们在网络上读写数据是通过Socket完成的,但是读的时候如何确定对方写了数据或对方有没有写完数据,而写的时候怎么知道有空间可以写,所以就需要I/O机制来控制。(系统内部同理)

 由此可知I/O分为两步:等和数据迁移 

这五种I/O模型决定了I/O中等待部分的等待方式,本文讨论的多路转接相对其他效率高一些。

用钓鱼来比喻这五种模型,分别如下:

阻塞式I/O : 放一个鱼竿,盯着鱼钩什么都不干;

非阻塞式 I/O :放一个鱼竿,隔一段时间检查一次有没有鱼上钩;

信号驱动I/O  : 鱼竿上系着一个铃铛,钓鱼的人在旁边看书,一旦铃铛响,直接把鱼竿拿上来(不检查是否有鱼);

多路转接I/O : 放10个鱼竿,哪个鱼竿有鱼上钩就拿起鱼竿;

异步I/O : 找一个人帮他钓鱼, 本身只负责布置任务和回收资源(鱼)

可以看出来,多路转接的等待方式充分利用了资源,把I/O中等的时间最小化。


二、select搭建的服务器


先看一下select函数的各个参数



各参数作用:

nfds --- 关心的文件描述符数量

readfds---select关心该集合中的文件描述符是否可读

writefds---关心该集合中文件描述符是否可写

exceptfds --- 关心是否出错

这三个文件描述符集返回的时候都表示哪个文件描述符发生了变化

timeout--- 当设置为NULL时,阻塞等待,设置成0时,非阻塞等待,大于0时表示等待的时间

timeout的结构体:

 struct timeval {               long    tv_sec;         /* 秒*/               long    tv_usec;        /* 微秒 */           };

返回值:失败返回-1,成功返回发生变化的文件描述符个数



#include<stdio.h>#include<stdlib.h>#include<string.h>#include<sys/socket.h>#include<sys/types.h>#include<unistd.h>#include<netinet/in.h>#include<sys/select.h>#include<sys/time.h>int array_fds[1024];static void usage(char* proc){    printf("usage:%s [ip][port]",proc);}ssize_t startup(char* ip,int port){    ssize_t sock=socket(AF_INET,SOCK_STREAM,0);    if(sock<0)    {        perror("socket");        exit(2);    }    int flag=1;    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));    struct sockaddr_in server_addr;    server_addr.sin_family=AF_INET;    server_addr.sin_port=htons(port);    server_addr.sin_addr.s_addr=inet_addr(ip);    if(bind(sock,(struct sockaddr*)&server_addr,sizeof(server_addr))<0)    {        perror("bind");        exit(3);    }    if(listen(sock,10)<0)    {        perror("listen");        exit(4);    }    return sock;}int main(int argc,char* argv[]){    if(argc!=3)    {        usage(argv[0]);        exit(1);    }    int listen_sock=startup(argv[1],atoi(argv[2]));    fd_set rfds;//监视读的文件描述符集合    fd_set wfds;//监视写的文件描述符集合    int maxfd=0;    int array_size=sizeof(array_fds)/sizeof(array_fds[0]);    array_fds[0]=listen_sock;    int i=1;    for(;i<array_size;++i)    {        array_fds[i]=-1;    }    while(1)    {        struct timeval _timeout={2,0};        FD_ZERO(&rfds);        maxfd=-1;        for(i=0;i<array_size;++i)        {            if(array_fds[i]>0)            {                FD_SET(array_fds[i],&rfds);                FD_SET(array_fds[i],&wfds);                if(array_fds[i]>maxfd)                {                    maxfd=array_fds[i];                }            }        }        switch(select(maxfd+1,&rfds,&wfds,NULL,NULL))        {            case 0:                printf("timeout...");                break;            case -1:                perror("select");                break;             default:                {                     int j=0;                    for(;j<array_size;++j)                    {                        if(array_fds[j]<0)                        {                            continue;                        }                        if(j==0&&FD_ISSET(array_fds[j],&rfds))                        {                            struct sockaddr_in client_addr;                            socklen_t len=sizeof(client_addr);                            int new_sock=accept(array_fds[j],(struct sockaddr*)&client_addr,&len);                            if(new_sock<0)                            {                                perror("accept");                                exit(5);                            }                             int k=1;                            for(;k<array_size;k++)                            {                                if(array_fds[k]<0)                                {                                    array_fds[k]=new_sock;                                    break;                                }                                else if(k==array_size)                                {                                    close(new_sock);                                }                                else                                {                                    continue;                                }                             }                         }                        if(j!=0&&FD_ISSET(array_fds[j],&rfds))                        {                            char buff[1024];                            memset(buff,0,sizeof(buff)/sizeof(buff[0]));                            int s=read(array_fds[j],buff,sizeof(buff));                            if(s>0)                            {                                buff[s]='\0';                                printf("client say:%s",buff);                                if(j!=0&&FD_ISSET(array_fds[j],&wfds))                                {                                       write(array_fds[j],buff,strlen(buff));                                }                            }                            else if(s==0)                            {                                printf("client quit!\n");                                fflush(stdout);                                close(array_fds[j]);                                array_fds[j]=-1;                            }                            else                            {                                perror("read");                                close(array_fds[j]);                                array_fds[j]=-1;                            }                         }                    }                    break;                    }                }    }    return 0;}

客户端用dup2进行了输出重定向

#include<stdio.h>#include<unistd.h>#include<sys/socket.h>#include<sys/types.h>#include<string.h>#include<netinet/in.h>#include<stdlib.h>#include<fcntl.h>#include<arpa/inet.h>#include<sys/stat.h>static void usage(char* proc){    printf("usage:%s[server ip][server port]",proc);}int main(int argc,char*argv[]){    if(argc!=3)    {        usage(argv[0]);        exit(1);    }    int sock=socket(AF_INET,SOCK_STREAM,0);    if(sock<0)    {        perror("socket");        exit(2);    }    struct sockaddr_in server_addr;    server_addr.sin_family=AF_INET;    server_addr.sin_port=htons(atoi(argv[2]));    server_addr.sin_addr.s_addr=inet_addr(argv[1]);    if(connect(sock,(struct sockaddr*)&server_addr,sizeof(server_addr)))    {        perror("connect");        exit(3);    }    int oldfd=dup(STDOUT_FILENO);    char buff[1024];    while(1)    {        printf("Enter Please:");        fflush(stdout);        dup2(sock,STDOUT_FILENO);        int s=read(0,buff,sizeof(buff)-1);        if(s>0)        {            if(buff[0]=='\n')            {                dup2(oldfd,STDOUT_FILENO);                continue;            }            if(strncmp(buff,"quit",4)==0)            {                break;            }            buff[s]=0;            printf("%s",buff);            fflush(stdout);            dup2(oldfd,STDOUT_FILENO);            int _s=read(sock,buff,sizeof(buff)-1);            if(_s>0)            {                buff[_s]=0;                printf("server echo:#%s",buff);            }            else if(s<=0)            {                continue;            }        }    }    close(sock);    close(oldfd);    return 1;}


三、select和多线程相比

select

优点:

提高效率,减少了等的时间

缺点:

1)每次select之后都要设置几个集合的值,增大了开销

2)每次调用select要遍历返回的集合

3)select可以监视的文件描述符有限


多线程虽然等待时间长,但是每一部分没有这么大的开销  

多线程致命的缺点是当用户量增大时,占用了太多内存,增大了服务器的压力




阅读全文
0 0