Netty入门教程——认识Netty

什么是Netty?

  Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。
  Netty 是一个普遍使用的 Java 网络编程框架(Netty 在 2011 年得到了Duke's Choice Award,见https://www.java.net/dukeschoice/2011)。它活跃和成长于用户社区,像大型公司   Facebook 和 Instagram 以及流行 开源项目如 Infinispan, HornetQ, Vert.x, Apache Cassandra 和 Elasticsearch 等,都利用其强大的对于网络抽象的核心代码。java

Netty和Tomcat有什么区别?

  Netty和Tomcat最大的区别就在于通讯协议,Tomcat是基于Http协议的,他的实质是一个基于http协议的web容器,可是Netty不同,他能经过编程自定义各类协议,由于netty可以经过codec本身来编码/解码字节流,完成相似redis访问的功能,这就是netty和tomcat最大的不一样。web

  有人说netty的性能就必定比tomcat性能高,其实否则,tomcat从6.x开始就支持了nio模式,而且后续还有APR模式——一种经过jni调用apache网络库的模式,相比于旧的bio模式,并发性能获得了很大提升,特别是APR模式,而netty是否比tomcat性能更高,则要取决于netty程序做者的技术实力了。redis

为何Netty受欢迎?

  如第一部分所述,netty是一款收到大公司青睐的框架,在我看来,netty可以受到青睐的缘由有三:数据库

  1. 并发高
  2. 传输快
  3. 封装好

Netty为何并发高

  Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通讯框架,对比于BIO(Blocking I/O,阻塞IO),他的并发性能获得了很大提升,两张图让你了解BIO和NIO的区别:apache

  

                  阻塞IO的通讯方式编程

 

    

                 非阻塞IO的通讯方式api

 

  从这两图能够看出,NIO的单线程能处理链接的数量比BIO要高出不少,而为何单线程能处理更多的链接呢?缘由就是图二中出现的 Selector
当一个链接创建以后,他有两个步骤要作,第一步是接收完客户端发过来的所有数据,第二步是服务端处理完请求业务以后返回response给客户端。NIO和BIO的区别主要是在第一步。
  在BIO中,等待客户端发数据这个过程是阻塞的,这样就形成了一个线程只能处理一个请求的状况,而机器能支持的最大线程数是有限的,这就是为何BIO不能支持高并发的缘由。
  而NIO中,当一个Socket创建好以后,Thread并不会阻塞去接受这个Socket,而是将这个请求交给Selector,Selector会不断的去遍历全部的Socket,一旦有一个Socket创建完成,他会通知Thread,而后Thread处理完数据再返回给客户端—— 这个过程是阻塞的,这样就能让一个Thread处理更多的请求了。
下面两张图是基于BIO的处理流程和netty的处理流程,辅助你理解两种方式的差异:

    

                        BIO的处理流程缓存

 

 

      

                     NIO的处理流程tomcat

 

   除了BIO和NIO以外,还有一些其余的IO模型,下面这张图就表示了五种IO模型的处理流程:服务器

        

                   五种常见的IO模型

  • BIO,同步阻塞IO,阻塞整个步骤,若是链接少,他的延迟是最低的,由于一个线程只处理一个链接,适用于少链接且延迟低的场景,好比说数据库链接。
  • NIO,同步非阻塞IO,阻塞业务处理但不阻塞数据接收,适用于高并发且处理简单的场景,好比聊天软件。
  • 多路复用IO,他的两个步骤处理是分开的,也就是说,一个链接可能他的数据接收是线程a完成的,数据处理是线程b完成的,他比BIO能处理更多请求,可是比不上NIO,可是他的处理性能又比BIO更差,由于一个链接他须要两次system call,而BIO只须要一次,因此这种IO模型应用的很少。
  • 信号驱动IO,这种IO模型主要用在嵌入式开发,不参与讨论。
  • 异步IO,他的数据请求和数据处理都是异步的,数据请求一次返回一次,适用于长链接的业务场景。

 

Netty为何传输快

  Netty的传输快其实也是依赖了NIO的一个特性——零拷贝。咱们知道,Java的内存有堆内存、栈内存和字符串常量池等等,其中堆内存是占用内存空间最大的一块,也是Java对象存放的地方,通常咱们的数据若是须要从IO读取到堆内存,中间须要通过Socket缓冲区,也就是说一个数据会被拷贝两次才能到达他的的终点,若是数据量大,就会形成没必要要的资源浪费。
  Netty针对这种状况,使用了NIO中的另外一大特性——零拷贝,当他须要接收数据的时候,他会在堆内存以外开辟一块内存,数据就直接从IO读到了那块内存中去,在netty里面经过ByteBuf能够直接对这些数据进行直接操做,从而加快了传输速度。

        

                传统数据拷贝

 

        

                      零拷贝

 

   上文介绍的ByteBuf是Netty的一个重要概念,他是netty数据处理的容器,也是Netty封装好的一个重要体现,将在下一部分作详细介绍。

 

为何说Netty封装好?

  要说Netty为何封装好,这种用文字是说不清的,直接上代码:

  • 阻塞I/O
public class PlainOioServer { public void serve(int port) throws IOException { final ServerSocket socket = new ServerSocket(port);     //1
        try { for (;;) { final Socket clientSocket = socket.accept();    //2
                System.out.println("Accepted connection from " + clientSocket); new Thread(new Runnable() {                        //3
 @Override public void run() { OutputStream out; try { out = clientSocket.getOutputStream(); out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8")));                            //4
 out.flush(); clientSocket.close(); //5
 } catch (IOException e) { e.printStackTrace(); try { clientSocket.close(); } catch (IOException ex) { // ignore on close
 } } } }).start(); //6
 } } catch (IOException e) { e.printStackTrace(); } } }
}
  • 非阻塞IO
