I/O多路复用模型之select(一)

原理:

select函数会等待,直到描述符句柄中有可用资源(可读、可写、异常)时返回,返回值是可用资源(可读/可写/异常等)描述符的个数(>0),0代表超时,-1代表错误。具体到内核大致是:当应用程序调用select() 函数, 内核就会相应调用 poll_wait(), 把当前进程添加到相应设备的等待队列上,然后将该应用程序进程设置为睡眠状态。直到该设备上的数据可以获取,然后调用wake_up()唤醒该应用程序进程。select每次轮训都会遍历所有描述符句柄。

函数接口:

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

max_fd   :fd+1

readfds  :可读描述符句柄

writefds :可写描述符句柄

exceptfds:异常描述符句柄

timeout  :超时时间

select函数的参数将告诉内核:

(1)我们所关心的对应描述符句柄

(2)对于每个描述符我们所关心的条件,是否可读,是否可写或是否异常

(3)希望等待多长时间,struct timeval * timeout

struct timeval{

long tv_sec;   /*秒 */

long tv_usec;  /*微秒 */

}

timeout == NULL  等待无限长的时间;

timeout->tv_sec == 0 &&timeout->tv_usec == 0不等待,直接返回;

timeout->tv_sec !=0 ||timeout->tv_usec!= 0 等待指定的时间,超时后返回0,表示在一定时间内没有可用资源的描述符。

一个描述符句柄保存在fd_set类型中,fd_set类型变量每一位代表了一个描述符。我们也可以认为它只是一个由很多二进制位构成的数组。

fd_set类型变量是一下几个宏来控制它:

#include <sys/select.h>

int FD_ZERO(int fd, fd_set *fdset);   --将一个fd_set类型变量所有位设置为0

int FD_CLR(int fd, fd_set *fdset);    --将fd_set类型变量中对应fd删除

int FD_SET(int fd, fd_set *fd_set);   --将fd添加到fd_set类型变量中,进而将fd_set类型变量的对应的位置位

int FD_ISSET(int fd, fd_set *fdset);  --fd是否在fd_set类型变量中,对应为1说明资源可用

select函数执行结果:执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。错误

值可能为:

EBADF 文件描述词为无效的或该文件已关闭

EINTR 此调用被信号所中断

EINVAL 参数n 为负值。

ENOMEM 核心内存不足

从http://blog.csdn.net/lingfengtengfei/article/details/12392449中摘取“理解select模型:”

理解select模型:

理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

(1)执行fd_set set;FD_ZERO(&set);则set用位表示是0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

(3)若再加入fd=2,fd=1,则set变为0001,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

基于上面的讨论,可以轻松得出select模型的特点:

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

(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判断是否有时间发生)。

下面具体举个简单例子。

功能实现:

服务器回复从客户端接收到的数据-->回显数据

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<errno.h>

#include<netdb.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#include<netdb.h>

#include<sys/time.h>

#include<string.h>

#include<sys/select.h>

#include<pthread.h>

/*************************
*用于返回最大文件描述值
*
*************************/
int max_fd(int a[], int num)
{
    int max = -1;
    int i = 0;
    for(i = 0; i < num; i++)
    {
        if(a[i] > max)
        {
            max = a[i];

        }
    }

    return max;
}

