前面一篇介绍了服务器端使用多线程的方式来处理多个客户端的请求的,但是当客户端数量增多时线程数量会急剧增加,导致消耗大量的资源。
于是就引出了服务器端的一种新的模型。
1. 阻塞与非阻塞
首先介绍几个基本的概念。
阻塞方式( block ),顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,假如事件没有发生,进程或线程就被阻塞,函数不能立即返回。
非阻塞方式( non-block ),就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,假如事件发生则和阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高。
套接字相关函数默认时采用阻塞方式操作,而大多数情况下,程序不仅仅只拥有一个套接字。当进程以阻塞方式操作其中之一时,必将导致不能使用其他的套接字。如果希望这些套接字同时工作,就必须设计并发的套接字程序,即在一个套接字读写的同时保证另一个套接字也能正常地操作。
多路复用函数 select 把一些文件描述符集合在一起,如果某个文件描述符的状态发生变化,比如进入“写就绪”或者“读就绪”状态,函数 select 会立即返回,并且通知进程读取或写入数据;如果没有 I/O 到达,进程将进入阻塞,直到函数 select 超时退出为止。
套接字也是文件,函数 select 也支持套接字描述符,并且可以应用在 TCP 协议和 UDP 协议的套接字中。利用多路复用,进程就可以同时监控多个套接字信息,在多个套接字上并发地执行操作。
2.select介绍
select的大概思想:将多个套接字放在一个集合里,然后统一检查这些套接字的状态(可读、可写、异常等),调用select后,会更新这些套接字的状态,然后做判断,如果套接字可读,就执行read操作。这样就巧妙地避免了阻塞,达到同时处理多个连接的目的。当然如果没有事件发生,select会一直阻塞,如果不想一直让它等待,想去处理其它事情,可以设置一个最大的等待时间。
-
int
select(
int
maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,
struct
timeval *timeout); -
/*参数列表
-
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
-
-
fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
-
-
fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
-
-
fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。
-
-
struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态:
-
第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
-
第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
-
第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
-
*/
-
/*
-
返回值:
-
-
-
负值:select错误
-
-
正值:某些文件可读写或出错
-
-
0:等待超时,没有可读写或错误的文件
-
*/
上面有两个结构体。
第一, struct timeval 代表时间值