爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      浅谈NIO和Epoll的实现原理

      首页 知识中心 其他 文章详情页

      浅谈NIO和Epoll的实现原理

      2023-03-08 10:27:24 阅读次数:130

      什么是NIO

        NIO又叫New/Non-blocking IO,这个概念基本人人都听过,但是不一定每个人都懂他它的运行的原理。

        这里我们来探讨这个问题,先用一个例子解释一下BIO到底阻塞了哪里。

      /**
       * 这是一个单线程BIOServer
       * @author endless
       * @create 2020-03-23
       */
      public class BioServerDemo {
      
        public static void main(String[] args) throws IOException {
          // 创建ServerSocket,并绑定端口
          ServerSocket serverSocket = new ServerSocket(9999);
          System.out.println("服务启动成功");
          while (true) {
            Socket socket = serverSocket.accept();
            System.out.println("连接成功");
            System.out.println("准备接收数据");
            byte[] bytes = new byte[1024];
            socket.getInputStream().read(bytes);
            System.out.println("接收到了数据:" + new String(bytes));
          }
        }
      }
      
      /**
       * BIO client
       *
       * @author endless
       * @create 2020-03-23
       */
      public class BioClientDemo {
      
        public static void main(String[] args) throws IOException {
          // 连接Server
          Socket socket = new Socket("127.0.0.1", 9999);
          System.out.println("连接成功");
          Scanner scanner = new Scanner(System.in);
          // 循环等待输入消息
          while (true) {
            String str = scanner.next();
            // 约定退出口令
            if ("exit".equalsIgnoreCase(str)) {
              socket.close();
              System.exit(0);
            }
            socket.getOutputStream().write(str.getBytes());
            socket.getOutputStream().flush();
          }
        }
      }

      先运行Server

      浅谈NIO和Epoll的实现原理

        命令行打印服务启动成功,此时并无客户端连接,所以连接成功并未打印,说明程序被阻塞在了serverSocket.accept()方法

        此时运行Client,Server打印日志连接成功和准备接收数据,此时Client尚未发送数据,Server被阻塞在

      socket.getInputStream().read(bytes)上,因此其他客户端无法进行连接。

      浅谈NIO和Epoll的实现原理

        在Client输入Hello回车,此时Server打印接收到了数据:Hello,说明客户端的连接发送过来数据了,此时服务端线程才解阻塞,在这

      个情况下,这个Server没有办法处理并发,同时期只能处理一个连接。

        那么BIO是如何实现并发呢?答案也很明显,就是使用多线程,我们对Server进行一些小改动。

      /**
       * 这是一个BIOServer
       * @author endless
       * @create 2020-03-23
       */
      public class BioServerDemo {
        public static void main(String[] args) throws IOException {
      
          ServerSocket serverSocket = new ServerSocket(9999);
          System.out.println("服务启动成功");
          while (true) {
            Socket socket = serverSocket.accept();
            new Thread(()->{
              System.out.println("连接成功");
              System.out.println("准备接收数据");
            	byte[] bytes = new byte[1024];
              try {
                socket.getInputStream().read(bytes);
              } catch (IOException e) {
                e.printStackTrace();
              }
              System.out.println("接收到了数据:" + new String(bytes));
            }).start();
          }
      
        }
      
      }

        使用子线程来对接收到的Socket进行处理,这样每个连接都被阻塞在单独的线程上,就可以实现并发访问Server。

        总结:BIO的阻塞有两个地方:accept()和read(),并且BIO的并发只能通过多线程。

        但是这里会有一个问题,就是如果绝大部分的连接都没有进行数据传输,只是建立了连接,这样就会产生很多无效的线程,而线程又

      是非常宝贵的稀缺资源,这样就会白白损失很多的性能,这也是BIO最大的性能瓶颈。

        那能不能只用一个线程就能实现并发并且处理全部的连接呢?是否能设计一个Api,让accept和read不再阻塞,使用一个线程就能处

      理并发连接呢?答案是肯定的,这里就要用到我们的今天的主角NIO了。

        NIO在JDK中被封装在了一个新的类中,我们先来写一个例子,这个例子实现了使用单线程来处理多连接。

       
      /**
       * NIO Server Demo
       *
       * @author endless
       * @create 2020-03-23
       */
      public class NioServerDemo {
      
        // 保存客户端连接
        static List<SocketChannel> channelList = new ArrayList<>();
      
        public static void main(String[] args) throws IOException, InterruptedException {
      
          // 创建NIO ServerSocketChannel
          ServerSocketChannel serverSocket = ServerSocketChannel.open();
          serverSocket.bind(new InetSocketAddress(9998));
          // 设置ServerSocketChannel为非阻塞
          serverSocket.configureBlocking(false);
          System.out.println("服务启动成功");
      
          while (true) {
            SocketChannel socketChannel = serverSocket.accept();
            if (socketChannel != null) { // 如果有客户端进行连接
              System.out.println("连接成功");
              // 设置SocketChannel为非阻塞
              socketChannel.configureBlocking(false);
              // 保存客户端连接在List中
              channelList.add(socketChannel);
            }
            // 遍历连接进行数据读取
            Iterator<SocketChannel> iterator = channelList.iterator();
            while (iterator.hasNext()) {
              SocketChannel o = iterator.next();
              ByteBuffer byteBuffer = ByteBuffer.allocate(128);
              int read = o.read(byteBuffer);
              // 如果有数据,把数据打印出来
              if (read > 0) {
                System.out.println("接收到消息:" + new String(byteBuffer.array()));
              } else if (read == -1) { // 如果客户端断开,把socket从集合中去掉
                iterator.remove();
                System.out.println("客户端断开连接");
              }
            }
          }
        }
      }

      客户端可以复用之前的BIO客户端

      运行NIOServer,Server启动完毕后运行两个Client,各发送一条消息进行测试

      浅谈NIO和Epoll的实现原理

       

      浅谈NIO和Epoll的实现原理

       

      浅谈NIO和Epoll的实现原理

      控制台显示两个连接成功,并且接收到了来自两个客户端的消息,表明Server可以使用单线程处理并发连接,这个Api的原理是什么呢?我们来进一步探究一下。

      我们沿着源码一路往下找,会找到一个无法下载源码的文件

      浅谈NIO和Epoll的实现原理

        这里可以看得出,在Windows系统中,编译后的代码显示直接返回了WindowsSelectorProvider对象,很显然,这个对象是在和windows系统核心中的select方法交互,但是Linux中是不是也是这样呢,我们需要下载一个Linux版本的OpenJDK源码来探究一下。下载

      OpenJDK源码

        下载后解压,Linux源码分布在\openjdk\jdk\src\share和\openjdk\jdk\src\solaris目录中。

      浅谈NIO和Epoll的实现原理

       

        在上图中可以看出,JDK在不同的系统中采用了不同的实现方案,这里使用的是EPollSelectorProvider,说明在Linux中,使用的是EPoll来实现的。

        我们先来看看serverSocket.configureBlocking(false);到底是如何工作的,沿着源码往下找,发现一个本地方法。

      浅谈NIO和Epoll的实现原理

       

        这个本地方法的源码可以在OpenJDK中找到,如下图

      浅谈NIO和Epoll的实现原理

       

        上图红框中的函数就是这个本地方法调用的底层C语言的方法,前面的前缀是根据JNI调用规则添加的,我们知道,在C当中是可以直接调用操作系统的Api的,这个方法调用了fcntl这个命令,把传进来的blocking参数设置到了文件描述符上(文件描述符可以看作是一个对象,Linux中一切皆文件,类似于高级语言中的一切皆对象,任何的数据流转都要通过一个文件描述符来操作)。

      接着看看serverSocket.accept()是如何实现

      浅谈NIO和Epoll的实现原理

       

      上图可以看到,accept方法调用了一个native方法accept0

      浅谈NIO和Epoll的实现原理

       

      这个accept0方法的描述简单翻译一下,就是接受一个新的连接,把给定的文件描述符引用设置为新的Socket,并将isaa[0]设置为套接字的远程地址,成功返回1,不成功返回IOStatus.UNAVAILABLE or IOStatus.INTERRUPTED。

      继续探究一下本地方法的源码

      浅谈NIO和Epoll的实现原理

       

      这里调用了操作系统的accept方法,想知道这个方法文档的同学可以在Linux环境中使用man命令来查看

      浅谈NIO和Epoll的实现原理

       

      man命令可以查看Linux详尽的文档,2表示第二章:系统调用指令,最后加上你想查的指令方法即可

      浅谈NIO和Epoll的实现原理

       

        这里主要看一下返回值,accept返回一个非负数作为socket的描述符,如果失败返回-1。通过这个文件描述符,Java就可以通过native方法调用操作系统的API来在Socket中进行数据的传输。我们的代码用一个List保存了接收到的Socket,相当于保存了Socket的文件描述符,通过一个线程轮询这些文件描述符即可实现数据通信和处理新连接,这样就节约了大量的线程资源,但是大家想一想这种模型还有什么缺陷。

      ……

        是的,如果连接数太多的话,还是会有大量的无效遍历,例如10000个连接中只有1000个连接时有数据的,但是由于其他9000个连接并没有断开,我们还是要每次轮询都遍历一万次,相当于有十分之九的遍历都是无效的,这显然不是一个让人很满意的状态。

        总结:NIO的非阻塞是由操作系统来完成的,SocketChannel与文件描述符一一对应,通过遍历文件描述符来读取数据。

      什么是多路复用器

        上面的例子还不是Java NIO的完全体,仅仅是将原来的同步阻塞IO优化成了同步非阻塞IO,既然还是同步的,就意味着我们每次遍

      历,还是需要对每个Socket进行一次read操作来检查是不是有数据过来,都会调用系统内核的read指令,只不过是把阻塞变成了非阻

      塞,如果无用连接很多的话,那么绝大部分的read指令都是无意义的,这就会占用很多的CPU时间。

        Linux有select、poll和epoll三个解决方案来实现多路复用,其中的select和poll,有点类似于上面的NIOServerDemo程序,他会把

      所有的文件描述符记录在一个数组中,通过在系统内核中遍历来筛选出有数据过来的Socket,只不过是从应用程序中遍历改成了在内核中

      遍历,本质还是一样的。

        Epoll则使用了事件机制,在复用器中注册了一个回调事件,当Socket中有数据过来的时候调用,通知用户处理信息,这样就不需要对

      全部的文件描述符进行轮训了,这就是Epoll对NIO进行的改进。

        我们来探究一下在Java中是如何用Epoll实现NIO事件机制的,先对上面的NIOServerDemo再进行改进。

      /**
       * NIO Selector Server Demo
       *
       * @author  endless
       * @create 2020-03-23
       */
      public class NioSelectorServerDemo {
      
        public static void main(String[] args) throws IOException, InterruptedException {
      
          // 创建NIO ServerSocketChannel
          ServerSocketChannel serverSocket = ServerSocketChannel.open();
          serverSocket.socket().bind(new InetSocketAddress(9998));
          // 设置ServerSocketChannel为非阻塞
          serverSocket.configureBlocking(false);
          // 打开Selector处理Channel,即创建epoll
          Selector selector = Selector.open();
          // 将ServerSocket注册到selector用来接收连接
          serverSocket.register(selector, SelectionKey.OP_ACCEPT);
          System.out.println("服务启动成功");
      
          while (true) {
      
            // 阻塞等待需要处理的事件发生
            selector.select();
      
            // 获取selector中注册的全部事件的 SelectionKey 实例
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
      
            // 遍历SelectionKey对事件进行处理
            while (iterator.hasNext()) {
              SelectionKey key = iterator.next();
              iterator.remove();
              // 如果是OP_ACCEPT事件,则进行连接获取和事件注册
              if (key.isAcceptable()) {
                ServerSocketChannel server = (ServerSocketChannel) key.channel();
                SocketChannel socketChannel = server.accept();
                socketChannel.configureBlocking(false);
                // 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
                socketChannel.register(selector, SelectionKey.OP_READ);
                System.out.println("客户端连接成功");
              }
      
              // 如果是OP_READ事件,则进行读取和打印
              if (key.isReadable()) {
                SocketChannel socketChannel = (SocketChannel) key.channel();
                ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                int read = socketChannel.read(byteBuffer);
                // 如果有数据,把数据打印出来
                if (read > 0) {
                  System.out.println("接收到消息:" + new String(byteBuffer.array()));
                } else if (read == -1) { // 如果客户端断开连接,关闭Socket
                  System.out.println("客户端断开连接");
                  socketChannel.close();
                }
              }
            }
      
          }
      
        }
      
      }

       

       

      这段代码需要关注的点有以下几个方法:

      1. Selector.open()
      2. socketChannel.register()
      3. selector.select()

      接下来就来看看这三个方法究竟做了什么。

      Select.open()

      首先调用了SelectorProvider的openSelector()方法,这个方法返回一个EPollSelectorImpl实例

      EPollSelectorProvider.java

          public AbstractSelector openSelector() throws IOException {
      return new EPollSelectorImpl(this);
      }

      EPollSelectorImpl的构造方法中new了一个EPollArrayWrapper实例

      EPollSelectorImpl.java

          EPollSelectorImpl(SelectorProvider sp) throws IOException {
      super(sp);
      // 创建管道,用long类型返回管道的两个文件描述符。读端以高32位的形式返回,写端以低32位的形式返回。
      long pipeFds = IOUtil.makePipe(false);
      fd0 = (int) (pipeFds >>> 32);
      fd1 = (int) pipeFds;
      // 创建Epoll文件描述符,并且创建映射数组记录事件
      pollWrapper = new EPollArrayWrapper();
      // 初始化中断文件描述符,把新创建的Epoll注册到管道的读文件描述符上
      pollWrapper.initInterrupt(fd0, fd1);
      fdToKey = new HashMap<>();
      }

      EPollArrayWrapper的构造方法中创建了Epoll实例

      EPollArrayWrapper.java

          EPollArrayWrapper() throws IOException {
      // 创建了Epoll实例,并将它的事件数组地址记录下来方便操作
      epfd = epollCreate();

      // the epoll_event array passed to epoll_wait
      int allocationSize = NUM_EPOLLEVENTS * SIZE_EPOLLEVENT;
      pollArray = new AllocatedNativeObject(allocationSize, true);
      pollArrayAddress = pollArray.address();

      // eventHigh needed when using file descriptors > 64k
      if (OPEN_MAX > MAX_UPDATE_ARRAY_SIZE)
      eventsHigh = new HashMap<>();
      }

      ...

      private native int epollCreate();

      Native方法epollCreate()的源码,调用了内核的epoll_create指令,创建并获取了一个文件描述符。

      epoll有三个指令,epoll_create、epoll_ctl、epoll_wait,都可以在Linux环境中使用man命令来查看详细文档。

      EPollArrayWrapper.c

      JNIEXPORT jint JNICALL
      Java_sun_nio_ch_EPollArrayWrapper_epollCreate(JNIEnv *env, jobject this)
      {
      /*
      * epoll_create expects a size as a hint to the kernel about how to
      * dimension internal structures. We can't predict the size in advance.
      */
      int epfd = epoll_create(256);
      if (epfd < 0) {
      JNU_ThrowIOExceptionWithLastError(env, "epoll_create failed");
      }
      return epfd;
      }
      socketChannel.register()

      注册事件时其实并没有对Epoll进行事件添加,而是在只是把它加入了待添加的容器。

      public final SelectionKey register(Selector sel, int ops,
      Object att)
      throws ClosedChannelException
      {
      synchronized (regLock) {
      if (!isOpen())
      throw new ClosedChannelException();
      if ((ops & ~validOps()) != 0)
      throw new IllegalArgumentException();
      if (blocking)
      throw new IllegalBlockingModeException();
      // 判断是否存在事件的key,存在的话就更新,不存在就新建
      // 最终都会走到 EPollArrayWrapper#setUpdateEvents方法
      SelectionKey k = findKey(sel);
      if (k != null) {
      k.interestOps(ops);
      k.attach(att);
      }
      if (k == null) {
      // New registration
      synchronized (keyLock) {
      if (!isOpen())
      throw new ClosedChannelException();
      k = ((AbstractSelector)sel).register(this, ops, att);
      addKey(k);
      }
      }
      return k;
      }
      }

      AbstractSelector.register调用实现的子类方法implRegister

      EPollSelectorImpl.java

          protected void implRegister(SelectionKeyImpl ski) {
      if (closed)
      throw new ClosedSelectorException();
      SelChImpl ch = ski.channel;
      int fd = Integer.valueOf(ch.getFDVal());
      fdToKey.put(fd, ski);
      pollWrapper.add(fd);
      keys.add(ski);
      }

      调用setUpdateEvents写入待更新事件容器

      EPollArrayWrapper.java


      /**
      * Add a file descriptor
      */
      void add(int fd) {
      // 强制初始update events为0,因为他可能会被之前注册kill掉
      synchronized (updateLock) {
      assert !registered.get(fd);
      setUpdateEvents(fd, (byte)0, true);
      }
      }

      /**
      * 设置文件描述符的待更新事件到 eventsHigh 中。
      */
      private void setUpdateEvents(int fd, byte events, boolean force) {
      if (fd < MAX_UPDATE_ARRAY_SIZE) {
      if ((eventsLow[fd] != KILLED) || force) {
      eventsLow[fd] = events;
      }
      } else {
      Integer key = Integer.valueOf(fd);
      if (!isEventsHighKilled(key) || force) {
      eventsHigh.put(key, Byte.valueOf(events));
      }
      }
      }
      selector.select()

      调用selector.select()时,会将刚才注册的待更新事件绑定到文件描述符上,然后进入阻塞状态等待事件回调。

      EPollSelectorImpl.java

          protected int doSelect(long timeout) throws IOException {
      if (closed)
      throw new ClosedSelectorException();
      processDeregisterQueue();
      try {
      begin();
      pollWrapper.poll(timeout);
      } finally {
      end();
      }
      processDeregisterQueue();
      int numKeysUpdated = updateSelectedKeys();
      if (pollWrapper.interrupted()) {
      // Clear the wakeup pipe
      pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);
      synchronized (interruptLock) {
      pollWrapper.clearInterrupted();
      IOUtil.drain(fd0);
      interruptTriggered = false;
      }
      }
      return numKeysUpdated;
      }

      EPollArrayWrapper.java


      int poll(long timeout) throws IOException {
      // 将上一步待更新的时间进行注册
      updateRegistrations();
      // 进入阻塞状态,等待事件发生
      updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd);
      for (int i=0; i<updated; i++) {
      if (getDescriptor(i) == incomingInterruptFD) {
      interruptedIndex = i;
      interrupted = true;
      break;
      }
      }
      return updated;
      }


      /**
      * Update the pending registrations.
      */
      private void updateRegistrations() {
      synchronized (updateLock) {
      int j = 0;
      while (j < updateCount) {
      int fd = updateDescriptors[j];
      short events = getUpdateEvents(fd);
      boolean isRegistered = registered.get(fd);
      int opcode = 0;

      if (events != KILLED) {
      if (isRegistered) {
      opcode = (events != 0) ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
      } else {
      opcode = (events != 0) ? EPOLL_CTL_ADD : 0;
      }
      if (opcode != 0) {
      // 调用native方法进行事件绑定
      epollCtl(epfd, opcode, fd, events);
      if (opcode == EPOLL_CTL_ADD) {
      registered.set(fd);
      } else if (opcode == EPOLL_CTL_DEL) {
      registered.clear(fd);
      }
      }
      }
      j++;
      }
      updateCount = 0;
      }
      }

      总结:到此为止,我们已经粗略的把整个NIO调用流程都梳理了一遍,Java调用了操作系统的Api来创建Socket,获取到Socket的文件描述符,再创建一个Selector对象,对应操作系统的EPoll描述符,将获取到的Socket连接的文件描述符的事件绑定到Selector对应的EPoll文件描述符上,进行事件的异步通知,这样就实现了使用一条线程,并且不需要太多的无效的遍历,将事件处理交给了操作系统内核,大大提高了效率。

      EPoll指令详解

      epoll_create

      int epoll_create(int size);

      创建一个epoll实例,并返回一个非负数作为文件描述符,用于对epoll接口的所有后续调用。参数size代表可能会容纳size个描述符,但size不是一个最大值,只是提示操作系统它的数量级,现在这个参数基本上已经弃用了。

      epoll_ctl

      int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

      使用文件描述符epfd引用的epoll实例,对目标文件描述符fd执行op操作。

      参数epfd表示epoll对应的文件描述符,参数fd表示socket对应的文件描述符。

      参数op有以下几个值:EPOLL_CTL_ADD:注册新的fd到epfd中,并关联时间eventEPOLL_CTL_MOD:修改已经注册的fd的监听事件;EPOLL_CTL_DEL:从epfd中移除fd,并且忽略掉绑定的event,这时event可以为null;

      参数event是一个结构体。


      struct epoll_event {
      __uint32_t events; /* Epoll events */
      epoll_data_t data; /* User data variable */
      };

      typedef union epoll_data {
      void *ptr;
      int fd;
      __uint32_t u32;
      __uint64_t u64;
      } epoll_data_t;

      events有很多可选值,这里只举例最常见的几个:

      EPOLLIN :表示对应的文件描述符是可读的(close也会发送消息);EPOLLOUT:表示对应的文件描述符是可写的;EPOLLERR:表示对应的文件描述符发生了错误;

      成功则返回0,失败返回-1

      epoll_wait

      int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

      等待文件描述符epfd上的事件。

      epfd是Epoll对应的文件描述符,events表示调用者所有可用事件的集合,maxevents表示最多等到多少个事件就返回,timeout是超时时间。

      完

      操作系统的IO还涉及到零拷贝和直接内存两部分的知识,也是操作系统提高性能的利器,将在以后的文章中进行探讨。

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://blog.51cto.com/alex4dream/2740771,作者:洛神灬殇,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:【RabbitMQ技术专题】基本组件和概念介绍

      下一篇:datatable转list方法(有借鉴到他人)

      相关文章

      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5250013

      查看更多

      热门标签

      linux java python javascript 数组 前端 docker Linux vue 函数 shell git 节点 容器 示例
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号