int main(int argc, char *argv[])
{
    int sockfd = 0;
    int confd = 0;
    int i = 0, j = 0;
    fd_set fd_read[2];
    int clifd[FD_SETSIZE]; /*存放监听以及已与客户连接的套接字*/
    int fd_count = 0; /*已经连接的套接字个数*/
    struct sockaddr_in seradd, cliadd;
    int fd_ret = 0;
    socklen_t cli_len = sizeof(cliadd);
    char readbuf[1024];
    char writebuf[1024];
    /*create sock;*/
    sockfd = socket(AF_INET, SOCK_STREAM, 0); /*此套接字是阻塞的*/
    if(sockfd < 0)
    {
        perror("sock create fail !!");
        exit(-1);
    }

    seradd.sin_family = AF_INET;
    seradd.sin_port = htons(8080);
    seradd.sin_addr.s_addr = htonl(INADDR_ANY);

    /*bind*/
    if(-1 == (bind(sockfd, (struct sockaddr *)&seradd, sizeof(struct sockaddr))))
    {
        perror("bind error");
        exit(-1);
    }

    /*listen*/
    if(-1 == (listen(sockfd, 5)))
    {
        perror("listen error");
        exit(-1);
    }

    //memset(&fd_read[0],0,sizeof(fd_read[0]));

    FD_ZERO(&fd_read[0]); /*清空fd_read[0]所有位*/
    FD_SET(sockfd, &fd_read[0]); /*将sockfd添加到fd_read[0]描述集中,也就是说将sockfd对应的fd_read[0]位中置位*/
    for(i = 0; i < FD_SETSIZE; i++)
    {
        clifd[i] = -1;
    }

    clifd[0] = sockfd;
    printf("--ser start work---\n");
    while(1)
    {
        FD_ZERO(&fd_read[1]);
        fd_read[1] = fd_read[0]; /*每次select后,没有达到条件的描述将被清空,所以每次都需要重新赋值*/
        /*进程将阻塞在select函数处,直到在整个队列中有读描述符可用为止,个人理解--内核检测整个队列,然后将可用的描述符返回(一个或多个,一个没有时将阻塞)*/
        fd_ret = select(max_fd(clifd, FD_SETSIZE) + 1, &fd_read[1], NULL, NULL, NULL); /*我们只关心读描述符集*/

        if(fd_ret < 0)
        {
            perror("select error");
        }
        else if(fd_ret > 0)
        {
            /*是监听套接字可读*/
            if(FD_ISSET(sockfd, &fd_read[1]) && (fd_count < FD_SETSIZE - 1))
            {

                confd = accept(sockfd, (struct sockaddr *)&cliadd, &cli_len); /*获取与客户端连接套接字*/
                if(-1 == confd)
                {
                    perror("confd error");
                }

                for(i = 1; i < FD_SETSIZE; i++)
                {
                    if(clifd[i] == -1)
                    {
                        clifd[i] = confd; /*将获得新连接套接字放到clifd数组中*/
                        FD_SET(confd, &fd_read[0]); /*将获得新连接套接字添加到读描述集中*/
                        fd_count++;
                        break;
                    }
                }
            }

            /*连接套接字可读*/
            for(j = 1; j < FD_SETSIZE; j++)
            {
                if(FD_ISSET(clifd[j], &fd_read[1]))
                {
                    /*从clifd[i]套接字中读取数据*/
                    if(read(clifd[j], readbuf, sizeof(readbuf)) <= 0)
                    {
                        perror("read data error");
                        FD_CLR(clifd[j], &fd_read[0]); /*将clifd[j]描述从读描述符集中删除*/
                        close(clifd[j]); /*关闭该套接字*/
                        clifd[j] = -1;
                        fd_count--;
                        continue;

                    }

                    strcpy(writebuf, readbuf);
                    printf("read data:%s\n", readbuf);
                    if(write(clifd[j], writebuf, sizeof(writebuf)) <= 0)
                    {
                        perror("write data error");
                        FD_CLR(clifd[j], &fd_read[0]);
                        close(clifd[j]);
                        clifd[j] = -1;
                        fd_count--;
                        continue;
                    }
                }
            }
        }

    }

}

注意:在使用select函数时,请注意一下几点

1,select第一个参数是最大描述加1;

2,描述集中在每次select后都要重新赋值,select函数会把不符合条件的对应位清空;

3,如果有时间参数,在超时后,时间参数都必须重新赋值,否则会一直超时。

参考博客:

http://blog.chinaunix.net/uid-21275705-id-224351.html

http://blog.csdn.net/lingfengtengfei/article/details/12392449

http://linux.chinaunix.net/techdoc/net/2009/05/03/1109887.shtml

时间: 2024-10-13 17:27:34

I/O多路复用模型之select(一)的相关文章

linux下多路复用模型之Select模型

Linux关于并发网络分为Apache模型(Process per Connection (进程连接) ) 和TPC , 还有select模型,以及poll模型(一般是Epoll模型) Select模型极其作用:这文章讲述的很好,没必要重述已有的东西,就直接给链接 http://blog.csdn.net/turkeyzhou/article/details/8609360 我的理解: 1 /* According to POSIX.1-2001 */ 2 #include <sys/selec

I/O多路复用模型之select(二) 本文地址:http://www.itpux.com/thre

