为什么要使用Netty

有兴趣的同学可以移步笔者的个人博客 更多博客

为什么使用netty

Netty是一个网络通信框架,其出现的原因主要是为了解决NIO的不足。如:

  1. NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等;
  2. 需要具备其它的额外技能做铺垫,例如熟悉Java多线程编程,因为NIO编程涉及到Reactor模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序;
  3. 可靠性能力补齐,工作量和难度都非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐工作量和难度都非常大;

NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。

本文会从传统的阻塞I/O和线程池模型面临的问题讲起,然后对比几种常见I/O模型,一步步分析NIO怎么利用事件模型处理I/O,解决线程池瓶颈处理海量连接,包括利用面向事件的方式编写服务端/客户端程序。

传统BIO模型分析

{
 ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//线程池
 ServerSocket serverSocket = new ServerSocket();
 serverSocket.bind(8088);
 while(!Thread.currentThread.isInturrupted()){//主线程死循环等待新连接到来
 Socket socket = serverSocket.accept();
 executor.submit(new ConnectIOnHandler(socket));//为新的连接创建新的线程
}
class ConnectIOnHandler extends Thread{
    private Socket socket;
    public ConnectIOnHandler(Socket socket){
       this.socket = socket;
    }
    public void run(){
      while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循环处理读写事件
          String someThing = socket.read()//读取数据
          if(someThing!=null){
             //处理数据
             socket.write()//写数据
          }
      }
    }
}

上面的代码就是典型的传统BIO服务端监听代码,由于accept()read()write() 这三个方法是阻塞的、耗时的。如果是单线程的话,在执行阻塞代码是会使cpu空等,并且最重要的是,如果有10000个并发的话,那么等待时间是不能够接受的。

使用多线程的本质就是:

  1. 利用多核。
  2. 当I/O阻塞系统,但CPU空闲的时候,可以利用多线程使用CPU资源。

但是传统IO在使用多线程解决上面的问题时会严重依赖于线程,意味着每个请求都要建立一个线程,当客户端数量达到万级时,就需要建立万级的线程。线程占用的内存,切换线程资源昂贵,最终导致这种方案无法保证系统的伸缩性。

传统NIO模型分析

所有的系统I/O都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。

需要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在"干活",而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。


interface ChannelHandler{
     void channelReadable(Channel channel);
     void channelWritable(Channel channel);
  }
  class Channel{
    Socket socket;
    Event event;//读,写或者连接
  }

  //IO线程主循环:
  class IoThread extends Thread{
  public void run(){
  Channel channel;
  while(channel=Selector.select()){//选择就绪的事件和对应的连接
     if(channel.event==accept){
        registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器
     }
     if(channel.event==write){
        getChannelHandler(channel).channelWritable(channel);//如果可以写,则执行写事件
     }
     if(channel.event==read){
         getChannelHandler(channel).channelReadable(channel);//如果可以读,则执行读事件
     }
   }
  }
  Map<Channel,ChannelHandler> handlerMap;//所有channel的对应事件处理器
 }

NIO由原来的阻塞读写(占用线程)变成了单线程轮询事件,找到可以进行读写的网络描述符进行读写。除了事件的轮询是阻塞的(没有可干的事情必须要阻塞),剩余的I/O操作都是纯CPU操作,没有必要开启多线程。

原文地址:https://www.cnblogs.com/anning1994/p/10028042.html