很多场合都需要IO复用,比如:
1)客户端程序要同时处理多个socket.
2)客户端程序要同时处理用户输入和网络连接
3)TCP服务器要同时处理监听 socket和连接socket.
4)服务器要同时处理TCP请求和UDP请求。
5)服务器要同时监听多个端口,或者处理多种服务。
注意:IO复用本身是阻塞的。
LINUX下实现IO复用的系统调用有select,poll和epoll.本节就是来依次分析这三个系统调用。
关于select调用
select系统调用的用途是:
在一段时间内,监听用户感兴趣的文件描述符,对这些描述符的可读、可写和异常事件。
其实我感觉都是这样的,不然也不用叫IO复用了。
先看看原型吧,不然以后也记不住。
#include<sys/select.h>
int select ( int nfds, fd_set* readfds, fd_set* writefds, fd_set* excepfds, struct timeval* timeout);
先解释下各个参数的意思。
nfds: 最大的文件描述符加1,因为文件描述符是从0开始计数。
readfds,writefds,excepfds分别指向可读、可写和异常等事件对应的文件描述符集合。
当select返回时,内核将修改这几个集合来表示哪些文件描述符已经就绪。
注意:此时原先的三个参数集合已经被破坏了。
fd_set结构体实际上是一个整型数组,数组的每个元素的每1位标记一个文件描述符。
注意:fd_set能容纳的文件描述符数量由FD_SETSIZE指定,所以有所限制。
平时编程时,可以用以下的几个宏来操作
#include<sys/select.h>
FD_ZERO---清除所有位
FD_SET-设置某个位
FD_CLR-清除某个位
FD_ISSET -测试某个位是否被设置
至于select API的timeout参数,则是用来设置select函数的超时时间,这个参数是个指针,
当select返回时,里面的值就是select等待了多久的时间。注意:select调用失败时,timeout值是不确定的。
timeout 0---select立即返回
timeout 非0---等待超时时间,然后返回。
timeout NULL--一直阻塞,直到某个文件描述符就绪。
select的返回值:所有可读,可写,异常文件描述符的总数。
失败时返回-1并且设置errno,这是一个进程级变量。
比如说,select是阻塞的,这个时候被信号中断,则select返回-1,并且errno为EINTR.表示被信号中断了。
~~~~~~~~~~~~以下是针对select调用来定义的
好的,这个时候,需要来确定下什么叫做socket可读。
1)socket内核接收缓冲区中的字节数>=其低水位SO_RCVLOWAT.
2) socket通信的对方关闭了连接,这个时候在缓冲区里有个文件结束符,此时读操作将返回0
3)监听socket上有连接请求,也就是连接队列里有可以accept的连接。
4)socket上有未处理的错误,此时我们可以用getsockopt来读取和清除该错误。
对于为什么有错误会设置为读条件满足,其实是因为你确实可以读,只不过读的话返回错误而已。
下列情况下可写
1)socket内核发送缓冲区里的可用字节数大于等于其低水位标记SO_SNDLOWAT.
也就是说有可用内核缓冲区供我们写一个socket.
2)socket的写操作被关闭,注意:此时可以写,但是写的结果是出发一个SIGPIPE信号。
3)socket使用非阻塞connect连接成功或者失败(超时)之后。
4)socket上有未处理的错误,此时我们可以用getsockopt来读取和清除该错误。
什么时候是一个socket的异常情况?
网络程序中,只有1种情况:socket上接收到带外数据。
其实就是这个意思:socket收到普通数据时select返回,然后这个 socket标记为可读。
如果收到带外数据,select 返回,然后这个socket标记为异常。
下一节,带外数据接收的时候需要指定MSG_OOB标识。
来源:oschina
链接:https://my.oschina.net/u/1382024/blog/194241