Select函数实现原理分析(转载) select需要驱动程序的支持,驱动程序实现fops内的poll函数.select通过每个设备文件对应的poll函数提供的信息判断当前是否有资源可用(如可读或写),如果有的话则返回可用资源的文件描述符个数,没有的话则睡眠,等待有资源变为可用时再被唤醒继续执行. 下面我们分两个过程来分析select: 1. select的睡眠过程 支持阻塞操作的设备驱动通常会实现一组自身的等待队列如读/写等待队列用于支持上层(用户层)所需的BLOCK或NONBLOCK操作.当

几种典型的服务器网络编程模型归纳(select poll epoll)

1.同步阻塞迭代模型 同步阻塞迭代模型是最简单的一种IO模型. 其核心代码如下: bind(srvfd); listen(srvfd); for(;;) { clifd = accept(srvfd,...); //开始接受客户端来的连接 read(clifd,buf,...); //从客户端读取数据 dosomthingonbuf(buf); write(clifd,buf)//发送数据到客户端 } 上面的程序存在如下一些弊端: 1)如果没有客户端的连接请求,进程会阻塞在accept系统调用处

Linux的I/O多路复用机制之--select&poll

1. Linux下的五种I/O模型 1)阻塞I/O(blocking I/O)2)非阻塞I/O (nonblocking I/O)3) I/O复用(select 和poll) (I/O multiplexing)4)信号驱动I/O (signal driven I/O (SIGIO))5)异步I/O (asynchronous I/O (the POSIX aio_functions)) (前四种都是同步,只有最后一种才是异步IO.) 五种I/O模型的比较: 2.多路复用--select 系统提

[转]IO模型及select、poll、epoll和kqueue的区别

(一)首先,介绍几种常见的I/O模型及其区别,如下: blocking I/O nonblocking I/O I/O multiplexing (select and poll) signal driven I/O (SIGIO) asynchronous I/O (the POSIX aio_functions)—————异步IO模型最大的特点是 完成后发回通知. 阻塞与否,取决于实现IO交换的方式.      异步阻塞是基于select,select函数本身的实现方式是阻塞的,而采用sel

多路复用输入/输出 ---- select

一.select 系统提供select函数来实现多路复用输入/输出模型.select系统调用是用来让我们的程序监视多个文件句柄的状态变化的.程序会停在select这里阻塞等待,直到被监视的文件句柄有一个或多个发生了状态改变. 文件句柄,其实就是一个整数,我们最熟悉的句柄是0.1.2三个,0:标准输入,1:标准输出,2:标准错误输出.0.1.2是整数表示的,对应的FILE *结构:stdin.stdout.stderr. 二.select 相关 1.select函数  //一次可等待多个描述符 #

39.IO多路复用(用select实现伪并发)

IO多路复用 1.用select实现多端口被多客户端访问的多路复用伪并发 IO多路复用服务端:既读又写 # IO多路复用实现伪并发 用多个IO,可以监听多个文件句柄(socket对象)(一般是可以读了或者可以写了), # 一旦文件句柄出现变化,就可以感应到 # 对于原生的socket 只能处理一个请求,只能监听一个端口 # 1.如何让server端监听两个端口 import socket sk1 = socket.socket() sk1.bind(('127.0.0.1', 8001,)) s

epoll模型与select模型的区别

Nginx  --->epoll模型 Apache --->select模型 处理大量连接的读写时,Apache所采用的select网络I/O模型比较低,用两个通俗的比喻来解释二者的区别: 第一个比喻: 例如你在大学读书,住的宿舍楼有很多房间,你的朋友要来找你,select版宿管大妈就会 带着你的朋友到各个房间挨个去找,直到找到为止.而epoll版宿管大妈会先记下每位入住同学的房间号码,当你朋友来找你时,只需告诉你的朋友你住在哪个房间?不用亲自带着你的朋友满宿舍的找.如果同时来了100个人,都

Socket I/O模型之select模型

socket网络编程中有多种常见的I/O模型: 1.blocking阻塞 2.nonblocking非阻塞 3.I/O multiplexing复用 4.signal driven 5.asynchronous I/O异步 这里我们主要介绍I/O multiplexing模型中的代表select模型:select模型将多个套接字放在一个集合里,然后统一检查这些套接字的状态,每次调用套接字后会更新这些套接字的状态,然后做判断,如果套接字可读,就执行read操作.这样就巧妙地避免了阻塞,达到同时处理