1. 程式人生 > >I/O多路轉接之select伺服器

I/O多路轉接之select伺服器

(1)每次調⽤用select,都需要把fd集合從⽤使用者態拷貝到核心態,這個開銷在fd很多時會很⼤大 (2)同時每次調⽤用select都需要在核心遍歷傳遞進來的所有fd,這個開銷在fd很多時也很⼤大 (3)select⽀支援的⽂檔案描述符數量太⼩小了,預設是1024 select 伺服器
發生了改變就是讀事件寫事件就緒
檔案描述符通常關心讀事件、寫事件、異常事件
也可以關心至少一個或者有多個事件
select是系統呼叫介面
只負責等,一次等多個檔案描述符就緒之後select就會返回通知上層

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select的引數:
nfds:等多個檔案描述符中最大的加一
後四個引數都是輸入輸出型引數
輸入型:哪些檔案描述符關心對應型別的事件
輸出型:哪些你所關心的檔案描述符對應得事件已經發生了

rdset,wrset,exset分別對應於需要檢測的可讀⽂檔案描述符的集合,可寫⽂檔案描述符的集 合及異 常⽂檔案描述符的集合。
struct timeval結構⽤用於描述⼀一段時間長度,如果在這個時間內,需要監視的描述符沒有事件 發⽣生則函式返回,返回值為0。

下⾯面的巨集提供了處理這三種描述片語的⽅方式: FD_CLR(inr fd,fd_set* set);⽤用來清除描述片語set中相關fd 的位 FD_ISSET(int fd,fd_set *set);⽤用來測試描述片語set中相關fd 的位是否為真 FD_SET(int fd,fd_set*set);⽤用來設定描述片語set中相關fd的位 FD_ZERO(fd_set *set);⽤用來清除描述片語set的全部位 引數timeout為結構timeval,⽤用來設定select()的等待時間,其結構定義如下:

如果引數timeout設為: NULL:則表⽰示select()沒有timeout,select將⼀一直被阻塞,直到某個⽂檔案描述符上發⽣生了 事件。 0:僅檢測描述符集合的狀態,然後⽴立即返回,並不等待外部事件的發⽣生。 特定的時間值:如果在指定的時間段⾥裡沒有事件發⽣生,select將超時返回

函式返回值: 執行成功則返回檔案描述詞狀態已改變的個數 如果返回0代表在描述詞狀態改變前已超過timeout時間,沒有返回; 當有錯誤發⽣生時則返回-1,錯誤原因存於errno,此時引數readfds,writefds,exceptfds和 timeout的值變成不可預測。錯誤值可能為: EBADF ⽂檔案描述詞為⽆無效的或該⽂檔案已關閉 EINTR 此調⽤用被訊號所中斷 EINVAL 引數n 為負值。 ENOMEM 核⼼心記憶體不⾜足 常見的程式⽚片段如下: fs_set readset; FD_SET(fd,&readset); select(fd+1,&readset,NULL,NULL,NULL); if(FD_ISSET(fd,readset)){…⋯…⋯}

理解select模型:
理解select模型的關鍵在於理解fd_set,為說明方便,取fd_set長度為1位元組,fd_set中的每⼀一bit 可以對應⼀一個⽂檔案描述符fd。則1位元組長的fd_set最⼤大可以對應8個fd。

