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
版权声明:本文为博主原创文章,转载请附上博文链接!

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

  1. 高级I/O之I/O多路转接——pool、select

    当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中使用阻塞I/O: ) if (write(STDOUT_FILENO, buf, n) != n) err_sys("wri ...

  2. IO多路转接select和poll

    select IO多路复用的设置方法与信号的屏蔽有点相似: 信号屏蔽需要先设定一个信号集, 初始化信号集, 添加需要屏蔽的信号, 然后用sigprocmask设置 IO多路转接需要先设定一个文件描述符 ...

  3. Linux下I/O多路转接之select --fd_set

    fd_set 你终于还是来了,能看到这个标题进来的,我想,你一定是和我遇到了一样的问题,一样的疑惑,接下来几个小时,我一定竭尽全力,写出我想说的,希望也正是你所需要的: 关于Linux下I/O多路转接 ...

  4. UNIX环境高级编程——I/O多路转接(select、pselect和poll)

    I/O多路转接:先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I/O时,该函数才返回.在返回时,它告诉进程哪些描述符已准备好可以进行I/O. poll.pselect ...

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

    当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻塞在一个描述符上面,另外的描述符虽然有数据但是不能读出来,这样实时性不能满足要求,大概的解决方案有以下几种: 1.使用多进程或 ...

  6. select 与 I/O多路转接

    参考博客:http://blog.sina.com.cn/s/blog_607072980102uxcw.html I/0多路转接: 描述符表示某个I/O.构造一张有关描述符的数据表,调用select ...

  7. I/O多路转接之select

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

  8. I/O多路转接之poll 函数

    poll 一.poll()函数: 这个函数是某些Unix系统提供的用于执行与select()函数同等功能的函数,自认为poll和select大同小异,下面是这个函数的声明: #include < ...

  9. I/O多路转接 --- UNIX环境高级编程

    I/O多路转接技术:先构造一张有关描述符的列表,然后调用一个函数,知道这些描述符中的一个已准备好进行I/O时,给函数才返回.在返回时,它告诉进程哪些描述符已准备好可以进行I/O. poll.selec ...

随机推荐

  1. TP方法中打印地址栏中所有的参数:

    print_r($this->request->param());//打印出地址栏中所携带的所有参数:

  2. CSS Basic Memo

    1.bootstrap 清除浮动原理 .clearfix:before, .clearfix:after { content: ' ', display: table } .clearfix:afte ...

  3. 201671010142 <java程序设计>初次学习心得与感悟

    从开始对JDK的配置就遇到了问题,从这点就可以知道自己知识的薄弱.又知道了在控制台下一些常用命令的掌握.对知识的理解挺艰难,比如遇到一个新的问题就不知道该从哪里入手,有时候还不知道到底问题是啥.接受能 ...

  4. E: Sub-process /usr/bin/dpkg returned an error code (1)错误解决

    在用apt-get安装软件时出现了类似于install-info: No dir file specified; try --help for more information.dpkg:处理 get ...

  5. Android : App客户端与后台服务的AIDL通信以及后台服务的JNI接口实现

    一.APP客户端进程与后台服务进程的AIDL通信 AIDL(Android Interface definition language-“接口定义语言”) 是 Android 提供的一种进程间通信 ( ...

  6. BigDecimal加减乘除

    import java.math.BigDecimal; public class Testmath { public static void main(String[] args) { String ...

  7. php源代码安装

    1.在官网下载php安装源文件.版本选择5的最新稳定版. [root@trial download]# wget http://cn.php.net/distributions/php-5.5.38. ...

  8. 关于multi-index

    [转载请注明出处]http://www.cnblogs.com/mashiqi 2017/02/22 将$D^{\alpha}$和$\partial^{\alpha}$区别对待.$D^{\alpha} ...

  9. 文笔很差系列1 - 也谈谈AlphaGo

    距离AlphaGo击败李世石已经过去数月了,心中的震撼至今犹在,全刊报道此项比赛的<围棋天地>杂志我已经看了不下十遍.总也想说点自己的意见,却也不知道从哪里说起,更不知道想表达些什么. 作 ...

  10. annotation的概念及其作用

    概念 能够添加到 Java 源代码的语法元数据.类.方法.变量.参数.包都可以被注解,可用来将信息元数据与程序元素进行关联.Annotation 中文常译为“注解”. 作用 标记,用于告诉编译器一些信 ...