浅谈NIO和Epoll的实现原理

本文深入探讨了NIO(非阻塞IO)的工作原理,对比BIO(阻塞IO),并详细讲解了Epoll在Linux系统下如何实现高效的多路复用,通过Java NIO API与操作系统底层交互,实现单线程处理并发连接,极大提升网络应用性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是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
在这里插入图片描述
命令行打印服务启动成功,此时并无客户端连接,所以连接成功并未打印,说明程序被阻塞在了serverSocket.accept()方法

此时运行Client,Server打印日志连接成功准备接收数据,此时Client尚未发送数据,Server被阻塞在socket.getInputStream().read(bytes)上,因此其他客户端无法进行连接。
在这里插入图片描述
在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
 * 
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值