阅读 477

bio、nio、aio

IO模型就是选用什么样的通道进行数据的接收和发送,JAVA支持的三种网络IO编程模式分别为:Bio、Nio、Aio。

Bio(blocking IO)

  • 同步阻塞模型,一个客户端对应一个县城。如果服务端启动后没有客户端来连接,那么会一直阻塞。在客户端连接后,没有信息传输也会进行阻塞。
  • 缺点:
    • 1、IO代码里read操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,浪费资源。
    • 2、如果线程很多,会导致服务器线程太多,压力太大,比如C10K问题。

Nio(non blocking IO)

  • 非同步阻塞队列。在创建服务器绑定端口的时候就可以自主的设置是否为阻塞。和bio最大的不同是可以讲serversocket注册到selector上,也就是加入了多路复用器。
Selector selector = Selector.open(); 24 // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
复制代码
  • 在将serversocket注册到selector上后,在循环中进行阻塞
// 阻塞等待需要处理的事件发生  selector.select()
复制代码
  • 在对多路复用器中的key进行循环,根据key的不同事件状态进行连接或者读取。
 public class NioSelectorServer { 
 public static void main(String[] args) throws IOException, InterruptedException {
 // 创建NIO ServerSocketChannel 
 ServerSocketChannel serverSocket = ServerSocketChannel.open(); 
  serverSocket.socket().bind(new InetSocketAddress(9000)); 
  // 设置ServerSocketChannel为非阻塞 
  serverSocket.configureBlocking(false); 
  // 打开Selector处理Channel,即创建epoll 
  Selector selector = Selector.open(); 
  // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
 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(); 
 // 如果是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("客户端连接成功"); 
 } else if (key.isReadable()) { // 如果是OP_READ事件,则进行读取和打印 
 SocketChannel socketChannel = (SocketChannel) key.channel(); 
 ByteBuffer byteBuffer = ByteBuffer.allocate(128); 
 int len = socketChannel.read(byteBuffer); 
 // 如果有数据,把数据打印出来 
 if (len > 0) { 
 System.out.println("接收到消息:" + new String(byteBuffer.array())); 
 } else if (len == ‐1) { // 如果客户端断开连接,关闭Socket 
 System.out.println("客户端断开连接"); 
 socketChannel.close(); 
 } 
 } 
 //从事件集合里删除本次处理的key,防止下次select重复处理 
 iterator.remove(); 
 } 
 } 
 } 
}
复制代码

! nio阻塞后,不会无限制的去循环,会在接收到事件时,才会去处理。每次只会响应有事件请求的客户端。

  • Nio有三大核心组件

    • NIO 有三大核心组件: Channel(通道), Buffer(缓冲区),Selector(多路复用器)
      • 1、channel 类似于流,每个 channel 对应一个 buffer缓冲区,buffer 底层就是个数组
      • 2、channel 会注册到 selector 上,由 selector 根据 channel 读写事件的发生将其交由某个空闲的线程处理
      • 3、NIO 的 Buffer 和 channel 都是既可以读也可以写
  • nio底层是怎么感知有事件发生的呢?

    • 在早期(jdk1.4)nio的底层是一个轮询的方式去查看有没有客户端进行连接的事件,然后进行处理。这样的性能会很低,如果连接数过多,那么每次循环所带来的也是会水涨船高。这种方法是基于用linux的内核函数select()或poll()来实现。
    • 在JDK1.5开始引入了epoll基于事件响应机制来优化NIO。也就是如果有事件发生,客户端主动通知多路复用器请求事件,只处理发生事件请求的客户端,不会在次去全部循环处理。如下图:

  • nio 功能实现最核心的三个方法:

    • Selector.open() //创建多路复用器
    • socketChannel.register() //将channel注册到多路复用器上
    • selector.select() //阻塞等待需要处理的事件发生

多路复用器(selector)底层?

      Selector selector = Selector.open(); //创建多路复用器
复制代码
  • selector底层是对linux c语言系统创建的epoll对象的封装
  • linux中一切皆文件,所以在open()之后会创建epoll,底层会返回一个epfd。
  • 在socketChannel.register(selector, SelectionKey.OP_READ); 之后,epollwrapper会调用add方法,将channel的fd保存起来,在调用 selector.select()时,底层会调用epolctl(),在这个方法内会真的进行epoll和channel的绑定。
  • 当有事件发生时,会将socketchannel的事件添加到epoll底层的一个事件就绪list内radlist
  • selector.select(); 会调用一个epollwait方法 查看rdlist中是否存在 有等待执行的事件,如果有的话 会执行事件不在阻塞,如果没有事件那么会继续阻塞。

Aio

  • 对nio很多方法的封装,但是使用不多,附上一张图对比较下。

  • 底层linux内调用的链路图。

文章分类
后端
文章标签