select函数与I/O多路转接

相作大家都写过读写IO操作的代码,例如从socket中读取数据可以使用如下的代码:

while( (n = read(socketfd, buf, BUFSIZE) ) >0)

if( write(STDOUT_FILENO, buf, n) = n)

{

printf(“write error”);

exit(1);

}

当代码中的socketfd描述符所对应的文件表项是处于阻塞时,它会一直阻塞,直到有数据从网络的另一端发送过来。如果它是一个服务器程序,它要读写大量的socket,那么在某一个socket上的阻塞很明显会影响与其它socket的交互过程。类似的问题不单单出现在网络上,还可以出现在读写加锁的文件和FIFO等等一系列的情况。

一种比较好的解决方法似乎是采用非阻塞IO来实现。把所要读取数据的socketfd设置为非阻塞状态,依次用read函数检查是否有数据到来,如有,它会返回接到数据的个数,否则它会返回-1以表示当前还没有数据到达。这样,对于每个socket,如有数据到来则读取,没有也会马上返回。这就是非阻塞IO的好处拉。部分代码如下:

//clientfd[] 为客户端的socket描述符组数,假设数组的大小为MAX,并且所有客户端socket描述符都设置为非阻塞状态时。

for(i = 0; i < MAX; ++i)

{

int n;

if( (n = read(clientfd[i], buf, SZIE)) >0)

{

//send response to client in here.

}

}

这里代码看起来与上面的代码没有太大的区别,其实是有很大的区别;区别就是使使用了非阻塞IO进行整个交互过程,使得各个客户端都得到相对平等的时间待遇。这种模式我们通常称为这“轮询”模式。轮询模式同样有它的不足之处,在执行read函数时,实际上大部分时间还是没有数据可读的,但仍不断地执行read,浪费了很多CPU时间。

实际,对于上述的问题,一种比较好的技术就是I/O多路转接(I/O multiplexing)它可谓是上面两种方法的接衷:先构造一张有关描述符的数据表,然后调用一个函数,仅当有一个或多个描述符已准备可以进行IO操作时才返回,否则一直阻塞。在返回时,它会告诉进程那些描述符已准备好可以进行IO。

现在实现多路转接的任务落在select函数的身上了,现在给大家详细介绍select函数的使用。我们的主角出场了,呵呵!掌声!

函数的功能:实现多路转接,通过调用内核来实现。它向内核提供如下的参数

1)我们所关心的描述符

2)对于每个描述符,我们所关心的条件(是否读一个给定的描述符,还是想写一个给定的描述符,还是关心一个描述符的异常条件)

3)希望等待多久时间(可以永远等待,等待一个固定时间,或完全不等待)

从select返回时,内核告诉我们:

1)已准备好的描述符数量

2)哪一个描述符已准备好读、写或异常条件

使用这种返回值,就可调用相应的I/O函数,通常是read或write,并确知该函数不会阻塞。

函数的定义:

#include <sys/types.h>

#include <sys/time.h>

#include <unistd.h>

int select( int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, sturct timeval *tvptr);

返回:准备就绪的描述符,若超时则为0,若出错则为-1

最后一个参数为struct timeval的指针变量,它指定愿意等待的时间。

struct timeval{

long tv_sec; /*秒数*/

long tv_usec; /*微秒数*/

};

对于参数tvptr有三种情况:

如果tvptr == NULL 则永远等待。如果捕捉到一个信号则中断此无限期等待。当指定的描述符中的一个或多个已准备好或捕捉到一个信号则返回。如果是捕捉到一个信息,则select返回-1,errno设置为EINTR.

如果tvptr->sec ==0 && tvptr->tv_usec == 0 则完全不等待。即测试所有的描述符后马上返回。这是得到多个描述符的状态而不阻塞select函数的轮询方法。

如果tvptr->tv_sec != 00 || tvptr->tv_usec != 0 则等待指定的秒数和微秒数。当指它的描述符之一已准备好,或指定的时间值已超时则返回。如果在超时时还没有一个描述符准备好,则返回值是0。与第一种情况类似,这种等待可能被信号所中断。

中间三个参数readfds, writefds, exceptfds是指向描述符集的指针,它们描述了我们关心的可读、可写和处异常条件的各个描述符。这种描述符集存在一种叫fd_set的数据类型中(在头文件select.h中有定义)。具体做法每个描述符对应于数据结构fd_set所占用内存空间的一个位,如果第i位为0则表示值为i的描述符不包含在该集中,反之亦然。为了方便用户使用,系统提供了如下的四个宏进行操作。

FD_ZERO(fd_set *fdset); //清空fdset中的所有位

FD_SET(int fd, fd_set *fdset); //在fdset中打开fd所对应的位

FD_CLR(int fd, fd_set *fdset); //在fdset中关闭fd所对应的位

FD_ISSET(int fd, fd_set *fdset); //测试fd是否在fdset中

通常做法是,先定义一个描述符集

fd_set rset;

int fd;

必须使用FD_ZERO清除其所有位

FD_ZERO(&rset);

然后设置我们所关心的位

FD_SET(fd, &rset);

FD_SET(STDOUT_FILENO,&rset);

从select返回时,用FD_ISSET测试该集中的一个给定位是否仍旧设置

if( FD_ISSET(fd, &rset)){

...

}

select函数的这三个参中的任一个(或全部)可以是空指针,这表示对相应的条件不关心。值得一提的是:如果这三个指针全部为空,则select函数提供了比sleep更精确的计时器(sleep等待整数秒,而select函数可以等待少于1秒的时间,具体时间粒度取决于系统时钟)。

select第一个参数 maxfdp1的意思是“最大的fd加1(max fd plus 1)”。在三个描述符集中找出最大的描述符值,然后加1,这就是第一个参数。也可以将第一个参数设置为FD_SETSIZE,这是<sys/types.h>这义的一个常数,通常是256或1024。但对于大部分应用程序来说,此值太大了。如果将maxfdp1设置为最大的描述符值加1,内核只需要在此范围内寻找打开位,而不必在上数百个的大范围内搜索。

如下是示例代码:

fd_set readset, writeset;

FD_ZERO(&readset);

FD_ZERO(&writeset);

FD_SET(0, &readset);

FD_SET(3, &readset);

FD_SET(1, &writeset);

FD_SET(2, &writeset);

select(4, &readset, &writeset, NULL, NULL); //注意第一个参数为4

select有三个可能的返值:

1)返回值-1表示出错。例在未有描述符准备好数据时捕捉到一个信号时

2)返回值0表示没有描述符准备好。若指定的描述符都没有准备好,并且指定的时间已到,则发生这种情况。

3)返回一个正数,说明已经准备好的描述符数,在这种情况下。三个描述符集中仍旧打开的位是已准备好的描述符位。

对于“准备好”的意思,要作一些列具体的说明:

1)对于读集中的一个描述符的read不会阻塞,则此描述符是准备好的。

2)对于写集中的一个描述符的write不会阻塞,则此描述符是准备好的。

3)对于异常条件集中的一个描述符有一个未决异常条件,则此描述符是准备好的。

如果在一个描述符中碰到文件结束符,则select认为描述符是可读的,然后调用read,它返回0,这是unix指示到达文件尾处的方法。

通过select函数实现I/O多路转接,上面第二个例子的代码可改写成如下:

//clientfd[] 为客户端的socket描述符组数,假设数组的大小为MAX。

//serverfd表示服务器描述符,非阻塞。

//readsocket表示客户端socket描述集,同样包括服务的socket描述符

//maxfdp1 表示readsocket中最大 socket值加1

while(1)

{

int n = select(maxfdp1, &readsocekt, NULL, NULL, NULL)

if(n >0)

{

//is that some connectiion request

if(FD_ISSET(serverfd, &readsocket))

{

//用accept函数来获取连接的客户socket描述符,并加到客户端描述符数组clientfd和readsocket中。

}

for(int i = 0; i < MAX; ++i)

{

if(FD_ISSET(clientfd[i], &readsocket))

{

//response to client here.

}

}

}

}

在本例代码每次循环时,都采用select函数查询是否有描述符准备好,有则处理。无则阻塞,直到有数据准备好为止。在这段时间里面,可以让CPU做其它事情,避免了轮询方法所占用的大量CPU时间。

最后关于I/O多路转接问题的情况。I/O多路转接至今还不是POSIX的组成部分。SVR4和4.3+BSD都提供select函数以执行I/O多路转接。SVR4实际上用poll实现select。同时,在SVR4和BSD的select实现之间,有些差异,BSD系统总是返回一个所有准备好的描述符数之和,如果某个描述符同时在两个集中(如读集和写集),则返回值把它的描述符中累加两次。不同的是,SVR4更正了这一点,只计一次。于此,唯有POSIX标准化了select这样的函数才能解决此问题。

最后,写本文的初衷是见到网上介绍select的资料不多,而且不够详细,故有感而写。上面的代码只能用来说明问题,也许表达得不够清楚。上面对select函数的描述来源于<<UNIX环境高级编程>>(中文版)一书。需要的话可以参考此书,此书不失为一本经典的UNIX书籍。
---------------------
作者:海枫
来源:CSDN
原文:https://blog.csdn.net/linyt/article/details/1722445
版权声明:本文为博主原创文章,转载请附上博文链接!

