温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

I/O多路转接之select

发布时间:2020-07-27 16:58:14 来源:网络 阅读:401 作者:朔月云影 栏目:网络安全

I/O多路转接之select(只负责等

系统提供select函数来实现多路复用输入/输出模型。

 I/O多路转接之select

传向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定义如下:

 I/O多路转接之select

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进行一次线性扫描,这也会有一些开销




向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI