zoukankan      html  css  js  c++  java
  • 为什么要使用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操作,没有必要开启多线程。

  • 相关阅读:
    【POJ 3162】 Walking Race (树形DP-求树上最长路径问题,+单调队列)
    【POJ 2152】 Fire (树形DP)
    【POJ 1741】 Tree (树的点分治)
    【POJ 2486】 Apple Tree (树形DP)
    【HDU 3810】 Magina (01背包,优先队列优化,并查集)
    【SGU 390】Tickets (数位DP)
    【SPOJ 2319】 BIGSEQ
    【SPOJ 1182】 SORTBIT
    【HDU 5456】 Matches Puzzle Game (数位DP)
    【HDU 3652】 B-number (数位DP)
  • 原文地址:https://www.cnblogs.com/anning1994/p/10028042.html
Copyright © 2011-2022 走看看