I/O多路轉接之select

I/O多路轉接之select(只負責等

系統提供select函數來實現多路複用輸入/輸出模型。

wKioL1ebbQeR8Qb7AACv6XIoP-A453.jpg-wh_50

傳向select的參數告訴內核:

1)我們所關心的文件描述符。

參數nfds是需要監視的最大的文件描述符值+1;

2)對每個描述符,我們所關心的狀態。

rdset,wrset,exset分別對應於需要檢測的可讀文件描述符的集合,可寫文件描述符的集合及異常文件描述符的集合。

監視的文件描述符分爲三類set,每一種對應等待不同的事件。readfds中列出的文件描述符被監視是否有數據可供讀取(如果讀取操作完成則不會 阻 塞)。writefds中列出的文件描述符則被監視是否寫入操作完成而不阻塞後,exceptfds中列出的文件描述符則被監視是否發生異常,或者無 法控制的數據是否可用(這些狀態僅僅應用於套接字)。這三類set可以是NULL,這種情況下select()不監視這一類事件。


下面的宏提供了處理這三種描述詞組的方式:

FD_ZERO移除指定set中的所有文件描述符。每一次調用select()之前都應該先調用它;

FD_CLR則從指定的set中移除一個文件描述符;

FD_SET添加一個文件描述符到指定的set中;

FD_ISSET測試一個文件描述符是否指定set的一部分。如果文件描述符在set中則返回一個非0整數,不在則返0,FD_ISSET在調用select() 返回之後使用,測試指定的文件描述符是否準備好相關動作。

3)我們要等待多長時間。(我們可以等待無限長的時間,等待固定的一段時間,或者根本就不等待)

timeout參數是一個指向timeval結構體的指針,timeval定義如下:

wKioL1eb_jLz-YuXAAA7fNBC1lY833.jpg-wh_50

struct timeval結構用於描述一段時間長度,如果在這個時間內,需要監視的描述符沒有事件發生則函數返回,返回值爲0。

從 select函數返回後,內核告訴我們以下信息:

1)已經做好準備的描述符的個數。

2)對於三種條件哪些描述符已經做好準備。(讀,寫,異常)

執成功則返回件描述詞狀態已改變的個數;如果返回0代表在描述詞狀態改變前已超過timeout時間,沒有返回;
當有錯誤發時則返回-1,錯誤原因存於errno,此時參數readfds,writefds,exceptfds和timeout的值變成不可預測。錯誤值可能爲:EBADF 件描述詞爲效的或該件已關閉 ,EINTR 此調被信號所中斷 EINVAL 參數n 爲負值,ENOMEM 核內存不。

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


int fds[64];
const fds_nums=sizeof(fds)/sizeof(fds[0]);
static int startup(const char* _ip,int _port)
{
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(2);
    }
    int opt=1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

    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,5)<0)
    {
        perror("listen");
            exit(4);
    }
    return sock;
}
static void usage(const char* _proc)
{
    printf("Usage:%s[IP] [PORT]\n",_proc);
}
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        usage(argv[0]);
        exit(1);
    }
    int i=0;
    for(i=0;i<fds_nums;++i)
    {
        fds[i]=-1;
    }

    int  listen_sock=startup(argv[1],atoi(argv[2]));
    fd_set rset;
    FD_ZERO(&rset);
    FD_SET(listen_sock,&rset);

    fds[0]=listen_sock;

    int done=0;
    while(!done)
    {
        int max_fd=-1;
        for(i=0;i<fds_nums;++i)
        {
            if(fds[i]>0)
            {
                FD_SET(fds[i],&rset);
                max_fd=max_fd<fds[i]?fds[i]:max_fd;
            }
        }

        struct timeval timeout={0,0};
        switch(select(max_fd+1,&rset,NULL,NULL,NULL/*&timeout*/))
        {
            case 0:
                printf("timeout..\n");
                break;
            case 1:
                perror("select");
                break;
            default:
                for(i=0;i<fds_nums;++i)
                {
                    if(i=0&& FD_ISSET(listen_sock,&rset))
                    {
                        struct sockaddr_in peer;
                        socklen_t len=sizeof(peer);
                        int new_fd=accept(listen_sock,(struct sockaddr*)&peer,&len);
                        if(new_fd>0)
                        {
                            printf("get a new client:socket->%s:%d\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
                            int j=0;
                            for(j=0;j<fds_nums;j++)
                            {                            
                                if(fds[i]==-1)
                                {
                                    fds[j]=new_fd;
                                    break;
                                }
                            }
                            if(j==fds_nums)
                            {
                                close(new_fd);
                            }
                        }
                        else
                        {
                            if(FD_ISSET(fds[i],&rset))
                            {
                                char buf[1024];
                                memset(buf,'\0',sizeof(buf));
                                ssize_t _s=read(fds[i],buf,sizeof(buf)-1);
                                if(_s>0)
                                {
                                    printf("client#  %s\n",buf);
                                }
                                else if(_s==0)
                                {
                                printf("client close...\n");
                                close(fds[i]);
                                }
                                else
                                {
                                    perror("read");
                                }
                            }
                        }
                    }
                }
                break;
        }
    }
return 0;
}

select優點:

(1)相較於之前多線程的方法,使用select不用創建線程,更方便

(2)select目前幾乎在所有的平臺上都支持,其良好跨平臺支持也是它的一個優點 

select缺點:

(1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大 

(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大 

(3)能夠監視的文件描述符的數量存在最大限制,在Linux上一般爲1024,因爲它依賴於文件系統

(4)select()所維護的文件描述符的數據結構,隨着文件描述符數量的增大,其複製的開銷也線性增長。

(5)由於網絡響應時間的延遲使得大量TCP連接處於非活躍狀態,但調用select()會對所有socket進行一次線性掃描,這也會有一些開銷




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