网络IO模型

理解网络IO模型 首先理解什么是socket 网络数据是如何到达并且被处理的

网络IO基础知识

Socket是什么

操作系统需要识别网卡接收的数据 并且将数据准确的给到指定的进程 如何把数据准确地给到对应的进程 就是socket要解决的问题

Socket是应用层和传输层中间的抽象 供应用层调用实现进程在网络中的通信

有了Socket做结构支持 应用层就可以跨设备 在网络中实现通信了

服务端如何处理网络请求

  • 服务端和客户端建立连接
  • 内核等待数据到达 通过DMA将数据写入到内核内存 CPU将数据从内核内存写入到socket缓存区
  • 进程将socket缓冲区内存拷贝到用户内存 进行处理

三种网络IO模型

BIO

BIO即阻塞IO

进程调用recv后 如果有数据就返回 如果没有 就会加入到socket的阻塞队列 线程进入阻塞状态 等待被唤醒 然后将socket缓冲区数据拷贝到用户内存 进行消息处理

如图所示:

存在什么问题呢

调用recv系统调用后,线程就会进入阻塞状态 在高并发情况下 需要为每个socket分配一个线程

此时就会导致:

1)线程数量多 占用内存空间

2)线程上下文切换频繁 导致CPU利用率低

NIO

NIO即非阻塞IO

进程调用Recv后 如果发送socket内无数据 就会返回错误 内核会理解答复 如果没有数据 则继续轮询

当线程数量非常多的时候 会导致

1)内核态和用户态来回切换

2)线程数量多 同样占用大量内存空间

在BIO和NIO中 存在着同样的问题 一个线程 只能监听一个socket 那么怎么去解决这个问题呢

IO多路复用方案给出了解决方案 通过一个线程去同时处理多个socket 当socket有数据 再来通知用户进程

IO多路复用

一个线程 如何去监听多个Socket

在Linux中 IO多路复用提供了多种实现 select poll epoll

我们先讲select

BIO就像是有每个顾客有一个取餐口 每个取餐口都需要一个店员等待食物做好 然后提醒顾客拿走 每个顾客需要去取餐口拿自己的食物 如果没有食物 那么就得在那里一直等

select就像是一个店员管理号所有食物的取餐情况 给顾客一个取餐玲 当有食物做好了 就通知顾客来拿

socket的思想:

线程阻塞 等待socket数据到达 唤醒线程 线程从被监听的socket集合中遍历 找到可读或者可写事件

socket的实现:

首先 将待监听的socket的fd放入一个bitmap(文件描述符列表 采用位图实现)

在调用socket后 将待监听的socket的fd传入一个BitMap中 将线程从工作线程队列移出 并且把线程加入所有待监听的socket的等待队列

当socket有数据到达 中断程序唤醒线程 把线程从所有socket的等待队列中移除 并且加入工作线程队列

然后 线程就知道现在有socket接收到了数据 然后就遍历socket列表 找到就绪的socket

显而易见 有几个问题

  • 每次调用select都需要把线程加入每个socket的等待队列 每次唤醒都需要把线程从每个socket的等待队列中拿出 加入工作队列
  • 每次进程被唤醒后处理事件时 无法知道是哪个socket接收到了数据 需要遍历所有socket 才能知道有哪些socket接收到了数据

怎么去解决以上两个问题呢

epoll

epoll相比select的提升

1)将维护等待队列和阻塞进程分为两个部分 避免每次调用都去维护等待队列

在select中 每次都要创建一个socket fd集合 并且遍历这些fd 将进程添加到socket的阻塞队列中 线程进入阻塞状态

而在epoll 将这一操作分为了epoll_ctl 和epoll_wait 将维护socket集合 和将进程阻塞分为两个步骤

避免每次系统调用进入阻塞状态等待消息队列到来时 都需要去创建socket_fd集合

2)通过维护就绪列表 让进程被唤醒时知道哪些socket有消息 减少遍历

