面试必会系列 - 5.1 网络BIO、NIO、epoll,同步/异步模型、阻塞/非阻塞模型,你能分清吗?

本文已收录至 Github(MD-Notes),若博客中图片模糊或打不开,可以来我的 Github 仓库,包含了完整图文https://github.com/HanquanHq/MD-Notes,涵盖了互联网大厂面试必问的知识点,讲解透彻,长期更新中,欢迎一起学习探讨 ~

更多内容,可以访问:

面试必会系列专栏https://blog.csdn.net/sinat_42483341/category_10300357.html
操作系统系列专栏https://blog.csdn.net/sinat_42483341/category_10519484.html


BIO,NIO,epoll

同步/异步模型、阻塞/非阻塞模型

同步:当一个同步调用发出后,调用者要一直等待返回结果。通知后,才能进行后续的执行。
异步:当一个异步过程调用发出后,调用者不能立刻得到返回结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
阻塞:是指调用结果返回前,当前线程会被挂起,即阻塞。
非阻塞:是指即使调用结果没返回,也不会阻塞当前线程。

比喻:
小Q去钓鱼,抛完线后就傻傻的看着有没有动静,有则拉杆(同步阻塞)
小Q去钓鱼,拿鱼网捞一下,有没有鱼立即知道,不用等,直接就捞(同步非阻塞)
小Q去钓鱼,这个鱼缸比较牛皮,扔了后自己就打王者荣耀去了,因为鱼上钩了这个鱼缸带的报警器会通知我。这样实现异步(异步非阻塞)

同步、异步

客户端在请求数据的过程中,能否做其他事情

  • 同步模型:程序自己读取,程序在 IO 上的模型就叫 同步模型
  • 异步模型:程序把读取的过程交给内核,自己做自己的事情,叫 异步模型

同步的意思是:客户端与服务端 相同步调。就是说 服务端 没有把数据给 客户端 之前,客户端什么都不能做。它们做同样一件事情,就是说它们有相同步调,即同步。

阻塞、非阻塞

客户端与服务端是否从头到尾始终都有一个持续连接,以至于 占用了通道,不让其他客户端成功连接。

阻塞的意思是:客户端与服务端之间是否始终有个东西占据着它们中间的通道。就是说 客户端与服务端中间,始终有一个连接。导致其他客户端不能继续建立新通道连接服务器。

那么,BIO NIO AIO 就可以简单的理解为:
  • BIO(同步阻塞):客户端在请求数据的过程中,保持一个连接(阻塞)不能做其他事情(同步)
  • NIO(同步非阻塞):客户端在请求数据的过程中,不用保持一个连接(非阻塞),不能做其他事情。(许多个小连接,也就是轮询)
  • AIO(异步非阻塞):客户端在请求数据的过程中,不用保持一个连接,可以做其他事情。(客户端做其他事情,数据来了等服务端来通知。)

BIO 同步阻塞

  • 阻塞式的 ServerSocket

    • 阻塞地等待客户端的连接,连接后抛出一个线程
    • 阻塞地等待客户端发送消息
    • C10K 问题,如果有1万个连接,就要抛出1万个线程

NIO 同步非阻塞

Java:New IO 新的 IO 包

