IO与NIO的具体流程

Netty

传统IO(同步阻塞式)

传统IO的缺点就是一个线程只能处理一个socket,需要处理多个socket时只能使用线程池。但并不是连接的所有socket都在无时无刻传送数据,所以大多数时间处理socket的线程都在处于阻塞状态,效率低下,开销大。
单线程下只能有一个客户端,多线程可以有多个客户端,但十分消耗性能

阻塞点

  • 在等待客户端连接时,发生阻塞

      while(true){ 
      	//获取一个套接字(阻塞)
      	final Socket socket = server.accept();
      	System.out.println("来个一个新客户端!");
      	newCachedThreadPool.execute(new Runnable() {				
      		@Override
      		public void run() {
      			//业务处理
      			handler(socket);
      		}
      	});		
      }
    

在final Socket socket = server.accept();处一直等待客户端连接

  • 连接后在获取输入流时,发生阻塞

      while(true){
      			//读取数据(阻塞)
      			int read = inputStream.read(bytes);
      			if(read != -1){
      				System.out.println(new String(bytes, 0, read));
      			}else{
      				break;
      			}
      		}
    

在int read = inputStream.read(bytes);处一直等待客户端发送数据

NIO(同步非阻塞)

NIO之所以是同步,是因为它的accept/read/write方法的内核I/O操作都会阻塞当前线程,与IO相比,NIO可以实现单线程处理多个客户端
NIO由三个主要部分组成:Channel(通道)、Buffer(缓冲区)、Selector(选择器)

与IO的对应关系

相同功能关键字

ServerSocketChannel 对应 ServerSocket
SocketChannel 对应 Socket

新增关键字

Selector,用于监听ServerSocketChannel和SocketChannel发生的事件
SelectionKey,每一个Channel都需要被注册到Selector上才能使用,而SelectionKey代表了此Channel在某一Selector上注册=。

具体流程

  • 初始化一个NIOServer,并初始化一个Selector,将使用的端口号传入初始化方法

      NIOServer server = new NIOServer();
      server.initServer(8000);
    
  • 初始化ServerSocketChannel,将对应的ServerSocket与传入的端口号绑定,并指明一个Selector为这个ServerSocketChannel服务。

      ServerSocketChannel serverChannel = ServerSocketChannel.open();
      // 设置通道为非阻塞
      serverChannel.configureBlocking(false);
      // 将该通道对应的ServerSocket绑定到port端口
      serverChannel.socket().bind(new InetSocketAddress(port));
      // 获得一个通道管理器
      this.selector = Selector.open();
      // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
      // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
      serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    

serverChannel.register(selector, SelectionKey.OP_ACCEPT);
指明一个Selector监听ServerSocketChannel,当发生OP_ACCEPT 事件时,返回一个SelectionKey,类似于K-V,一个SelectionKey对应了一个事件。

  • 服务端初始化后,开始监听客户端的消息,在注册的信息到达之前,selector.select()一直处于监听状态。当客户端成功与服务器建立连接后,触发了OP_ACCEPT,此时程序才会向下执行

      selector.select();
    
  • 触发事件后,将获取到一个迭代器,用于遍历触发的事件,也就是SelectionKey,然后按序逐一调用handler方法,根据事件类型的不同,转入不同的处理方法

      public void handler(SelectionKey key) throws IOException {
      	// 客户端请求连接事件
      	if (key.isAcceptable()) {
      		handlerAccept(key);
      		// 获得了可读的事件
      	} else if (key.isReadable()) {
      		handelerRead(key);
      	}
      }
    
  • 用户连接时触发的是OP_ACCEPT,用key.isAcceptable()接收,转入OP_ACCEPT的具体处理方法,在具体的方法中,先从key中获取到SocketServerChannel,再从server中获取到SocketChannel,指明一个Selector监听SocketChannel,当发生OP_READ事件时,Selector将会把此事件放入容器中按序处理

      public void handlerAccept(SelectionKey key) throws IOException {
      ServerSocketChannel server = (ServerSocketChannel) key.channel();
      // 获得和客户端连接的通道
      SocketChannel channel = server.accept();
      // 设置成非阻塞
      channel.configureBlocking(false);
    
      // 在这里可以给客户端发送信息哦
      System.out.println("新的客户端连接");
      // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
      channel.register(this.selector, SelectionKey.OP_READ);
    

    }

这时,Selector中监听的事件有ServerSocketChannel的OP_ACCEPT事件 和 SocketChannel的
OP_READ事件

  • 当Selcetor内存在事件时,便使用selector.select()进行处理

你可能感兴趣的:(Netty)