云计算百科
云计算领域专业知识百科平台

Linux IO模型与epoll多路复用|TCP高并发服务器原理

前言

开发网络服务器,最核心的难题就是如何高效处理大量的客户端并发连接。传统方案是每来一个客户端就创建一条线程,但连接数量暴涨之后,大量线程会带来巨大的内存开销与CPU调度压力,服务器极易过载。Linux下一共存在4种IO模型,IO多路复用是实现高并发的主流技术,select、poll、epoll都属于多路复用,其中epoll性能最为突出,Nginx、Redis这类高性能服务底层都依赖epoll。本篇梳理4类IO模型,讲解多路复用核心原理以及epoll整套工作逻辑,帮助理解TCP并发服务器底层。

一、TCP并发:多线程模型

最简单的并发思路:每接入一个新客户端,新建一条独立线程专门负责和客户端通信。

  • 优点:业务逻辑直观,编码简单。
  • 缺点:线程拥有独立栈内存,系统能够创建的线程数量存在上限;并发客户端过多,内存与调度压力陡增,服务器存在崩溃风险。
  • 工程优化:通常搭配线程池使用,预先创建好一批线程做资源复用,避免频繁创建销毁线程,缓解性能压力。
  • 局限:线程池只能缓解问题,无法支撑上万规模的海量连接,这时就要依靠IO多路复用。

    二、Linux四种IO模型

    1. 阻塞IO

    当进程发起IO调用,如果没有就绪数据,操作系统直接将进程挂起,让出CPU资源,不会消耗CPU;等到数据准备完成之后,调用才会返回。

    • 优点:CPU利用率高,无数据时不占用CPU。
    • 缺点:一条线程同一时刻只能处理一个文件描述符,想要处理大量连接就需要大量线程。

    2. 非阻塞IO

    把文件描述符设置成非阻塞模式。调用读写接口,没有数据的时候不会阻塞,直接返回错误。应用程序需要不停循环调用系统接口,轮询检查是否有数据到来。

    • 优点:单线程可以管理多个fd。
    • 缺点:轮询会持续占用CPU,即便没有数据也要不断执行代码,CPU占用很高,实际项目很少单独使用。

    3. 信号驱动IO

    属于异步IO的雏形。

  • 将文件描述符设置信号驱动IO,向内核完成注册;
  • 由内核负责监控fd上是否发生IO事件;
  • 事件就绪之后,内核向进程发送SIGIO信号;
  • 用户进程捕获这个信号,在信号处理函数中完成读写处理。
  • 缺点:信号机制不可靠,事件容易丢失;业务写在信号处理函数极易产生bug,几乎不会在正式项目使用。

    4. IO多路复用

    核心思想:单线程,委托内核同时监视一大批文件描述符。只要集合中任意一个fd事件就绪,函数就返回,用户程序再去处理已经就绪的fd。

    代表实现:select、poll、epoll。

    select

    select接收一组文件描述符集合,集合内任意fd产生事件,select就解除阻塞返回。

    select会直接修改传入的fd集合:会清除掉没有事件发生的fd,集合里面只会保留就绪的fd。所以每一轮循环,都必须重新装填fd集合。

    配套操作宏:

    • FD_ZERO:清空整个fd集合
    • FD_SET:把指定fd加入集合
    • FD_CLR:将指定fd从集合移除
    • FD_ISSET:判断fd是否还在就绪集合内

    select函数参数解读:

  • nfds:集合内最大的文件描述符数值再加1;
  • readfds:监听读事件的fd集合;
  • writefds:监听写事件的fd集合;
  • exceptfds:监听异常事件集合;
  • timeout:超时时间,传入NULL代表永久阻塞等待。
  • 返回值含义:

    • 大于0:已经就绪的文件描述符数量;
    • -1:调用出错;
    • 0:超时,全程没有任何事件产生。

    select的固有短板

  • fd集合存在最大数量限制;
  • 每次调用都需要把fd集合从用户态拷贝到内核态;
  • 返回之后,用户程序必须遍历整个集合,自己判断哪一个fd发生事件。
  • 三、epoll:Linux高性能多路复用

    epoll是Linux独有的多路复用实现,解决select的性能缺陷。内核内部维护红黑树,用来存储所有需要监听的文件描述符。一共三个核心接口:epoll_create、epoll_ctl、epoll_wait。

    1. epoll_create

    功能:创建epoll实例,在内核生成事件监听表。

    • size:早期用于提示内核事件表预估大小,新版Linux中该参数已经被忽略。
    • 返回值:成功返回epoll自身的文件描述符epfd;失败返回-1。

    2. epoll_ctl

    专门操作epoll内核事件表,实现添加、修改、删除监听事件。

    参数:

  • epfd:epoll实例的fd,epoll_create的返回结果;
  • op:操作类型
    • EPOLL_CTL_ADD:向内核事件表新增fd与监听事件
    • EPOLL_CTL_MOD:修改已经注册fd的监听事件
    • EPOLL_CTL_DEL:从事件表删除fd
  • fd:待操作的文件描述符;
  • event:epoll_event结构体,指定监听什么事件,同时携带自定义用户数据。
  • 返回:成功返回0,失败返回-1。

    epoll_event结构:内部包含事件标记events,以及联合体data。data.fd用来保存文件描述符,epoll_wait返回事件的时候,会原样把data返回给用户,业务依靠这个fd定位就绪连接。

    常用事件标志:

    • EPOLLIN:读事件,接收缓冲区有可读数据;新客户端连接到来也会触发该事件;
    • EPOLLOUT:写事件,发送缓冲区有空余空间,可以向外发送数据;
    • EPOLLET:开启边缘触发模式;默认是水平触发LT。

    重要坑点:调用epoll_ctl添加fd的时候,event.data.fd必须和第三个传入参数fd保持一致,否则epoll_wait拿到错误fd,业务逻辑完全错乱。

    3. epoll_wait

    阻塞等待IO事件,内核将就绪事件拷贝到用户空间数组。

    参数:

  • epfd:epoll实例fd;
  • events:用户数组首地址,内核将就绪事件拷贝到此数组;
  • maxevents:数组最大容量;
  • timeout:超时毫秒,传-1代表永久阻塞等待事件。
  • 返回值:

    • 大于0:本次就绪的fd数量nready,循环只遍历0 ~ nready-1,不要遍历整个数组;
    • -1:接口调用出错;
    • 0:超时,没有任何事件发生。

    四、LT水平触发 与 ET边缘触发

  • LT水平触发(默认模式)

    只要缓冲区还有未读完的数据,每一次epoll_wait都会上报该事件。就算本次没有读完全部数据,下一轮epoll_wait依旧会通知。编码简单,不容易丢失事件。

  • ET边缘触发 EPOLLET

    只有IO状态发生改变的那一瞬间,才通知一次。例如新数据到达,只会触发一次通知;如果本次没有把缓冲区全部读完,剩余数据不会再产生事件通知。

  • ET模式必须把socket设置为非阻塞,循环读取直到读到EAGAIN,一次性读完缓冲区全部内容。性能更高,但编码复杂度上升。

    五、epoll TCP服务器完整业务流程

  • 创建socket,bind绑定地址、listen监听,得到监听sockfd;
  • 调用epoll_create创建epoll实例,拿到epfd;
  • 组装epoll_event,调用epoll_ctl(EPOLL_CTL_ADD),把监听sockfd加入内核事件表;
  • 循环调用epoll_wait阻塞等待事件到来;
  • epoll_wait返回就绪数量nready,循环遍历就绪事件:
    • 如果就绪fd是监听sockfd:调用accept获取新客户端confd,将confd加入epoll监听;
    • 如果是普通客户端confd:执行read读取数据,处理业务逻辑;
    • 捕获EPOLLHUP、EPOLLERR事件:代表客户端断开连接,把fd从epoll删除,调用close关闭文件描述符。
  • 六、开发高频踩坑总结

  • 同一个fd重复执行EPOLL_CTL_ADD,报错File exists,不允许重复添加;
  • event.data.fd和epoll_ctl第三个参数fd不一致,拿到错误文件描述符;
  • for循环遍历整个maxevents数组,而不是只遍历nready个就绪事件,读取到数组内的垃圾随机值;
  • ET边缘触发下socket忘记设置非阻塞,缓冲区数据读取不干净;
  • 客户端断开没有捕获EPOLLHUP,没有从epoll移除fd,造成文件描述符泄露。
  • 小结

    IO多路复用本质是单线程委托内核批量监控大量文件描述符。

    • select实现简单,但是性能上限低,存在fd数量限制;
    • epoll依靠内核红黑树、事件回调机制,在海量连接场景性能优异,是Linux网络开发必须掌握的技术。

    注意:多路复用只是优化IO等待过程,内部read/write依旧属于同步IO,并不是真正的异步IO。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux IO模型与epoll多路复用|TCP高并发服务器原理
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!