OS:Non-Blocking IO 非阻塞的 IO

  • 非阻塞式的 ServerSocketChannel

    • ss.configBlocking(false) 设置非阻塞
    • accept(3,中空转,要么返回client的描述符,要么返回-1,如果拿到了新的连接,调用clientList.add()
  • 缺点

    • ByteBuffer只有一个指针,用起来有很多坑

    • NIO的存在的问题:C10K问题

      • 放大:C10K当并发量很大的时候,需要遍历的文件描述符会很多,**每循环 **一次,都要调用 10K 次recv 系统调用(复杂度O(n)),进行用户态到内核态的切换,性能损耗大。
      • 缩小:你调用了 那么多次 recv 系统调用,但是如果 C10K 只有 1 个 Client 发来了数据,只有 1 次系统调用是有效的,剩余 n-1 次的监听都是无效的。我们希望,能够有一种方式,只需要调用 有效recv 系统调用 就可以。

多路复用器 select poll epoll 同步非阻塞

OS 提供的 多路复用器select, pool, epoll, kqueue

select,poll 的缺点是,每次你调用的时候,都需要将所有的文件描述符的集合 fds 作为参数传递给函数,而 epoll 的优势是在内核中开辟了一个空间(调用的是 epoll_create)

Java 把所有的多路复用器封装成了 Selector 类

可以在启动时,指定使用哪种多路复用器(默认优先选择 epoll),注意 windows 上是没有 epoll 的。

-Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.EPollSelectorProvider
  • 允许程序去调用内核,来监控更多的客户端,直到一个或多个文件描述符是可用的状态,再返回。减少了用户态、内核态的无用切换。

  • Linux 内核提供的 select 多路复用器,会返回给程序一个 list,告诉程序 哪些 fd 是可以读/写的状态,然后程序要自己读取,这是 IO 同步模型。

    只有当 read 系统调用不需要你程序自己去做的时候,才属于异步的 IO,目前只有 windows iocp 实现了。

  • 缺点

    • 如果有很多长连接,内核每次都要给程序传递很多连接对象

epoll 同步非阻塞

Nginx, Redis(C写的) 底层都使用了 epoll

strace 命令用来监控指定的程序(字节码)发生了哪些系统调用

  • epoll 也是多路复用器,但是它有一个 存放结果集的链表它与 select / poll 的区别如下:

    • select:应用程什么时候调用 select,内核就什么时候遍历所有的文件描述符,修正 fd 的状态。

    • epoll:应用程序在内核的 红黑树 中存放过一些 fd,那么,内核基于中断处理完 fd 的 buffer/状态 之后,继续把有状态的 fd 拷贝到链表中。

      即便应用程序不调用内核,内核也会随着中断,完成所有fd状态的设置。这样,程序调用epoll_wait可以及时去取链表包含中有状态的 fd 的结果集。规避了对于文件描述符的全量遍历。

      拿到 fd 的结果集之后,程序需要自己取处理 accept / recv 等系统调用的过程。所以epoll_wait依然是同步模型。

  • Epoll 是 Event poll,把有数据这个事件通知给程序,它不负责读取 IO,还需要程序自己取读取数据

  • man epoll 帮助文档:

    The epoll API performs a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them. The epoll API can be used either as an edge-triggered(边缘触发) or a level-triggered(条件触发) interface and scales well to large numbers of watched file descriptors(可以很好地扩展到大量监视文件描述符). The following system calls are provided to create and manage an epoll instance. [ 注:这里 (2) 的意思是 2 类系统调用。类似地,还有 7 类杂项]

    • epoll_create creates an epoll instance and returns a file descriptor referring to that instance.

      创建成功之后,返回一个 fd 文件描述符 例如 fd6。实际上是在内核开辟一块空间,里面存放红黑树

    • epoll_ctl, This system call performs control operations on the epoll(7) instance referred to by the file descriptor epfd. It requests that the operation op be performed for the target file descriptor, fd.

      int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
      // epoll_create = 4
      // epoll_ctl(4, add, 7, accept)
      

      例如,在文件描述符fd6中,使用 EPOLL_CTL_ADD 添加服务器用于 listen 的文件描述符

      • 此系统调用对文件描述符 epfd 引用的 epoll(7) 实例执行控制操作,int op 可选参数:EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL
    • epoll_wait waits for I/O events, blocking the calling thread if no events are currently available.

      **epoll_wait 不传递 fds,不触发内核遍历。**你的程序需要写一个死循环,一直调用 epoll_wait,可以设置阻塞,或者非阻塞

  • 早期在没有上述三个系统调用的时候,需要应用程序调用 mmap 来实现两端的内存共享提速,后期在 2.6 内核版本之后,提供了这些系统调用,就不需要 mmap 这种实现方式了

边沿触发

只要缓冲区还有东西可以读,只要你调用了epoll_wait函数,它就会继续通知你

水平触发

就像高低电平一样,只有从高电平到低电平或者低电平到高电平时才会通知我们。只有客户端再次向服务器端发送数据时,epoll_wait 才会再返回给你

AIO

  • AIO 是异步的模型,直接aio_read,立即返回,并不阻塞进程,然后由内核自行复制数据给 aio_read,这里的 aio_read 是不阻塞进程的,它直接读内核,不管是否准备好。
  • 使用的是 callback / hook / templateMethod 回调,是基于事件模型的 IO
  • Netty封装的是NIO,不是AIO
    • AIO 只有 Window 支持(内核中使用CompletionPort完成端口)
    • 在 Linux 上的 AIO 只不过是对 NIO 的封装而已(是基于epoll 的轮询)
关于 Linux 上的 同步/异步 IO

“停止使用这种只适用于特殊情况的垃圾,让所有人都在乎的系统核心尽其所能地运行好其基本的性能”,就是说,你cpu做你cpu该做的事,别净整些没用的。

就像系统调用那样,尽管Linux上建立一个新的系统调用非常容易,但并不提倡每出现一种新的抽象就简单的加入一个新的系统调用。这使得它的系统调用接口简洁得令人叹为观止(2.6版本338个),新系统调用增加频率很低也反映出它是一个相对较稳定并且功能已经较为完善的操作系统。

这也是为什么以前Linux版本的主内核树中没有类似于windows 上 AIO这样的通用的内核异步IO处理方案(在Linux 上的AIO只不过是对 NIO 的封装而已),因为不安全,会让内核做的事情太多,容易出bug。windows敢于这么做,是因为它的市场比较广,一方面是用户市场,一方面是服务器市场,况且windows比较注重用户市场,所以敢于把内核做的胖一些,也是因此虽然现在已经win10了,但是蓝屏啊,死机啊,挂机啊这些问题也还是会出现。

现在 Linux 对异步 IO 也开始上心了,根据 https://www.infoq.cn/article/zPhDatkQx5OPKd9J53mX Linux内核发展史,2019.5.5 发布的 5.1 版本的内核,包括用于异步 I/O 的高性能接口 io_uring, 是 Linux 中最新的原生异步 I/O 实现,是良好的 epoll 替代品。

老周说有个 Linux 6.x 版本之后,Linux 有了统一的异步IO实现

参考文献:

异步IO一直是 Linux 系统的痛。Linux 很早就有 POSIX AIO 这套异步IO实现,但它是在用户空间自己开用户线程模拟的,效率极其低下。后来在 Linux 2.6 引入了真正的内核级别支持的异步IO实现(Linux aio),但是它只支持 Direct IO,只支持磁盘文件读写,而且对文件大小还有限制,总之各种麻烦。到目前为止(2019年5月),libuv 还是在用pthread+preadv的形式实现异步IO。

随着 Linux 5.1 的发布,Linux 终于有了自己好用的异步IO实现,并且支持大多数文件类型(磁盘文件、socket,管道等),这个就是本文的主角:io_uring

作者:CarterLi
链接:https://segmentfault.com/a/1190000019300089

前面的文章说到 io_uring 是 Linux 中最新的原生异步 I/O 实现,实际上 io_uring 也支持 polling,是良好的 epoll 替代品。

作者:CarterLi
链接:https://segmentfault.com/a/1190000019361819?utm_source=tag-newest

Netty

Netty主要用于网络通信。

  • 很多网页游戏的服务器都是用Netty写的。
  • Tomcat,Zookeeper,很多开源分布式的底层也是netty写的。

在这里插入图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值