IO模型与并发服务器

发布时间:2026/7/23 9:35:37
IO模型与并发服务器 阻塞IO用户层charbuf[1024];recv(fd,buf,sizeof(0),0);//程序卡在这里等待数据内核1.检查socket接收缓冲区是否有数据2.若无数据将进程状态设为TASK_INTERRUPTIBLE(可中断睡眠)进程从CPU运行队列移出触发调度器切换其他进程运行3.数据到达网卡触发硬件中断→内核将数据拷贝到接收缓冲区唤醒进程(状态改为TASK_RUNNING)重新加入运行队列非阻塞IO用户层函数原型int fcntl(int fd, int cmd, .../* arg */);fcntl(fd,F_SETFL,O_NONBLOCK);//设置非阻塞标志while(1){intnrecv(fd,buf,sizeof(buf),0);if(n0)break;//数据就绪if(errno!EAGAIN)exit(1);//错误处理usleep(1000);//避免CPU占满}内核1.检查接收缓冲区无论是否就绪都立即返回结果2.无数据时返回EAGAIN错误码进程继续执行(不进入睡眠)IO多路复用IO多路复用是一种单线程或单进程管理多个描述符(如套接字)的技术核心是通过系统调用监视多个IO操作的状态当某个IO操作就绪(可读、可写或发生异常)时通知应用程序进行处理为什么需要多路复用1.提高并发性能2.非阻塞IO协作3.高效管理大量连接select函数头文件#includesys/socket.h#includesys/types.h函数原型intselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);参数nfds要监视的最大的文件描述符1readfds要监视的读文件描述符集合如果不关心可以传NULLwritefds要监视的写文件描述符集合如果不关心可以传NULLexceptfds要监视的异常文件描述符集合如果不关心可以传NULLtimeout超过时间如果是NULL表示永久阻塞 返回值成功返回就绪的文件描述符的个数失败返回-1(并重置错误码)poll函数头文件#includesys/socket.h#includesys/types.h函数原型intpoll(structpollfd*fds,nfds_tnfds,inttimeout);参数fds一个指向pollfd结构体数组的指针它描述了要监视的文件描述符及其事件 nfdsfds数组中有效描述符元素的数量 timeout是等待时间单位为毫秒 返回值成功返回结构体中revents域不为0的的文件描述符的个数 如果在超时前没有任何事件发生poll()返回0失败返回-1(并重置错误码)服务器模型服务器模型有两种循环服务器、并发服务器循环服务器循环服务器在同一时刻只能响应一个客户端的请求并发服务器并发服务器在同一时刻可以响应多个客户端的请求TCP服务器默认的就是一个循环服务器因为有两个阻塞的函数accept和reve之间互相影响UDP服务器默认的就是一个并发服务器因为只有一个阻塞的函数recvfrom多线程并发服务器实现原理主线程负责接听新连接(accept)子线程每个线程处理一个连接的读写recv()/send()流程多进程并发服务器实现原理fork()模型1.主进程监听连接子进程处理请求2.父子进程完全独立崩溃互不影响预fork()优化1.启动时预先创建多个子进程(类似Apache)2.通过共享监听Socket(SO_REUSEPORT)实现负载均衡主进程1.创建监听Socket→bind()→listen()2.while(1){accept()接收新连接→fork()创建子进程处理连接}子进程1.close()关闭监听Socket(继承自父进程)2.recv()/send()处理客户端请求3.exit()退出IO多路复用并发服务器主线程1.创建监听Socket→bind()→listen()2.初始化fd_set集合将监听Socket加入集合3.while(1){select()监听所有fd→返回就绪的fd数量for(每个就绪的fd){if(是监听Socket)→accept()新连接并加入fd_setelse→recv()/send()处理数据}}pollfd结构体struct pollfd{int fd; //文件描述符short events; //等待的事件short revents; //实际发生的事件};事件常值说明**读事件**POLLIN普通或优先带数据可读POLLPRI高优先级数据可读**写事件**POLLOUT普通或优先带数据可写POLLWRNORM高优先级数据可写**错误事件**POLLERR发生错误POLLHUP发生挂起POLLNVAL描述不是打开的文件简述五种IO模型并说明是同步操作还是异步操作阻塞式I/O是最流行的I/O模型默认的情况下所有套接字都是阻塞的非阻塞式I/O设置成非阻塞是通知进程当所请求的I/O操作非得把本进程置于休眠状态才能完成时则不要置于休眠状态而是返回一个错误I/O多路复用同时阻塞监听多个I/O操作任意一个或多个触发后都会继续执行信号驱动式I/O内核在描述符就绪时会发送SIGIO信号通知异步I/O给内核传递描述符缓冲区指针、缓冲区大小和文件偏移并告知内核在操作完成后如何通知我们同步I/O操作指导致阻塞进程直到I/O操作完成的操作异步I/O操作不会导致请求阻塞。上述五种除了异步I/O外的其他四种都是同步I/O简述三种IO多路复用方法以及各自的特点select(最古老) 时间复杂度O(n) 触发水平触发模式 LT机制通过一个固定大小的位掩码(位图)来监控文件描述符集合每次调用需将集合从用户态全量拷贝到内核态内核暴力遍历所有fd检查状态。特点select支持的文件描述符最多是1024随fd增多性能线性下降每次遍历后需要重置集合。poll(对select的改进) 时间复杂度O(n) 触发水平触发模式 LT机制采用动态数组struct pollfd同样需全量拷贝和内核遍历特点无最大连接数限制但高并发下性能仍随fd数量线性下降用户态和内核态拷贝开销依然存在epoll(Linux高性能方案) 时间复杂度O(1) 触发水平/边沿触发模式 LT/ET机制内核维护红黑树管理fd通过回调机制(事件就绪时主动加入就绪链表 )调用epoll_wait仅返回就绪fd且采用内存映射(mmap)减少拷贝。特点水平触发(LT默认)和边沿触发(ET)两种模式海量连接下性能稳定(仅活跃连接数相关)是Nginx,Redis等高性能服务器的首选水平触发(LT,Level-Triggered)定义当文件描述符(fd)处于就绪状态(比如接收缓冲区有数据可读)时内核就会一直返回该fd直到你不再满足就绪条件(比如把数据读完)特点通知特点只要缓冲区还有数据epoll_wait每次都会返回该FD重复提醒找到你处理完。编程难度简单。允许每次只读一部分不用担心漏数据适合传统阻塞编程。性能开销频繁就绪时可能多次唤醒进程但内核遍历成本可控。适用场景通用型大多数业务服务器首选(如Redis)默认模式稳妥可靠。边沿触发(ET,Edge-Triggered)定义仅当fd的状态发生变化(从不可读变为可读或从不可写变为可写)的那一瞬间内核才返回该fd。此后即使缓冲区仍有数据只要状态不再变化内核也不再通知。特点通知特点仅当fd状态发生变化的瞬间(如无数据→有数据)通知一次之后即使有残留数据也不再提醒。编程难度苛刻。必须用非阻塞fdwhile循环读至EAGAIN否则残留数据会静默丢失导致响应超时。性能开销极高。大幅减少无效唤醒次数高并发下CPU和吞吐量表现最优(Nginx的看家本领)适用场景极致性能场景需处理海量连接且对延迟敏感(如游戏网关、高频交易)