(1)執⾏行fd_set set; FD_ZERO(&set);則set⽤用位表⽰示是0000,0000。
(2)若fd=5,執⾏行FD_SET(fd,&set);後set變為0001,0000(第5位置為1)
(3)若再加⼊入fd=2,fd=1,則set變為0001,0011
(4)執⾏行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都發⽣生可讀事件,則select返回,此時set變為0000,0011。注意:沒有事件 發⽣生的fd=5被清空。 基於上⾯面的討論,可以輕鬆得出select模型的特點:
(1)可監控的檔案描述符個數取決與sizeof(fd_set)的值。我這邊服務 器上sizeof(fd_set)= 512,每bit表示一個⽂檔案描述符,則我伺服器上⽀支援的最⼤大⽂檔案描述符是512*8=4096。據說 可調,另有說雖 然可調,但調整上限受於編譯核心時的變數值。
(1)可以有效突破select可監控的⽂檔案描述符上 限。
(2)將fd加⼊入select監控集的同時,還要再使⽤用一個數據結構array儲存放到select監控集 中的fd,一是⽤用於再select 返回後,array作為源資料和fd_set進⾏行FD_ISSET判斷。二是select 返回後會把以前加⼊入的但並無事件發⽣生的fd清空,則每次開始 select前都要重新從array取得fd 逐⼀一加入(FD_ZERO最先),掃描array的同時取得fd最⼤大值maxfd,⽤用於select的第⼀一個 參 數。
(3)可見select模型必須在select前迴圈array(加fd,取maxfd),select返回後迴圈array (FD_ISSET判斷是否有時間發⽣生)。

#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
int fds[sizeof(fd_set)*8];   
static void Usage(char*proc)
{
   printf("%s [local_ip] [local port]\n");
}
int startUp(char*ip,int port)
{
   int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(2);
    }
    struct sockaddr_in server;
    server.sin_family=AF_INET;
    server.sin_port=htons(atoi(port));
    server.sin_addr.s_addr=inet_addr(ip);
    if(bind(sock,(struct sockaddr*)&server,sizeof(struct sockaddr_in))<0)
    {
        perror("bind");
        exit(3);
    }
    if(listen(sock,10)<0)
    {
        perror("listen");
        exit(4);
    }
    return sock;
}
int main(int argv,char*argc[])
{

    if(argv!=3)
    {
        Usage(argc[0]);
        return 1;
    }
    int listen_sock=startUp(argc[1],argc[2]);
    int nums=sizeof(fd_set)*8;
    fd_set rds;
    int i=0;
    for(i=0;i<nums;i++)
    {
        fds[i]=-1;
    }
    while(1)
    {
      int max=-1;
      struct timeval timeout={5,0};
      fds[0]=listen_sock;
      for(i=0;i<nums;i++)
      {
          if(fds[i]>-1)
          {
              FD_SET(fds[i],&rds);//在rdss設定所要關心的檔案描述符對應的事件
          }
          if(max<fds[i])
          {
              max=fds[i];
          }
      }
      switch(select(max+1,&rds,NULL,NULL,&timeout))// 執行成功則返回檔案描述詞狀態已改變的個數 
      {
          case 0:
              printf("timeout...\n");
              break;
          case -1:
              perror("select");
              break;
        default:
              for(i=0;i<nums;i++)
              { 
                  if(i==0&&FD_ISSET(fds[i],&rds))//判斷listen_sock描述符上對應的事件是否就緒
                  {
                      struct sockaddr_in client;
                      socklen_t len=sizeof(client);
                      int  newsock=accept(listen_sock,(struct socketaddr*)&client,&len);//繼續取出將要關心的對應的檔案描述符上的對應的事件
                      if(newsock<0)
                      {
                          perror("accept");
                          return 2;
                      }
                      else
                      {
                          int j=0;
                          for(j=0;j<nums;j++)
                          {
                              if(fds[j]==-1)
                              {
                                  break;
                              }
                          }
                          if(j==nums)
                          {
                              close(newsock);
                          }
                          else
                          {
                              fds[j]=newsock;//將新的所要關心的檔案描述符對應的事件放到fds合適的位置
                          }
                      }
                  }
                else if(i!=0&&FD_ISSET(fds[i],&rds))//如果不是監聽套接字但是是其他檔案描述符對應的讀事件就緒了
                  {
                      char buf[1024];
                      ssize_t s=read(fds[i],buf,sizeof(buf)-1);
                      if(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");
                          close(fds[i]);
                          fds[i]=-1;
                      }
                  }
              }
      }
    }
    return 0;
}

(1)每次調⽤用select,都需要把fd集合從使用者態拷貝到核心態,這個開銷在fd很多時會很大
(2)同時每次呼叫select都需要在核心遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大 ’
(3)select⽀支援的⽂檔案描述符數量太小了,預設是1024