本章所有示例代码>>github
12.1 基于I/O复用的服务器端
1. 多进程服务器端的缺点
为了构建并发服务器,只要有客户端连接请求就会创建新进程。这的确是实际操作系统中采用的一种方案,但并非十全十美,因为创建进程时需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法(IPC属于相对复杂的方法)。
2. 理解复用
“在1个通信频道中传递多个数据(信号)的技术。”
时(time)分复用技术;
频(frequency)分复用技术;
3. 复用技术在服务器端的应用
多进程服务器端:
采用复用技术的服务器端,无论连接多少客户端,提供服务的进程只有1个:
12.2 理解select函数并实现服务器端
运用select函数是最具代表性的实现复用服务器端方法。
1. select函数的功能和调用顺序
使用select函数时可以将多个文件描述符集中到一起统一监视,监视项称为“事件”(event):
- 是否存在套接字接收数据?
- 无需阻塞传输数据的套接字有哪些?
- 哪些套接字发生了异常?
select函数的调用方法和顺序:
2. 设置文件描述符
利用select函数可以同时监视多个文件描述符,监视文件描述符可以视为监视套接字。此时首先需要将要监视的文件描述符集中到一起。集中时要按照监视项(接收、传输、异常)进行区分,即按照上述3种监视项分成3类。
使用fd_set结构体变量执行此操作。该数组是存有0和1的位数组。
最左端的位表示文件描述符0(所在位置)。如果该位设置为1,则表示该文件描述符是监视对象。
在fd_set变量中注册或更改值的操作都由下列宏完成:
- FD_ZERO(fd_set *fdset):将fd_set变量的所有位初始化为0;
- 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):若参数fdset指向的变量中包含文件描述符fd的信息,则返回“真”;
3. 设置检查(监视)范围及超时
#include<sys/select.h>
#include<sys/time.h>
int select(int maxfd,fd_set *readset, fd_set *writeset, fd_set
*exceptset, conststruct timeval *timeout);
//成功时返回大于0的值,失败时返回-1
-maxfd:被监听文件描述符个数,通常设置为监听的所有描述符最大值加1,因为文件描述符是从0开始的;
-readset:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值;
-writeset:将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值;
-exceptset:将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值;
-timeout:调用select函数后,为防止陷入无限阻塞的状态,传递超时(time-out)信息;
返回值:发生错误时返回-1,超时返回时返回0。因发生关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符;
第一,文件描述符的监视范围与select函数的第一个参数有关,每次新建文件描述符时,其值都会增1;
第二,select函数的超时时间与select函数的最后一个参数有关,其中,timeval结构体定义如下:
struct timeval
{
long tv_sec; // seconds
long tv_usec; // microsecond
};
本来select函数只有在监视的文件描述符发生变化时才返回。如果未发生变化,就会进入阻塞状态。指定超时时间就是为了防止这种情况的发生。
4. 调用select函数后查看结果
select函数调用完成后,向其传递的fd_set变量中将发生变化。原来为1的所有位均变为0,但发生变化的文件描述符对应位除外。
调用select函数后,结构体timeval的成员tv_sec和tv_usec的值将被替换为超时前剩余时间。