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

来源:互联网 发布:vender软件 编辑:程序博客网 时间:2024/06/06 02:51

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;}
原创粉丝点击