epoll减少遍历的本质

select把整个socket fd集合返回给用户程序 用户程序去遍历整个集合 才能知道有哪些socket接收到了数据 而epoll只会把接收到的socket fd返回给用户程序 用户程序不用自己去遍历查询 也就是维护一个就绪列表epfd

实现

创建一个epoll对象 即创建一个eventpoll

怎么去维护需要监控的对象

通过epoll_ctl 维护一个被监听的socket fd集合 这个集合使用红黑树实现 用来实现高效的增删改查

怎么接收数据

当接收数据的时候 会在eventpoll的就绪列表添加该socket fd

当程序执行到epoll_wait时 如果eventpoll的就绪列表有socket 那么就直接返回

如果eventpoll的就绪列表为空 那么进程进入阻塞状态 加入到eventpoll的等待队列 等待被唤醒

eventpoll相当于socket和进程之间的中介 socket的数据接收不影响进程的状态

而是通过修改eventpoll的就绪列表来改变进程状态

阻塞和唤醒线程

当进程执行到epoll_wait时 且就绪队列没有socket时 进程进入阻塞状态 加入event poll对象的等待队列

当有数据到达socket 中断程序将socket加入就绪队列 将进程从eventpoll对象从等待队列取出 唤醒进程

小结

BIO和NIO中 每个连接都需要一个线程去维护 通过recv系统调用阻塞等待数据到达 这占用了太多线程资源 并且多数线程需要频繁地进行线程上下文切换 在阻塞和运行态之间转换 并且CPU需要频繁进行内核态和用户态的转换 那么怎么在减少线程资源占用的情况下 高效地维护socket连接

这就是IO多路复用要解决的问题

在select中 通过维护一个监听的socket fd列表 并且把进程加入到阻塞队列

当socket接收到数据 中断程序将进程唤醒 进程遍历 socket fd列表 找到有消息的socket 进行消息处理

socket存在这两个问题:

  • 每次select调用都需要去维护一个socket列表
  • 每次进程被唤醒时 不知道哪个socket有数据 因此就需要去遍历socket fd集合

epoll通过维护socket队列和阻塞 两个操作分离 ,维护就绪队列避免进程唤醒时遍历所有socket 解决了以上两个问题

epoll的过程:

他通过一个 eventpoll对象维护一个就绪队列和socketfd集合(红黑树)和阻塞队列

当调用 event_ctl 就会去维护对应的socket fd集合

调用event_wait 进程进入阻塞队列 等待被唤醒

当数据到达socket的时候 通过中断程序 将socketfd加入就绪队列 并且唤醒进程

进程通过就绪队列就可以知道哪些socket有事件 此时就可以去遍历处理

如果文章对你有用 点个赞 谢谢ovo!

相关推荐
用户467245132231 小时前
synchronized的"双重人格":静态与非静态方法锁的惊天差异
后端
胡志辉1 小时前
Nginx CVE‑2026‑42945:隐藏18年高危漏洞被曝光(附解决方案)
前端·后端·nginx
BestHeaker1 小时前
CC Switch 全能使用教程
后端·职场和发展·跳槽·学习方法
折哥的程序人生 · 物流技术专研1 小时前
Java面试85题图解版 · 全系列总目录
java·开发语言·后端·面试·职场和发展
海棠Flower未眠1 小时前
Spring Boot 3 + JPA多模块系统对MySQL和DORIS进行多数据源集成实战(荣耀典藏版)
spring boot·后端·mysql
武子康2 小时前
Java-01 深入浅出 MyBatis 入门与核心原理:半自动 ORM 框架详解
java·后端·mybatis
木易 士心2 小时前
Java 跳出多层循环
java·开发语言·后端
神奇小汤圆2 小时前
背了那么久的慢 SQL 八股,不如动手跑一遍 EXPLAIN
后端
ClouGence2 小时前
我们做了个疯狂的决定,把 CloudDM 全部开源了
数据库·后端·mysql