原文地址:https://www.cnblogs.com/ricks/p/10259436.html

时间: 2024-08-29 02:33:19

select函数与I/O多路转接的相关文章

高性能服务器——I/O多路转接的三种模式(select &poll& epoll)

一.简单的服务器I/O模型 最简单的的TCP服务器,有三种模式: 1.单执行流,一个server端连接一个client端 2.多进程,一个server端通过多进程的方式,每个进程连接一个client端 3.多线程,一个server端通过多进程的方式,每个线程连接一个client端 (http://zhweizhi.blog.51cto.com/10800691/1830267) 这里实现过 要提升服务器性能,其实就是想要让一个server端能在负载允许的情况下,连接尽可能多的client端. 因

【Nginx】I/O多路转接之select、poll、epoll

从socket中读取数据可以使用如下的代码: while( (n = read(socketfd, buf, BUFSIZE) ) >0) if( write(STDOUT_FILENO, buf, n) = n) { printf(“write error”); exit(1); } 当代码中的socketfd描述符所对应的文件表项是处于阻塞时,它会一直阻塞,直到有数据从网络的另一端发送过来.如果它是一个服务器程序,它要读写大量的socket,那么在某一个socket上的阻塞很明显会影响与其它

I/O多路转接select/poll/epoll

I/O多路转接(多路复用)又被称为“事件驱动”,是操作系统提供的一个功能,当你关心的文件(如socket)可读.可写时(称为事件就绪)采用某种方式通知你,只有收到通知时你才去执行read/write操作,这样在每次读或写时就不会阻塞,即I/O操作中等的部分交给操作系统内核去完成,而read/write之类的操作只需要在事件就绪时完成数据拷贝.等的过程由select/poll/epoll等系统调用触发,这些函数可同时监视多个描述符上的事件是否就绪,因此可以在一个线程内不发生阻塞的交替完成多个文件的

I/O多路转接之select

系统提供select函数来实现多路复用输入/输出模型.select系统调用时用来让我们程序监视多个文件句柄的状态变化的.程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变.关于文件句柄,其实就是一个整数,我们最熟悉的文件句柄是0,1,2三个,0是标准输入,1是标准输出,2是标准错误输出. 函数原型: 参数: nfds 输入型参数   readfds,writefds,exceptfds,timeout即为输入型参数又为输出型参数 nfds:最大文件描述符+1: rea

I/O多路转接之 select

系统提供select函数来实现多路复用输入/输出模型. 作用:select系统调用是用来让我们的程序监视多个文件句柄的状态变化的.程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变. 函数原型: 参数说明: int nfds:需要监视的最大文件描述符值+1; fd_set *readfds & *writefds & *exceptfds:指向文件描述符的指针:这三个描述符集说明了我们关心的可读.可写或处于异常条件的各个描述符: struct timeval *

I/O多路转接-epoll

By francis_hao    Aug 5,2017 APUE讲多路转接的章节介绍了select.pselect和poll函数.而epoll是linux内核在2.5.44引入的.在glibc 2.3.2添加了支持. epoll_create – 打开一个epoll文件描述符 epoll_ctl – 控制epoll文件描述符接口 epoll_wait – 在epoll文件描述符上等待一个I/O事件 概述 #include <sys/epoll.h>int epoll_create(int s

IO多路转接

IO多路转接的技术可以避免阻塞IO的弊端,因为我们有时候需要在多个描述符上读read.写write,如果使用阻塞IO,就有可能长时间阻塞在某个描述符上而影响其它描述符的使用. 关于阻塞IO的处理办法,可以考虑一下几个方案: 1.多进程.弊端是多个进程终止时的通信,增加了程序的复杂度. 2.多线程.弊端是多个线程之间的同步,同样也增加了程序的复杂度. 3.轮询polling.使用非阻塞IO读取数据,弊端是浪费CPU时间,在多任务系统中应当避免使用这种方法. 4.异步IO.用到了信号机制,如系统V的

I/O多路转接

对于多个非阻塞I/O,怎么知道I/O何时已经处于可读或可写状态? 如果采用循环一直调用write/read,直到返回成功,这样的方式成为轮询(polling).大多数时间I/O没有处于就绪状态,因此这样的轮询十分浪费CPU. 一种比较好的技术是使用I/O多路转接,也叫做I/O多路复用.其基本思想为:先构造一个有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行I/O时,该函数才返回.在返回时,它告诉进程哪些描述符已经准备好可以进行I/O了. 有3个函数poll.pselect

select 函数1

Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect.accept.recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回).可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不