尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

网络编程卡壳?五种IO模型+select/poll一篇讲透,搞懂网络编程核心,阻塞/多路复用不再懵!

网络编程卡壳?五种IO模型+select/poll一篇讲透,搞懂网络编程核心,阻塞/多路复用不再懵! 文章目录本篇摘要一· 五种IO模型介绍**1. 阻塞IOBlocking IO, BIO****2. 非阻塞IONon-blocking IO, NIO****3. IO多路复用Multiplexed IO****4. 信号驱动IOSignal Driven IO, SIGIO****5. 异步IOAsynchronous IO, AIO**相关概念认识同步通信 vs 异步通信阻塞 vs 非阻塞对比**一句话总结**其他高级IO拓展了解小结二.非阻塞IO之fcntlfcntl 函数基本信息fcntl 函数的 5 种主要功能**通过 fcntl 设置非阻塞模式**基于fcntl函数简单测试测试思路效果展示源码展示三. 多路转接之selectselect相关函数介绍select代码编写测试基于tcp实现echo回显设计思路测试效果源码汇总**select缺点**四.多路转接之pollpoll相关函数介绍**函数原型****核心参数****返回值****核心事件宏events/revents****使用步骤**操作流程poll代码编写测试基于tcp实现echo回显设计思路测试效果源码汇总**特点与适用场景**poll的优点poll的缺点font color#DC3545 size5 face楷体 **典型场景相比epoll落后****与 select 的对比**五.本篇小结本篇摘要本篇将介绍五种IO模型以及对其中多路转接IO的selectpoll进行介绍使用总结等。一· 五种IO模型介绍1. 阻塞IOBlocking IO, BIO核心特点应用程序发起IO操作如读/写后线程会被“卡住”直到IO完成数据到达/发送完毕。工作流程总结简单但低效适合单连接、低并发场景如早期客户端程序所有套接字默认都是阻塞IO。2. 非阻塞IONon-blocking IO, NIO核心特点应用程序发起IO操作后函数立即返回不会阻塞线程但需主动轮询检查数据是否就绪非阻塞IO:如果内核还未将数据准备好,系统调用仍然会直接返回并且返叵EWOULDBLOCK错误码。工作流程总结避免线程阻塞但需频繁轮询浪费CPU适合短连接或对延迟敏感的场景。3. IO多路复用Multiplexed IO核心特点通过一个系统调用监控多个文件描述符FD当任意一个FD就绪数据可读/写时内核通知应用程序处理。代表实现select老、poll改进、epoll高性能Linux专属。工作流程总结用“单线程管多个连接”大幅提升并发能力如Nginx、Redis用epoll。4. 信号驱动IOSignal Driven IO, SIGIO核心特点应用程序通过信号Signal通知内核“监控某个FD”当数据就绪时内核发送信号触发应用程序处理。工作流程总结异步通知但信号可能丢失或延迟实际应用较少仅适合特定场景。5. 异步IOAsynchronous IO, AIO核心特点应用程序发起IO操作后内核全程完成数据拷贝完成后主动通知应用程序无需线程干预。工作流程总结完全异步“发起即忘”效率最高适合高并发、大文件传输等场景如Windows的IOCP。因此对应信号驱动IO以及异步IO区别信号驱动是数据准备好了告诉用户自己调用接口从内核态拷贝到用户态而异步是直接让内核拿到数据后拷贝到用户态完成才进行通知用户。相关概念认识同步通信 vs 异步通信核心区别同步调用者自己发起IO操作全程等待结果返回自己干完才停。异步调用者委托他人执行IO操作完成后收到通知交给别人干自己等通知。本质差异同步IO操作由调用方“亲自执行”需阻塞等待。异步IO操作由其他组件执行调用方仅接收结果通知。阻塞 vs 非阻塞关注点程序在“等待调用结果时的状态”。区别阻塞等待期间当前线程被“挂起”卡住不动直到结果返回才能继续。非阻塞等待期间线程不被“卡住”可继续执行其他任务不干等。对比同步/异步描述“IO操作由谁执行”自己干 vs 别人干。阻塞/非阻塞描述“等待时线程是否被挂起”卡住 vs 不卡住。二者独立同步操作可能阻塞或非阻塞如同步非阻塞需轮询异步操作通常搭配非阻塞避免卡死。一句话总结从“干等”阻塞到“轮询”非阻塞再到“监控多个”多路复用最后到“内核代劳”异步IO模型逐步解决“线程效率”和“并发能力”的矛盾。其他高级IO拓展了解非阻塞 IO调用时不阻塞线程立即返回结果可能未完成需主动轮询或配合其他机制如 I/O 多路复用获取最终结果。记录锁文件锁对文件/数据加锁确保多进程/线程并发访问时的数据一致性如防止同时写入。系统 V 流机制STREAMSUNIX 系统中的一种设备驱动模型支持数据的异步处理和模块化组件现代应用较少可简单理解为“流式数据处理框架”。I/O 多路转接I/O Multiplexing通过select/poll/epoll等机制单个线程监控多个文件描述符的 IO 状态有事件时才触发处理避免轮询浪费资源常用于高并发服务端。readv/writev 函数分散/聚集 I/O允许一次系统调用读写多个内存缓冲区分散写入、聚集读取减少多次拷贝开销提高大块数据传输效率。存储映射 IOmmap将文件直接映射到进程内存空间通过内存操作访问文件避免频繁读写系统调用适合大文件随机访问和进程间共享内存。小结这些高级 IO 主要解决效率、并发控制、资源利用问题常结合使用如epoll 非阻塞 IO mmap适用于高并发、高性能场景。二.非阻塞IO之fcntlfcntl函数基本信息头文件包含unistd.h和fcntl.h函数原型intfcntl(intfd,intcmd,.../* arg */);核心功能通过不同的cmd参数实现对文件描述符的多种控制操作。fcntl函数的 5 种主要功能复制文件描述符cmdF_DUPFD复制已有描述符。获取/设置文件描述符标记cmdF_GETFD或F_SETFD操作描述符的标志如关闭标志FD_CLOEXEC。获取/设置文件状态标记cmdF_GETFL或F_SETFL控制文件读写模式如阻塞/非阻塞。获取/设置异步 I/O 所有权cmdF_GETOWN或F_SETOWN指定接收异步 I/O 信号的进程。获取/设置记录锁cmdF_GETLK、F_SETLK或F_SETLKW对文件区域加锁/解锁。通过fcntl设置非阻塞模式核心步骤以F_SETFL为例使用F_GETFL获取当前文件状态标志返回一个位图。通过按位或运算|添加O_NONBLOCK标志。使用F_SETFL将修改后的标志写回文件描述符。示例set_nonblock函数voidset_nonblock(intfd){intflagsfcntl(fd,F_GETFL,0);// 获取当前标志flags|O_NONBLOCK;// 添加非阻塞标志fcntl(fd,F_SETFL,flags);// 设置回文件描述符}基于fcntl函数简单测试测试思路将标准输入键盘设置为非阻塞模式通过无限循环持续尝试读取输入有数据时输出内容无数据时因EAGAIN/EWOULDBLOCK提示“没有数据”并休眠1秒若被信号中断EINTR则提示后继续循环其他错误则提示错误最终实现非阻塞读取标准输入的功能。效果展示标准输入流没输入就打印没有数据否则打印输入的数据。源码展示#includeiostream#includeunistd.h#includefcntl.husingstd::cout;usingstd::endl;voidset_nonblock(intfd){intflagsfcntl(fd,F_GETFL,0);flags|O_NONBLOCK;fcntl(fd,F_SETFL,flags);}intmain(){set_nonblock(0);while(1){charbuf[1024];intnread(0,buf,sizeof(buf));if(n0){buf[n-1]0;coutbufendl;}elseif(n0){//无论是读取失败还对方的接收缓冲区没有数据最后都是n-1因此三种情况区分coutnendl;if(errnoEINTR){cout被打断了endl;continue;}elseif(errnoEAGAIN||errnoEWOULDBLOCK){sleep(1);cout没有数据endl;//虽然IO效率没有提高可以在等待的时间做自己想做的事情continue;}else{cout出错了endl;continue;}}}return0;}三. 多路转接之selectselect相关函数介绍功能系统提供select函数实现多路复用输入/输出模型用于监视多个文件描述符的状态变化程序会在select处等待直至监视的描述符有状态改变。函数原型intselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);nfds需监视的最大文件描述符值1。readfds/writefds/exceptfds分别对应可读、可写、异常文件描述符的集合。timeout设置等待时间的结构体struct timeval。文件描述符集合操作集合以位图形式存储从右到左第几位代表对应fd值用户通过以下函数操作voidFD_CLR(intfd,fd_set*set);// 清除fd在集合中intFD_ISSET(intfd,fd_set*set);// 检查fd是否在集合中voidFD_SET(intfd,fd_set*set);// 将fd添加到集合中voidFD_ZERO(fd_set*set);// 清空集合若不关心某类事件对应参数可传nullptr。timeout参数结构体含tv_sec秒和tv_usec微秒字段。若为NULL阻塞等待若字段全为0非阻塞立即返回。返回值成功返回状态改变的文件描述符数量。返回0超时时间内无描述符状态改变。返回-1发生错误错误原因存于errno涉及错误码如EBADF无效文件描述符或文件已关闭。EINTR调用被信号中断。EINVAL参数n为负值。ENOMEM内核内存不足。参数 readfds、writefds、exceptfds和 timeout的值可能变为不可预测状态后续代码不应依赖这些值。使用流程select代码编写测试基于tcp实现echo回显设计思路拿着select进行文件描述符的就绪监测(包括istenfd以及给用户开的普通的fd);当select等待到事件就绪就进行为新的连接开辟fd或者对普通fd的读写操作等;这里只检测读事件默认写都是就络的;但是select会更改输入参数导致每次都要动态维护需要监测的fd然后对d集合进行调整;因此这里采取一个数组来动态维护再进行fd集合的填充(新连接来或者有客户退出进行d数组的更新)。测试效果成功连接为新的连接开辟新的fd。普通的fd进行的读事件就绪进行正常读取。客户端退出对应fd的读事件就绪里面被写入了类似文件结尾的标志读取后直接关闭这个fd移除监测集合中对应的位置)。源码汇总点我速看源码select缺点文件描述符监控机制通过fd_set位图实现监控数量由sizeof(fd_set)决定某服务器为512字节支持最大4096个文件描述符位图容量存在上限。select的主要缺点每次调用需手动设置fd集合使用不便需将fd集合从用户态拷贝到内核态fd数量多时开销大内核需遍历所有传入的fd数量多时效率低支持的文件描述符数量上限较低依赖系统实现。局限性中的优势尽管存在上述缺点但其跨平台兼容性好因此仍被广泛使用。四.多路转接之pollpoll解决了select的部分问题主要体现在两方面突破fd数量上限select依赖固定大小数组存储文件描述符fd存在上限如1024poll改用pollfd结构体数组理论上仅受系统资源限制无固定上限。实现输入输出分离poll通过pollfd结构体中的events监听事件和revents就绪事件字段明确区分读写等事件类型无需每次全量遍历检查提升了事件处理的效率和灵活性。poll相关函数介绍poll是 Linux 系统中用于 I/O 多路复用的系统调用用于监控多个文件描述符FD的状态变化避免单线程阻塞等待 I/O。以下是其核心用法总结函数原型#includepoll.hintpoll(structpollfd*fds,nfds_tnfds,inttimeout);核心参数struct pollfd *fds如下#includepoll.hstructpollfd{intfd;shortevents;shortrevents;};监控的文件描述符列表每个元素包含三部分信息fd要监控的文件描述符如套接字、文件等。events感兴趣的事件通过宏定义指定如POLLIN、POLLOUT等。revents实际发生的事件由内核填充调用后检查该字段判断状态。nfds_t nfdsfds数组的长度即监控的文件描述符数量。int timeout超时时间单位毫秒-1永久阻塞直到有事件发生或出错。0立即返回非阻塞轮询。0最多等待指定毫秒数超时返回。返回值 0发生错误需检查errno。 0超时无任何事件发生。 0就绪的文件描述符数量fds中revents非零的元素个数。核心事件宏events/revents使用步骤初始化pollfd数组为每个需要监控的文件描述符创建struct pollfd结构体设置fd和events。structpollfdfds[2];fds[0].fdstdin_fd;// 标准输入fds[0].eventsPOLLIN;// 监控可读事件fds[1].fdsocket_fd;// 套接字fds[1].eventsPOLLIN|POLLOUT;// 监控可读和可写事件调用poll函数intretpoll(fds,2,5000);// 监控 2 个 FD最多等 5 秒处理返回结果若ret 0遍历fds数组检查revents判断哪些 FD 就绪for(inti0;infds;i){if(fds[i].reventsPOLLIN){// 处理可读事件}if(fds[i].reventsPOLLHUP){// 处理连接断开}}若ret 0超时处理。若ret 0错误处理如EINTR表示被信号中断。操作流程poll代码编写测试基于tcp实现echo回显设计思路这里只需要把对应的之前用数组维护的fd集合改成维护这个struct pollfd结构体数组对结构体里面的变量进行初始化更新判断等操作即可。测试效果同上面没啥区别。源码汇总点我速看源码特点与适用场景poll的优点接口设计更灵活不同于select使用三个位图表示fd_setpoll采用pollfd结构体指针结构体内包含要监视的事件和发生的事件避免了select“参数-值”传递方式的局限性使用更方便。文件描述符数量限制宽松poll没有最大数量限制实际受系统资源等影响仅理论上限为数组大小相比select有一定优势select通常有固定上限如1024。poll的缺点性能随监控数量线性下降当监听的文件描述符数量增多时每次调用poll都需要把大量pollfd结构从用户态拷贝到内核态并且poll返回后需轮询整个pollfd数组来获取就绪描述符若大量连接中只有少数就绪效率会显著降低如第10000个fd就绪仍需遍历全部。需手动管理就绪状态poll不自动从就绪列表中移除文件描述符需要开发者手动处理增加了代码复杂度与管理成本。存在不必要的拷贝开销每次调用poll都要将大量pollfd结构在用户态和内核态之间拷贝和select类似当监控数量大时这种拷贝带来的性能损耗不可忽视。典型场景相比epoll落后网络服务器监控多个客户端连接。同时处理文件和网络 I/O 的程序。与select的对比poll使用数组存储 FD无数量上限取决于内存而select使用fd_set通常限制为 1024。poll的事件模型更清晰通过位掩码区分事件select需重置fd_set。通过合理使用poll可以实现高效的 I/O 多路复用避免单线程阻塞导致的性能瓶颈。五.本篇小结本篇带大家简略认识五种常见的IO模型以及多路转接复用的select及poll的接口的使用也看到了有点与缺点基于poll略胜一筹但是还是比不上epoll因此下篇介绍epoll独特之处。
返回列表