select服務器

1.select簡單介紹
①select:一次用來等待多個文件描述符,只負責等待
②select函數:
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout)
③參數介紹:

  • 第一個參數nfds表示最大的文件描述符+1
  • fd_set是文件描述符集,readfds表示讀事件,writefds表示寫事件,exceptfds表示異常事件;
  • 後四個參數都是輸入輸出型參數:中間三個參數在輸入時,比特位的內容表示關心的文件描述符事件,輸出時,比特位的內容表示哪個文件描述符已經就緒。
  • 例如:在輸入時,readfds的第3個比特位爲1,表示只關心3號文件描述符的讀事件;在輸出時,readfds的第3個比特位爲1,表示3號文件描述符已經就緒;
  • 最後一個參數timeout可以將select設置爲阻塞式等待、非阻塞式等待或按特定時間非阻塞等待;

④只要有任何一個事件就緒,select就會返回;

2.select服務器
①select服務器介紹
select()檢測到來自服務器端的connect()行爲,作爲輸入參數,readfds,writefds,exceptfds應該標記所有需要探測的“可讀事件”,“可寫事件”,“錯誤事件”的句柄,作爲輸出參數,readfds,writefds,exceptfds中保存了select()捕捉到所有事件的句柄值,使用FD_ISSET檢查哪些句柄發生了事件。

②編寫select服務器:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<fcntl.h>

int fds[1024];

static void Usage(const char* proc)
{//服務器的使用介紹
    printf("%s[local_ip][local_post]\n",proc);
}

int startup(const char* _ip,int _port)
{
    //創建套接字
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(2);
    }
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(_port);
    local.sin_addr.s_addr=inet_addr(_ip);
    //綁定套接字
    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        perror("bind");
        exit(3);
    }
    //監聽套接字
    if(listen(sock,10))
    {
        perror("listen");
        exit(4);
    }
    return sock;
}

int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        return 1;
    }
    //創建監聽套接字
    int listen_sock=startup(argv[1],atoi(argv[2]));

    int nums=sizeof(fds)/sizeof(fds[0]);
    int maxfd=-1;
    int i=1;
    for(;i<nums;i++)
    {
        fds[i]=-1;//從1開始清空文件描述符
    }
    //將listen_sock託管給select_server
    fds[0]=listen_sock;

    while(1)
    {
        struct timeval timeout={5,0};
        fd_set rfds;
        //清空rfds的所有位
        FD_ZERO(&rfds);
        maxfd=-1;
        for(i=0;i<nums;i++)
        {
            if(fds[i]>0)
            {
                //標記可讀事件句柄,在rfds中設置文件描述符
                FD_SET(fds[i],&rfds);
                if(maxfd<fds[i])
                {//獲取最大文件描述符
                     maxfd=fds[i];
                }
            }
        }

        switch(select(maxfd+1,&rfds,NULL,NULL,/*&timeout*/NULL))
        {
            case 0:  printf("timeout...\n");
                     break;
            case -1: perror("select");
                     break;
            default:
            {
               for(i=0;i<nums;i++)
               {
                   if(fds[i]<0)
                   {//遍歷rdfs
                       continue;
                   }
                   if(i==0 && FD_ISSET(listen_sock,&rfds))
                   {//listen_sock就緒
                       struct sockaddr_in client;
                       socklen_t len=sizeof(client);
                       //等待客戶端的連接
                       int new_sock=accept(listen_sock,(struct sockaddr*)&client,&len);
                       if(new_sock<0)
                       { 
                           perror("accept");
                           continue;
                       } 
                       //客戶端成功連接
                       printf("get new client:[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
                       int j=1;
                       for(;j<nums;j++)
                       {
                           if(fds[j]==-1)
                           {
                               break;
                           }
                       }
                       if(j==nums)
                       {
                           printf("server full!\n");
                           close(new_sock);
                       }
                       else
                       {
                           fds[j]=new_sock;
                       }
                   }
                   else if(i>0 && FD_ISSET(fds[i],&rfds))
                   {//其他事件就緒
                       char buf[1024];
                       //讀取客戶端發送的數據
                       ssize_t s=read(fds[i],buf,sizeof(buf)-1);
                       if(s>0)
                       {
                           buf[s]=0;
                           printf("client say# %s\n",buf);
                       }
                       else if(s==0)
                       {//客戶端斷開連接
                           printf("client quit!\n");
                           close(fds[i]);
                           fds[i]=-1;
                       }
                       else
                       {
                           perror("read");
                       }
                   }
                   else
                   {}
               }
            }
            break;
        }
    }
}

運行結果:
使用telnet連接select服務器,連接成功後給出提示,並且telnet向select發送數據:
這裏寫圖片描述
這裏寫圖片描述

當telent退出時,select給出連接斷開的提示:
這裏寫圖片描述
這裏寫圖片描述

3.select的優缺點
優點:
與多線程和多進程服務器相比,select一次等待多個文件描述符,等待時間變短,提高了性能;
缺點:
①select本身對能監控的文件描述符有上限,默認爲1024;
②每次使用select都要將後4個參數重新設置,性能變低;
③每次調用select都要遍歷所有fd,當fd較多時,開銷很大;
④select爲系統調用,每次調用都要將fd集合從用戶態拷貝到內核,開銷很大;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章