Linux--高级I/O多路复用之select

来源:互联网 发布:java封装是什么意思 编辑:程序博客网 时间:2024/06/11 06:14

1. select是做什么的?

系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视**多个文件句柄**的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。

2. select函数原型?

 ![这里写图片描述](http://img.blog.csdn.net/20170606093025905?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2F5aGVsbG9fd29ybGQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 这里有几个参数。 1.参数nfds是需要监视的最大的文件描述符值+1。2.参数rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合。 如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:
    void FD_ZERO(fd_set *fdset);           //清空集合    void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中    void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除    int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写 

3.timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。

struct timeval{          long tv_sec;   //seconds          long tv_usec;  //microseconds       };

这个参数有三种选项:
(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。
(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。

select特点:

1、可监控的文件描述符个数取决与sizeof(fd_set)的值。本人服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,服务器上支持的最大文件描述符是4096(512*8)。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。

2、将fd加入select监控集的同时,还要使用一个数据结构array保存放到select监控集中的fd。
一是用于在select返回后,array作为源数据和fd_set进行FD_ISSET判断。
二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

3、select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

select缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
(3)select支持的文件描述符数量太小了,默认是1024。

测试程序:

server端:

#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>#include<string.h>#include<sys/stat.h>#include<fcntl.h>int array_fds[1024];int startup(char *ip,int port){    int sock = socket(AF_INET,SOCK_STREAM,0);    if(sock < 0)    {        perror("socket");        exit(1);    }    int flg = 1;    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&flg,sizeof(flg));    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,10)<0){        perror("listen");        exit(4);    }    return sock;}int main(int argc ,char* argv[]){    if(argc != 3)    {        printf("usage: %s [ip] [port]\n",argv[0]);        return 1;    }    int maxfd = 0;    int listenSock = startup(argv[1],atoi(argv[2]));    fd_set rfds;    array_fds[0] = listenSock;    int array_size = sizeof(array_fds)/sizeof(array_fds[0]);    int i = 1;    for(;i<array_size;++i)        array_fds[i] = -1;    while(1)    {        struct timeval timeout = {0,0};        FD_ZERO(&rfds);        maxfd = -1;        for(i = 0;i<array_size;++i)        {            if(array_fds[i] > 0){                FD_SET(array_fds[i],&rfds);                if(array_fds[i] > maxfd)                    maxfd = array_fds[i];            }        }    switch(select(maxfd+1,&rfds,NULL,NULL,NULL))//这里最后一个参数为timeout 则为等待一段时间后退出,若为NULL则表示阻塞等待    {        case 0:            printf("timeout...\n");            break;        case -1:            perror("select");            break;        default:            {                int j = 0;                for(;j<array_size;++j)                {                    if(array_fds[j] < 0)                        continue;                        //j==0表示listensock响应                    if(j == 0 && FD_ISSET(array_fds[j],&rfds)){                        struct sockaddr_in client;                        socklen_t len = sizeof(client);                        int new_fd = accept(array_fds[j],\                                            (struct sockaddr*)&client,&len);                        if(new_fd < 0){                            perror("accept");                            continue;                        }else{                            printf("get a new client:{%s:%d}\n",\                                    inet_ntoa(client.sin_addr),\                                    ntohs(client.sin_port));                            int k = 1;                            for(;k<array_size;++k){                                if(array_fds[k] < 0){                                    array_fds[k] = new_fd;                                    break;                                }                            }                        if(k == array_size){                            close(new_fd);                        }                    }                }                //j!=0表示其他的响应                else if(j!=0 && FD_ISSET(array_fds[j],&rfds)){                    char buf[10240];                    ssize_t s = read(array_fds[j],\                                     buf,sizeof(buf)-1);                    if(s > 0){                        buf[s] = 0;                        printf("client say :%s\n",buf);                    }else if(s == 0){                        printf("client quit~\n");                        close(array_fds[j]);                        array_fds[j] = -1;                    }else{                        perror("read");                        close(array_fds[j]);                        array_fds[j] = -1;                    }                   }            }        break;    }  } }}

实验结果:
这里写图片描述

原创粉丝点击