public class PlainNioServer { public void serve(int port) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); ServerSocket ss = serverChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); ss.bind(address); //1
        Selector selector = Selector.open();                        //2
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);    //3
        final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes()); for (;;) { try { selector.select(); //4
            } catch (IOException ex) { ex.printStackTrace(); // handle exception
                break; } Set<SelectionKey> readyKeys = selector.selectedKeys();    //5
            Iterator<SelectionKey> iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); try { if (key.isAcceptable()) {                //6
                        ServerSocketChannel server = (ServerSocketChannel)key.channel(); SocketChannel client = server.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, msg.duplicate()); //7
 System.out.println( "Accepted connection from " + client); } if (key.isWritable()) {                //8
                        SocketChannel client = (SocketChannel)key.channel(); ByteBuffer buffer = (ByteBuffer)key.attachment(); while (buffer.hasRemaining()) { if (client.write(buffer) == 0) {        //9
                                break; } } client.close(); //10
 } } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) { // 在关闭时忽略
 } } } } } }
  • Netty
public class NettyOioServer { public void server(int port) throws Exception { final ByteBuf buf = Unpooled.unreleasableBuffer( Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8"))); EventLoopGroup group = new OioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap();        //1
 b.group(group) //2
             .channel(OioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() {//3
 @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {            //4
 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);//5
 } }); } }); ChannelFuture f = b.bind().sync();  //6
 f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); //7
 } } }

  从代码量上来看,Netty就已经秒杀传统Socket编程了,可是这一部分博大精深,仅仅贴几个代码岂能说明问题,在这里给你们介绍一下Netty的一些重要概念,让你们更理解Netty。

  • Channel
    数据传输流,与channel相关的概念有如下四个,上一张图让你了解netty里面的Channel。

                          Channel一览

        

  • Channel,表示一个链接,能够理解为每个请求,就是一个Channel。
  • ChannelHandler,核心处理业务就在这里,用于处理业务请求。
  • ChannelHandlerContext,用于传输业务数据。
  • ChannelPipeline,用于保存处理过程须要用到的ChannelHandler和ChannelHandlerContext。
  •   ByteBuf
    ByteBuf是一个存储字节的容器,最大特色就是使用方便,它既有本身的读索引和写索引,方便你对整段字节缓存进行读写,也支持get/set,方便你对其中每个字节进行读写,他的数据结构以下图所示:
       

              ByteBuf数据结构

   

  他有三种使用模式:

  1. Heap Buffer 堆缓冲区
    堆缓冲区是ByteBuf最经常使用的模式,他将数据存储在堆空间。
  2. Direct Buffer 直接缓冲区
    直接缓冲区是ByteBuf的另一种经常使用模式,他的内存分配都不发生在堆,jdk1.4引入的nio的ByteBuffer类容许jvm经过本地方法调用分配内存,这样作有两个好处
    • 经过免去中间交换的内存拷贝, 提高IO处理速度; 直接缓冲区的内容能够驻留在垃圾回收扫描的堆区之外。
    • DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 以外的内存, GC对此”无能为力”,也就意味着规避了在高负载下频繁的GC过程对应用线程的中断影响.
  3. Composite Buffer 复合缓冲区
    复合缓冲区至关于多个不一样ByteBuf的视图,这是netty提供的,jdk不提供这样的功能。

  除此以外,他还提供一大堆api方便你使用。

  • Codec
    Netty中的编码/解码器,经过他你能完成字节与pojo、pojo与pojo的相互转换,从而达到自定义协议的目的。
    在Netty里面最有名的就是HttpRequestDecoder和HttpResponseEncoder了。

 转载来源:https://www.jianshu.com/p/b9f3f6a16911

相关文章
相关标签/搜索