• 为什么要使用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操作,没有必要开启多线程。

  • 相关阅读:
    shell--练习--简易计算器
    shell--运算符
    shell--传递参数
    PHP数学函数的练习
    PDO对数据库的操作
    PHP实现手机短信的验证
    ThinkPHP框架 _ 学习16
    ThinkPHP框架 _ 学习15
    ThinkPHP框架 _ 学习14
    ThinkPHP框架 _ 学习13
  • 原文地址:https://www.cnblogs.com/anning1994/p/10028042.html
Copyright © 2020-2023  润新知