select机制简介
在传统的网络编程方式中,在使用accept和recv等阻塞函数的时候,会造成应用程序阻塞,进而造成程序响应不及时(比如服务器在等待一个客户端发送数据,那么没有被等待的另一个客户端此时发送给服务器的数据不会被立即处理,造成客户等待)和CPU使用率低(经常等待数据,造成CPU经常没有活干)等等问题。为了解决传统网络编程方式存在的这些问题,linux设计者们提出了IO复用的概念(关于IO复用,请读者另外查询),并用select机制实现了这一概念,解决了前面所提到的问题。
linux c语言 select函数用法
表头文件 | #i nclude<sys/time.h> |
定义函数 | int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout); |
函数说明 | select()用来等待文件描述词状态的改变。参数n代表最大的文件描述词加1,参数readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。底下的宏提供了处理这三种描述词组的方式: |
参数 | timeout为结构timeval,用来设置select()的等待时间,其结构定义如下 |
返回值 | 如果参数timeout设为NULL则表示select()没有timeout。 |
错误代码 | 执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。 |
范例 | 常见的程序片段:fs_set readset; |
下面是linux环境下select的一个简单用法
FD_ZERO(fd_set* fds)
在我们使用fd_set之前一定要用此函数先清空一下,因为我们并不知道一个新建立的fd_set里面装的是什么数据,如果不清空就使用会造成程序意想不到的错误。
FD_SET(int fd , fd_set* fds)
将一个文件描述符加入进指定的fd_set内。(PS:由于每次使用select函数之后,系统都会将没有读写事件发生的文件描述符踢出此fd_set,所以下次继续使用select函数的时候,如果不将被踢出的描述符重新加入进来,就会造成无法检测文件描述符状态。所以,每次使用selct之后,一定要重新将被踢出的描述符重新加入表)
FD_ISSET(int fd, fd_set* fds)
检测指定的文件描述符是否在给定的fd_set中。
select(int maxfd , fd_set*write_fds , fd_set* readfds , fd_set* errfds , struct timeval* tv )
参数解析:
maxfd : 需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1(如在readset,writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所有1024位。
read_fd : 希望被检查集合中的文件描述符是否可读的fd_set
write_fd : 希望被检查集合中的文件描述符是否可写的fd_set
errfds : 希望被检查集合中的文件描述符是否出错的fd_set
理解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前循环加fd,取maxfd,select返回后利用FD_ISSET判断是否有事件发生。
4 select 机制的优势
先看一下下面的这句代码:
int iResult = recv(s,buffer,1024);
这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返回,不然就会一直阻塞在那里。在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永远没数据发送过来,那么程序就会被永远锁死。这个问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差。
再看代码:
int iResult =ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s,buffer,1024);
这一次recv的调用不管套接字连接上有没有数据可以接收都会马上返回。原因就在于我们用ioctlsocket把套接字设置为非阻塞模式了。不过你跟踪一下就会发现,在没有数据的情况下,recv确实是马上返回了,但是也返回了一个错误:WSAEWOULDBLOCK,意思就是请求的操作没有成功完成。看到这里很多人可能会说,那么就重复调用recv并检查返回值,直到成功为止,但是这样做效率很成问题,开销太大。
select模型的出现就是为了解决上述问题。
select模型的关键是使用一种有序的方式,对多个套接字进行统一管理与调度 。
https://www.cnblogs.com/ccsccs/articles/4224253.html select原理讲解。
https://www.cnblogs.com/cpper-kaixuan/p/3640392.html select用法讲解。
http://www.cppblog.com/hoolee/archive/2014/03/26/206351.aspx select使用示例。