I/O复用_select_学习

    最近在学习网络编程,觉得select这块的知识点确实比较难以理解,在学习socket网络通信机制时,只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序,(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞方式工作的程序,所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。下面详细介绍一下!

    select在socket编程总自己认为还是很重要的。这种I/O复用技术,可以大大的提高效率。

I/O多路复用技术适用的场合:

(1)当客户处理多个描述符时(一般是交互式输入和网络套接口),必须使用I/O复用技术。

(2)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,要用到I/O复用。

(3)如果一个服务器既要处理TCP,又要处理UDP,要使用I/O复用。

(4)如果一个服务器要处理多个服务或多个协议,要使用I/O复用。

但是以上的几种要求也可以用多进程多线程来完成,那么这里用I/O复用技术的优点是,系统开销小,系统不必创建多线程多进程,也不必维护多进程和多线程,而大大减小了系统的开销。

select函数理解:

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

在网络程序中,一个进程同时处理多个文件描述符是很常见的情况

select()系统调用可以使进程检测同时等待的多个I/O设备,当没有设备准备好时,select()阻塞,其中任一设备准备好时,select()就返回

select的返回值有如下情况:
1. 正常情况下返回就绪的文件描述符个数;
2. 经过了timeout时长后仍无设备准备好,返回值为0;
3. 如果select被某个信号中断,它将返回-1并设置errno为EINTR;
4. 如果出错,返回-1并设置相应的errno。

select中间的3个参数readfds, writefds, errorfds指定我们要让内核测试读,写,异常条件的描述符,如果对某一条件不感兴趣,可以把他设为空指针。

参数readfds指定了被读监控的文件描述符集;

参数writefds指定了被写监控的文件描述符集;

参数exceptfds指定了被例外条件监控的文件描述符集;

fd_set可以理解为一个集合。用来标识对应的描述符的状态。初始化是将文件描述符集合全设为0,如果哪个客户端准备好了要连接,则将客户端描述符对应的在描述符集合中的位置置为1.系统提供了4个宏对描述符集进行操作:

void FD_SET(int fd, fd_set *fdset);  //设置文件描述符集fdset中对应于文件描述符fd的位(设置为1)
void FD_CLR(int fd, fd_set *fdset);  //清除文件描述符集fdset中对应于文件描述符fd的位(设置为 0
void FD_ISSET(int fd, fd_set *fdset); //判断文件描述符集fdset中有没有描述符fd
void FD_ZERO(fd_set *fdset);      //清除文件描述符集fdset中的所有位(既把所有位都设置为0)
复制代码

    描述符集现在通常用整数数组中的位域表示,数组元素的每一位对应一个文件描述符

例如,一个整数占32位,那么整数数组的第一个元素代表文件描述符0到31,数组的第二个元素代表文件描述符32到63,以此类推

宏FD_SET设置整数数组中对应于fd文件描述符的位为1,

宏FD_CLR设置整数数组中对应于fd文件描述符的位为0,

宏FD_ZERO把fdset指向的文件描述符集合对应的位全部初始化为0。

假设执行如下程序后:
fd_set readset;
FD_ZERO(&readset);
FD_SET(5, &readset);
FD_SET(33, &readset);
则文件描述符集readset中对应于文件描述符5和33的相应位被置为1,如图1所示:

再执行如下程序后:
FD_CLR(5, &readset);
则文件描述符集readset对应于文件描述符5的相应位被置为0,如图2所示:

通常,操作系统通过宏FD_SETSIZE来声明在一个进程中select所能操作的文件描述符的最大数目。

例如:在Linux的头文件中我们可以看到:

#define __FD_SETSIZE 1024
#define FD_SETSIZE  __FD_SETSIZE

既定义FD_SETSIZE为1024,一个整数占4个字节,既32位,那么就是用包含32个元素的整数数组来表示文件描述符集

我们可以在头文件中修改这个值来改变select使用的文件描述符集的大小,但是必须重新编译内核才能使修改后的值有效。

上面是对select的总结,那么,有下面的几个问题:

(1)I/O复用可以做到既要处理监听套接口,又要处理已连接套接口,那么select是怎么做的?

监听套接口负责处理要连接服务器的客户端。

已连接套接口负责处理已经连接的客户端的读写操作。

当服务器阻塞在select中时,只要一个客户端准备好连接服务器,则select立即返回,但是服务器并不知道是哪个客户端的描述符的状态改变,因此要通过FD_ISSET()来判断。如果发现描述符集合fd_set中有值为1,那么这个位置的描述符fd就是第一次连接服务器,因此服务器进行accept()操作。但如果select立即返回,通过FD_ISSET()检查发现在描述符集合中对应的描述符的值没有变,因此服务器要进行对已连接客户端的读写操作。

(2)由于是I/O复用,也就是说服务器要监测好多的socket套接字,那么不同的客户端给服务器发送的请求不同,服务器是怎么知道哪个客户端给它发的消息并且回符相应的消息给客户端?

用select将文件描述符集合从用户态拷贝到内核态,通过FD_ISSET()来遍历描述符集合,看哪个位置上为1,即根据位置就能知道是哪个客户端的描述符。

select 的几个缺点:
(1)select支持的文件描述符集的数量太少,默认是1024,如果要增大默认的描述符集要重新编译内核,麻烦。
(2)每次调用select,都需要把fd集合从用户态拷贝到内核态,如果fd集合比较多,那么这个开销很大。
(3)select返回后原有的fd集合被改变,所以每次返回要将原有数据重置。
(4)每次select返回代表有客户端状态改变,因此都要遍历整个fd集合去查看哪个客户端发生改变。开销很大。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值