IO复用:让进程等待一系列IO条件而不是一个IO条件数组
经过select
和poll
函数咱们能够同时监听多个描述符,在描述符就绪时进行对应的操做。ide
定义:函数
//maxfdpl: 待测试的描述符个数
//返回就绪描述符的个数,若超时则为0, 若出错则为-1
int select(int maxfdpl, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
//超时选项
//NULL:wait forever;0:don't wait
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
//每一个fds_bit的每一位对应一个描述符
typedef struct fd_set {
int fds_bits[FD_SETSIZE/sizeof(int)/NBBY]; /* NBBY=bits in a byte ; usually 8*/
} fd_set;
#define FD_SETSIZE 1024 /* fd_set中描述符的总数 */
void FD_ZERO(fd_set *fdset); /* clear all bits in fdset */
void FD_SET(int fd, fd_set *fdset); /* turn on the bit for fd in fdset */
void FD_CLR(int fd, fd_set *fdset); /* turn off the bit for fd in fdset */
void FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset ? */
复制代码
select的使用方法:测试
int fds[FD_SETSIZE]; 保存当前全部描述符
fd_set rset, wset, eset; //定义读、写、异常对应的fd_set
//初始化fd_set,很是重要且不能省略,由于若是不初始化可能会影响FD_ISSET的调用结果
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_ZERO(&eset);
for (;;) {
//在循环中调用select
select(FD_SETSIZE, &rset, &wset, &eset, NULL);
//遍历当前全部的fd,处理就绪的fd
for (int i = 0; i < FD_SETSIZE; i++)
{
if (FD_ISSET(fds[i], &rset))
{
//handle read
}
if (FD_ISSET(fds[i], &wset))
{
//handle write
}
if (FD_ISSET(fds[i], &eset))
{
//handle exception
}
}
}
复制代码
fd_set的限制spa
在很早以前就看到网上的介绍说select在描述符个数上是有限制的,如今终于知道这个限制是从哪来的了,这实际上跟fd_set的实现机制有关。rest
fd_set
中使用int数组中的各个位来保存多个描述符的状态,这个数组称为描述符集
,好比数组的第一个数有32位,那么第一个数的每一位就表示第0~31个描述符的状态,这样一来当咱们调用FD_ISSET
来判断某一个描述符状态时,咱们只须要找到其对应的位判断其是0或者1就好了;同理当咱们须要设置某个描述符状态时,只须要设置对应的位的状态便可。而fd_set
中数组的大小是经过FD_SETSIZE
这个值算出来的,FD_SETSIZE
是一个宏定义,一般它的默认值比较小,在个人mac上查看其默认值是1024,也就是说在个人mac上select可以支持的最大的描述符数量是1024个。固然FD_SETSIZE
也能够从新定义,但若是要调整须要从新编译内核。code
描述符读就绪条件orm
描述符写就绪条件进程
SIGPIPE
信号connect
的套接字已创建链接或者connect
失败shutdown&closeip
有两个函数能够关闭套接字:shutdown
和close
,它们的区别以下:
close
会将引用计数减1,当计数为0时关闭套接字;shutdown
能够直接触发关闭。close
会终止读和写两个方向;shutdown
能够经过参数howto
指定关闭某个方向int close(int fd);
int shutdown(int fd, int howto);
/* * howto arguments for shutdown(2), specified by Posix.1g. */
#define SHUT_RD 0 /* shut down the reading side */
#define SHUT_WR 1 /* shut down the writing side */
#define SHUT_RDWR 2 /* shut down both sides */
复制代码
poll
和select
的功能相似,也支持IO复用,可是poll
没有使用描述符集,而是使用pollfd
这种结构来表示描述符的状态。
//nfds:array的长度,受进程能打开的最大文件数限制
//返回就绪描述符的个数,若超时则为0, 若出错则为-1
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
struct pollfd {
int fd; /* descriptor to check */
short events; /* events of intrests on fd */
short revents; /* events that occurred on fd */
};
复制代码
poll
的使用方法:
struct pollfd pollfds[OPEN_MAX]; //定义pollfd数组,将须要监听的描述符保存起来
for (;;)
{
//在循环中调用poll
poll(pollfds, OPEN_MAX, INFTIM);
for (int i = 0; i < OPEN_MAX; i++)
{
//遍历pollfd数组处理就绪的描述符
struct pollfd pfd = pollfds[i];
if (pfd.revents & POLLIN) {
//handle read
}
if (pfd.revents & POLLOUT) {
//handle write
}
}
}
复制代码
poll
识别的数据类型:普通(normal)、优先级带(priority band)、高优先级(high priority); 这些术语来自基于流的实现。(没太明白,先标记下)
events常量列举:
常量 | 出如今events | 出如今revents | 说明 |
---|---|---|---|
POLLIN | y | y | 普通或优先级带数据可读 |
POLLRDNORM | y | y | 普通数据可读 |
POLLRDBAND | y | y | 优先级带数据可读 |
POLLPRI | y | y | 高优先级带数据可读 |
POLLOUT | y | y | 普通数据可写 |
POLLWRNORM | y | y | 普通数据可写 |
POLLWRBAND | y | y | 优先级带数据可写 |
POLLERR | n | y | 发生错误 |
POLLHUP | n | y | 发生挂起 |
POLLNVAL | n | y | 描述符不是一个打开的文件 |
poll的就绪条件:
POLLERR
)。随后的读操做将返回-1,并设置全局的errno
变量。connect
的完成视为使对应的套接字可写。select
和poll
都支持IO复用,其思路都是调用函数监听多个描述符,当有描述符就绪或者超时的时候函数调用就会返回,对应的描述符集合状态也会改变,这时候再遍历描述符集合,处理其中就绪的部分便可。
这种方式在须要监听的描述符比较小,或者是每次就绪的描述符不少的状况下比较有效;但当描述符不少并且每次只有少数描述符就绪时,效率就比较低了。后面出现的epoll
就避免了这种线性遍历的问题。
另外select
还受FD_SETSIZE
的限制,只能处理较少的描述符,而poll
则没有这个限制。poll
监听的集合大小只受进程能打开的文件数量(RLIMIT_NOFILE
)的限制。