I/O多路转接(一)——select函数

I/O多路转接(一)——select函数

I/O的过程可以分成两步,等待和数据搬迁。
等待的过程等的是,读写事件就绪,比如说缓冲区有数据了说明读事件就绪,空了则说明写事件就绪。I/O的多路转接可以同时等待多个文件描述符,大大节省了I/O的等待时间,从而提高I/O效率。
多路转接函数要做的工作就是等待,等待读写或者异常事件就绪后通知用户。

第一篇介绍的是select函数。
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

返回值
大于0,有多少fd就绪;等于0,超出等待时间却没有一个fd就绪;小于0,出错。

参数
nfds:需要select等待的file descriptor数量。
readfds,writefds,exceptfds:分别代表了读事件集,写事件集,异常事件集。
timeout:代表了愿意等待的时间,timeout==NULL代表永远等待;timeout->tv_sec,
timeout->tv_usec分别代表秒与微妙,都等于0代表不等待检测后直接返回;不等于0代表愿意等待吃的时间。

对于读写异常事件集,用专门的操作函数。
void FD_CLR(int fd, fd_set *set);//清理一个fd
int FD_ISSET(int fd, fd_set *set);//检测一个fd是否就绪
void FD_SET(int fd, fd_set *set);//添加一个fd到set,代表你想关心哪个fd的哪个事件
void FD_ZERO(fd_set *set);//初始化

Select缺点

  1. select等待的file descriptor的数量是有上线的,默认为1024。
  2. 调用select时,需要将fd_array集合从用户态拷贝到内核态,同时也要在内核态中遍历所有传进来的fd。
  3. 由于三个事件集参数均是输入输出型参数,用户需要自己维护一个fd_array集合用来保存想要等待的file descriptor的事件,每次调用select前需要重新设置三个事件集,调用结束后又需要遍历检测 。
  4. 当select所等待的fd数量越来越多时,2与3的开销会越来越大,服务器的性能也随之越来越差。

下面是使用select编写的简单网路服务器
为了简单快速使用select这里只关心读事件。
server.c

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

int startup(char*ip,int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0){
        perror("socket");
        return 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");
        return 3;
    }

    if(listen(sock,10) < 0){
        perror("listen");
        return 4;
    }
    return sock;
}

int main(int argc,char *argv[])
{
    if(argc!= 3){
        printf("Usage:%s [ip] [port]\n",argv[0]);
        return 1;
    }
    int listen_sock = startup(argv[1],atoi(argv[2]));
    printf("listen_sock has been created,the value is %di\n",listen_sock);

    //监听套接字创建完成,等待读事件发生。
    fd_set rfds;//读事件描述副集。
    int size = sizeof(rfds)*8;
    int fd_array[size];//创建一个保存所有关心描述符的数组。
    int i = 0;

    for(;i<size;i++ ){//初始化数组。
        fd_array[i] = -1;
    }
    fd_array[0] = listen_sock;//数组第一个位置永远保存监听套接字描述符。
    struct timeval timeout = {5,1};
    while(1){
        int max = -1;
        FD_ZERO(&rfds);//清空读事件描述符集。
        for(i = 0;i < size; i++ ){
            FD_SET(fd_array[i],&rfds);//将想要关心的读时间描述符添加
                                      //到读事件描述符集。
            if(max < fd_array[i])
                max = fd_array[i];//找出最大的描述符
        }

        //设置读事件描述符集完成,开始调用select进行等待
        int ret = select(max + 1,&rfds,NULL,NULL,NULL/*&timeout*/);
        switch(ret){
            case 0:
                printf("timeout\n");
                break;
            case -1:
                perror("select");
                break;
            default:
                {
                    //一一查看所关心的读事件描述符是否发生状态改变
                    for(i= 0;i < size;i++){
                        if(fd_array[i] < 0)
                            continue;
                        if(i==0&&FD_ISSET(fd_array[i],&rfds)){          //listen_sock描述符状态改变说明有client请求连接
                            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;
                            }else{
                                printf("get a client!ip:%s,port:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
                                for(i= 0;i<size;i++){//找到一个未被占用的位置存放新链接的描述符。
                                    if(fd_array[i]< 0){
                                        fd_array[i]= new_sock;
                                        break;
                                    }
                                }
                                if(i==size){
                                    close(new_sock);
                                    printf("server is full\n");
                                }
                            }
                        }else if(i!= 0&&FD_ISSET(fd_array[i],&rfds)){//其他关心描述符状态改变。
                            char buf[1024];
                            ssize_t s = read(fd_array[i],buf,sizeof(buf)-1);
                            if(s >0){
                                buf[s]= 0;
                                printf("client say# %s\n",buf);
                            }else if(s==0){
                                printf("client is quit!\n");
                                close(fd_array[i]);
                                fd_array[i]= -1;
                            }else{
                                perror("read");
                                close(fd_array[i]);
                                fd_array[i]= -1;
                            }
                        }
                    } 
                }
                break; 
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值