select
select长这样:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
它接受3个文件描述符的数组,分别监听读取(readfds),写入(writefds)和异常(expectfds)事件。那么一个 IO多路复用的代码大概是这样:
struct timeval tv = {.tv_sec = 1, .tv_usec = 0};
ssize_t nbytes;
while(1) {
FD_ZERO(&read_fds);
setnonblocking(fd1);
setnonblocking(fd2);
FD_SET(fd1, &read_fds);
FD_SET(fd2, &read_fds);
// 把要监听的fd拼到一个数组里,而且每次循环都得重来一次...
if (select(FD_SETSIZE, &read_fds, NULL, NULL, &tv) < 0) { // block住,直到有事件到达
perror("select出错了");
exit(EXIT_FAILURE);
}
for (int i = 0; i < FD_SETSIZE; i++) {
if (FD_ISSET(i, &read_fds)) {
/* 检测到第[i]个读取fd已经收到了,这里假设buf总是大于到达的数据,所以可以一次read完 */
if ((nbytes = read(i, buf, sizeof(buf))) >= 0) {
process_data(nbytes, buf);
} else {
perror("读取出错了");
exit(EXIT_FAILURE);
}
}
}
}
首先,为了select需要构造一个fd数组(socket文件描述符,表示“我要监视这些fd是否有IO事件发生,有了就告诉程序处理”。这里为了简化,没有构造要监听写入和异常事件的fd数组)。之后,用select监听了read_fds中的多个socket的读取时间。调用select后,程序会Block住,直到一个事件发生了,或者等到最大1秒钟(tv定义了这个时间长度)就返回。之后,需要遍历所有注册的fd,挨个检查哪个fd有事件到达(FD_ISSET返回true)。如果是,就说明数据已经到达了,可以读取fd了。读取后就可以进行数据的处理。
select有一些发指的缺点:
select能够支持的最大的fd数组的长度是1024。这对要处理高并发的web服务器是不可接受的。- fd数组按照监听的事件分为了3个数组,为了这3个数组要分配3段内存去构造,而且每次调用
select前都要重设它们(因为select会改这3个数组);调用select后,这3数组要从用户态复制一份到内核态;事件到达后,要遍历这3数组。很不爽。 select返回后要挨个遍历fd,找到被“SET”的那些进行处理。这样比较低效。select是无状态的,即每次调用select,内核都要重新检查所有被注册的fd的状态。select返回后,这些状态就被返回了,内核不会记住它们;到了下一次调用,内核依然要重新检查一遍。于是查询的效率很低。

本文详细解读了select函数的工作原理,并剖析其在高并发场景下的不足。通过实例展示了如何优化select的使用,包括fd数组管理、性能瓶颈及替代方案。
https://www.bilibili.com/video/BV1qL411u7eE?spm_id_from=333.999.0.0
1166

被折叠的 条评论
为什么被折叠?



