彻底理解 IO多路复用

您所在的位置:网站首页 如何理解io流 彻底理解 IO多路复用

彻底理解 IO多路复用

2023-03-20 02:45| 来源: 网络整理| 查看: 265

看完下面这些,高频面试题你都会答了吧 目录

1、什么是IO多路复用?2、为什么出现IO多路复用机制?3、IO多路复用的三种实现方式4、select函数接口5、select使用示例6、select缺点7、poll函数接口8、poll使用示例9、poll缺点10、epoll函数接口11、epoll使用示例12、epoll缺点13、epoll LT 与 ET模式的区别14、epoll应用15、select/poll/epoll之间的区别16、IO多路复用完整代码实现17、高频面试题

1、什么是IO多路复用

「定义」

IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出cpu。多路是指网络连接,复用指的是同一个线程2、为什么有IO多路复用机制? 没有IO多路复用机制时,有BIO、NIO两种实现方式,但有一些问题 同步阻塞(BIO)服务端采用单线程,当accept一个请求后,在recv或send调用阻塞时,将无法accept其他请求(必须等上一个请求处recv或send完),无法处理并发// 伪代码描述 while(1) { // accept阻塞 client_fd = accept(listen_fd) fds.append(client_fd) for (fd in fds) { // recv阻塞(会影响上面的accept) if (recv(fd)) { // logic } } } 服务器端采用多线程,当accept一个请求后,开启线程进行recv,可以完成并发处理,但随着请求数增加需要增加系统线程,大量的线程占用很大的内存空间,并且线程切换会带来很大的开销,10000个线程真正发生读写事件的线程数不会超过20%,每次accept都开一个线程也是一种资源浪费// 伪代码描述 while(1) { // accept阻塞 client_fd = accept(listen_fd) // 开启线程read数据(fd增多导致线程数增多) new Thread func() { // recv阻塞(多线程不影响上面的accept) if (recv(fd)) { // logic } } } 同步非阻塞(NIO)服务器端当accept一个请求后,加入fds集合,每次轮询一遍fds集合recv(非阻塞)数据,没有数据则立即返回错误,每次轮询所有fd(包括没有发生读写事件的fd)会很浪费cpusetNonblocking(listen_fd) // 伪代码描述 while(1) { // accept非阻塞(cpu一直忙轮询) client_fd = accept(listen_fd) if (client_fd != null) { // 有人连接 fds.append(client_fd) } else { // 无人连接 } for (fd in fds) { // recv非阻塞 setNonblocking(client_fd) // recv 为非阻塞命令 if (len = recv(fd) && len > 0) { // 有读写数据 // logic } else { 无读写数据 } } } IO多路复用(现在的做法)服务器端采用单线程通过select/epoll等系统调用获取fd列表,遍历有事件的fd进行accept/recv/send,使其能支持更多的并发连接请求fds = [listen_fd] // 伪代码描述 while(1) { // 通过内核获取有读写事件发生的fd,只要有一个则返回,无则阻塞 // 整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,accept/recv是不会阻塞 for (fd in select(fds)) { if (fd == listen_fd) { client_fd = accept(listen_fd) fds.append(client_fd) } elseif (len = recv(fd) && len != -1) { // logic } } } 3、IO多路复用的三种实现方式selectpollepoll4、select函数接口​#include #include #define FD_SETSIZE 1024 #define NFDBITS (8 * sizeof(unsigned long)) #define __FDSET_LONGS (FD_SETSIZE/NFDBITS) // 数据结构 (bitmap) typedef struct { unsigned long fds_bits[__FDSET_LONGS]; } fd_set; // API int select( int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout ) // 返回值就绪描述符的数目 FD_ZERO(int fd, fd_set* fds) // 清空集合 FD_SET(int fd, fd_set* fds) // 将给定的描述符加入集合 FD_ISSET(int fd, fd_set* fds) // 判断指定描述符是否在集合中 FD_CLR(int fd, fd_set* fds) // 将给定的描述符从文件中删除 5、select使用示例int main() { /* * 这里进行一些初始化的设置, * 包括socket建立,地址的设置等, */ fd_set read_fs, write_fs; struct timeval timeout; int max = 0; // 用于记录最大的fd,在轮询中时刻更新即可 // 初始化比特位 FD_ZERO(&read_fs); FD_ZERO(&write_fs); int nfds = 0; // 记录就绪的事件,可以减少遍历的次数 while (1) { // 阻塞获取 // 每次需要把fd从用户态拷贝到内核态 nfds = select(max + 1, &read_fd, &write_fd, NULL, &timeout); // 每次需要遍历所有fd,判断有无读写事件发生 for (int i = 0; i



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3