目录
- 1.IO多路转接之select
-
- 1.初识select
- 2.select()
- 3.关于fd_set结构
- 4.关于timeval结构
- 5.理解select执行过程
- 6.select就绪条件
- 7.select特点
- 8.select优点(任何一个多路转接方案,都具备)
- 9.select缺点
- 10.select的一般编写代码的模式
- [11.思考 && 问题](#11.思考 && 问题)
- 2.IO多路转接之poll
1.IO多路转接之select
1.初识select
- select系统调用是用来让程序监视多个文件描述符的状态变化的
- 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变
- 总结:
- 帮用户进行一次等待多个文件描述符
- 当哪些文件描述符就绪了,select就要通知用户,对应就绪的sock有哪些,然后用户再调用recv/read等接口进行数据读取
2.select()
-
原型:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
-
参数:
- nfds:要监视的最大的文件描述符+1
- readfds,writefds,exceptfds :均为输入输出型参数,分别对应于需要检测的可读/可写/异常文件描述符的集合
- 输入时 :用户告诉内核,你要帮我关心哪些fd的哪一种事件
- bit位的位置表示文件描述符值,bit位的内容表示是否关心(1/0)
- 输出时 :内核告诉用户,我所关心的fd中,哪些fd上的哪类事件已经就绪了
- bit位的位置表示文件描述符值,bit位的内容表示是否就绪(1/0)
- 用户和内核都会修改同一个位图结构,这些参数用一次之后,一定要进行重新设定
- 输入时 :用户告诉内核,你要帮我关心哪些fd的哪一种事件
- timeout :用来设置select()的等待时间,其取值
- nullptr:阻塞等待
- 0:非阻塞等待
- 特定时间值:如果在指定的时间段里没有事件发生,select()将超时返回
- 如果等待时间内,有fd就绪,会怎样呢?
- 呈现输出型,存放距离下一次timeout剩余多长时间
-
返回值:
- 执行成功则返回文件描述词状态已改变的个数
- 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回
- 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的值变成不可预测
-
错误码可能值:
- EBADF:文件描述词为无效的或该文件已关闭
- EINTR:此调用被信号所中断
- EINVAL:参数n为负值
- ENOMEM:核心内存不足
3.关于fd_set结构
cpp
/* The fd_set member is required to be an array of longs. */
typedef long int __fd_mask;
typedef struct
{
/* XPG4.2 requires this member name. Otherwise avoid the name
from the global namespace. */
#ifdef __USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
} fd_set;
-
其实这个结构就是一个整数数组,更严格的说,是一个**"位图",使用位图中对应的bit位来表示要监视的文件描述符**
-
提供了一组操作fd_set的接口, 来比较方便的操作位图
cppvoid FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd的bit位 int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd的but位是否为真 void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的bit位 void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部bit位
4.关于timeval结构
-
timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的文件描述符没有事件发生则函数返回,返回值为0
cppstruct timeval { __time_t tv_sec; /* Seconds. */ __suseconds_t tv_usec; /* Microseconds. */ };
5.理解select执行过程
- 理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节
- fd_set中的每一bit可以对应一个文件描述符fd,则1字节长的fd_set最大可以对应8个fd.
- 执行fd_set set; FD_ZERO(&set);
- 则set用位表示是0000,0000
- 若fd=5,执行FD_SET(fd, &set);
- set变为0001,0000(第5个bit位置为1)
- 若再加入fd=2,fd=1
- 则set变为0001,0011
- 执行**select(6, &set, nullptr, nullptr, nullptr)**阻塞等待
- 若fd=1,fd=2上都发生可读事件,则select返回
- 此时set变为0000,0011
- **注意:**没有事件发生的fd=5被清空
- 执行fd_set set; FD_ZERO(&set);
6.select就绪条件
- 读就绪
- socket内核中,接收缓冲区中的字节数,大于等于低水位标记SO_RCVLOWAT
- 此时可以无阻塞的读该文件描述符,并且返回值大于0
- socket TCP通信中,对端关闭连接,此时对该socket读,则返回0
- 监听的socket上有新的连接请求
- socket上有未处理的错误
- socket内核中,接收缓冲区中的字节数,大于等于低水位标记SO_RCVLOWAT
- 写就绪
- socket内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小),大于等于低水位标记SO_SNDLOWAT
- 此时可以无阻塞的写,并且返回值大于0
- socket的写操作被关闭(close或者shutdown),对一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号
- socket使用非阻塞connect连接成功或失败之后
- socket上有未读取的错误
- socket内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小),大于等于低水位标记SO_SNDLOWAT
7.select特点
- 可监控的文件描述符个数取决与sizeof(fd_set)的值
- 我虚拟机上sizeof(fd_set)=128,每bit表示一个文件描述符,则支持的最大文件描述符是128*8=1024
- 将fd加入select监控集的同时,还要再使用一个第三方数组用来保存所有的合法fd
- 用于在select返回后,array作为源数据和fd_set进行FD_ISSET判断
- select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd并逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数
8.select优点(任何一个多路转接方案,都具备)
- 效率高
- 应用场景:有大量的链接,但是只有少量是活跃的,节省资源
9.select缺点
- 为了维护第三方数组,select服务器会充满大量遍历,OS底层帮用户关心fd的时候,也要遍历
- 每一次都要对select输出参数进行重新设定
- 能够同时管理的fd的个数是有上限的 <-- fd_set大小是被固定了的
- 因为几乎每一个参数都是输入输出型的,select一定会频繁地进行用户到内核,内核到用户的参数数据拷贝
- 编码比较复杂
10.select的一般编写代码的模式
cpp
while(true)
{
// 1.遍历数组,更新出最大值
// 2.遍历数组,添加所有需要关心的fd到fd_set位图中
// 3.调用select进行事件检测
// 4.遍历数组,找到就绪的事件,根据就绪的事件,完成对应的动作
// a.Accepter b.recver
}
11.思考 && 问题
- 如何看待_listensock?
- 获取新连接,依然把它看成IO,如果没有连接到来,就阻塞
- 参数影响编码模式
- nfds:随着获取的sock越来越多,添加到select的sock越来越多,注定了nfds每一次都可能要变化,需要对它动态计算
- readfds/writefds/exceptfds:都是输入输出型参数,输入输出不一定是一样的,所以注定每次都要对其进行重新添加
- timeout:输入输出型参数,每一次都要进行重置,前提是需要的话
- 1,2 --> 注定必须自己将合法的fd单独全部保存起来,以支持:a.更新最大fd b.更新位图结构
2.IO多路转接之poll
1.poll()
- 原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 参数:
- fds:是一个poll()监听的结构的数组,每一个元素中,包含了三部分内容:文件描述符,监听的事件集合,返回的事件集合
- nfds:表示fds数组的长度
- **timeout:
- 表示poll()的超时时间,单位是毫秒
- 设置为0,表示非阻塞等待
- 设置为-1,表示阻塞等待
- 返回值 :
- 执行成功则返回文件描述词状态已改变的个数
- 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回
- 当有错误发生时则返回-1,错误原因存于errno
2.pollfd结构
cpp
struct pollfd
{
int fd; /* File descriptor to poll. */
short int events; /* Types of events poller cares about. */
short int revents; /* Types of events that actually occurred. */
};
- events****和revents的取值
事件 | 描述 | 是否可作为输入 | 是否可作为输出 |
---|---|---|---|
POLLIN | 数据(包括普通数据和优先数据)可读 | 是 | 是 |
POLLRDNORM | 普通数据可读 | 是 | 是 |
POLLRDBAND | 优先级带数据可读(Linux不支持) | 是 | 是 |
POLLPRI | 高优先级数据可读,比如TCP带外数据 | 是 | 是 |
POLLOUT | 数据(包括普通数据和优先数据)可写 | 是 | 是 |
POLLWRNORM | 普通数据可写 | 是 | 是 |
POLLRDHUP | TCP****连接被对方关闭,或者对方关闭了写操作,它由GNU引入 | 是 | 是 |
POLLERR | 错误 | 否 | 是 |
POLLHUP | 挂起,比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件 | 否 | 是 |
POLLNVAL | 文件描述符没有打开 | 否 | 是 |
3.poll就绪条件
- 同select
4.poll的优点
- 效率高
- 应用场景:有大量的链接,但是只有少量是活跃的,节省资源
- 输入输出参数是分离的,不需要进行大量的重置
- 没有可以管理的fd最大数量限制
5.poll缺点
- poll依旧需要不少的遍历,在用户层检测事件就绪、内核检测fd就绪,都需要大量遍历
- 且用户还是需要自己维护第三方数组
- poll需要内核到用户的拷贝 -- 少不了的
- poll的代码结构依然比较复杂 -- 比select容易