I/O多路复用——select

接下来我们讨论3篇关于I/O多路复用的问题,首先我们来看下select,select是多路复用当中最早的一种

I/O复用的最主要的功能就是让程序能够同时去监听多个文件描述符,这样程序的性能就能提高。

select介绍


我们先来看一下select的接口。

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);


从上面的这些接口我们应该能有写认识,首先我么来看select系统调用的参数的含义。

参数功能
nfds被监听的文件描述符的总数。通常是文件描述符最大值加1
readfds可读事件的文件描述符集合
writefds可写事件的文件描述符集合
exceptfds异常事件的文件描述符集合
timeout设定超时时间

值得注意的是,后面的三个参数即是输入型参数,又是输出型参数,输入表示关心那些文件描述符对应的特定事件发生。输出表示的是那些文件描述符对应的事件就绪。当就绪后,内核将会去修改这些文件描述符的集合。这三个参数都是fd_set结构体类型

typedef struct
{
    /*XPG4.2requiresthismembername.Otherwiseavoidthename
    fromtheglobalnamespace.*/
    #ifdef__USE_XOPEN
    __fd_maskfds_bits[__FD_SETSIZE/__NFDBITS];
    #define__FDS_BITS(set)((set)->fds_bits)
    #else
    __fd_mask__fds_bits[__FD_SETSIZE/__NFDBITS];
    #define__FDS_BITS(set)((set)->__fds_bits)
    #endif
}fd_set;

可以看出fd_set就是一个结构体数组,这个结构体数组的每一位就是一个文件描述符的标记,配套提供了一些对于fd_set操作的宏。

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);
功能
FD_CLR进行对应位fd
FD_ISSET进行判断对应位fd
FD_SET设置fd的对应位置
FD_ZERO进行清空fd_set

最后要说一下的就是timeout参数,这个参数用来设置超时时间,它也是一个结构体,它用来告诉应用程序select等待多久,这里的单位是微秒级别的。

timeout参数说明
0立即返回,即轮询
NULL阻塞监视文件描述符,当有时间就绪才返回
大于0的时间超时时间设置

select调用时内核级别的,select的轮询方式是和非阻塞轮询方式是不同的,select的轮询方式是同时可以对多个I/O端口进行监听,任何一个端口数据好了,这个时候就可以读了。然后通过系统调用,就可以把数据从内核拷贝到用户进程。

![enter description here][1]

select缺点


1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。

  一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max查看,有宏FD_SETSIZE进行限制fd的数量。32位机默认是1024个。64位机默认是2048.

2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:

   当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。

3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大

select示例



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

//设定一个select数组
#define __SIZE__ 64
int gfds[__SIZE__];

int startup(int port,char *ip)
{
    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;
}

int main(int argc,char *argv[])
{
    if(argc != 3)
    {
        printf("Usage : %s [local_ip] [local_port]\n",argv[0]);
        return 5;
    }
    //创建监听套接字
    int listen_sock = startup(atoi(argv[2]), argv[1]);

    int i=0;

    //让记录套接字的数组初始化为-1.
    for(;i < __SIZE__;i++)
    {
        gfds[i] = -1;
    }

    while(1)
    {
        //监听套接字插入gfds
        gfds[0] = listen_sock;
        //定义max最终在nfds使用
        int max_fd = -1;     
        //创建文件描述符集
        fd_set rfds;
        //对文件描述符集进行初始化
        FD_ZERO(&rfds);
        //将监听套接字设置在文件描述符集合当中
        FD_SET(listen_sock,&rfds);
        //进行查找最大的max_fd, 为了后续操作nfds
        int k = 0;
        for(;k < __SIZE__;k++)
        {
            if(gfds[k] != -1)
            {
                //记得最大的文件描述符的内容和gfds内容进行比较
                if(gfds[k] > max_fd)
                {
                    max_fd = gfds[k];
                }

                FD_SET(gfds[k],&rfds);
            }
        }

        //设置timeout值
        struct timeval timeout={5,0};

        //进行select I/O多路复用
        switch(select(max_fd+1,&rfds,NULL,NULL,&timeout))
        {
            case -1:
            {
                perror("select");
                break;
            }
            break;
            case 0:
            {
                printf("time out\n");
                continue;
            }
            break;
            default:
            {
                int j = 0;
                for(;j < __SIZE__;j++)
                {
                    if(gfds[j] == -1)
                    {
                        //continue;
                        break;
                    }
                    else if(FD_ISSET(gfds[j],&rfds) && gfds[j] == listen_sock)
                    {
                        struct sockaddr_in peer;
                        socklen_t len = sizeof(peer);
                        int sock =accept(listen_sock,\
                                         (struct sockaddr *)&peer,&len);
                        printf("client :ip:%s,port:%d\n",\
                               inet_ntoa(peer.sin_addr),\
                               ntohs(peer.sin_port));
                        printf("sock: %d\n",sock);
                        if(sock < 0)
                        {
                            perror("accept");
                            continue;
                        }
                        else
                        {
                            int m = 0;
                            for(;m < __SIZE__;m++)
                            {
                                if(gfds[m] == -1)
                                {
                                    gfds[m] = sock;
                                    FD_SET(gfds[m],&rfds);
                                    break;
                                }
                            }
                            if(m == __SIZE__)
                            {
                                close(sock);
                                printf("too many client\n");
                            }
                        }
                    }
                    else if(FD_ISSET(gfds[j],&rfds))
                    {
                        char buf[1024];
                        ssize_t _r = read(gfds[j],buf,sizeof(buf)-1);
                        if(_r > 0)
                        {
                            buf[_r] = 0;
                            printf("client echo # %s\n",buf);
                        }
                        else if(_r == 0)
                        {
                            printf("client is closed\n");
                            close(gfds[i]);
                            gfds[j] = -1;
                            continue;
                        }
                        else
                        {
                            perror("read");
                            return 5;
                        }
                    }
                }
                    break;
            }
            break;
        }
        }

    close(listen_sock);
    return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值