Netty框架

Netty框架的作用

Netty是一款用于快速开发高性能的网络应用程序的Java框架。它封装了网络编程的复杂性,定义了一种架构模型和一套丰富的设计模式。通过Netty框架,可以编写更高效的、可复用的、可维护的代码。有了Netty,你可以实现自己的HTTP服务器,FTP服务器,UDP服务器,RPC服务器,WebSocket服务器,Redis的Proxy服务器,MySQL的Proxy服务器等等。

传统的HTTP服务器的流程

  1. 创建一个ServerSocket,监听并绑定一个端口
  2. 一系列客户端来请求这个端口
  3. 服务器使用Accept,获得一个来自客户端的Socket连接对象
  4. 启动一个新线程处理连接
    1. 读Socket,得到字节流
    2. 解码协议,得到Http请求对象
    3. 处理Http请求,得到一个结果,封装成一个HttpResponse对象
    4. 编码协议,将结果序列化字节流
    5. 写Socket,将字节流发给客户端
  5. 继续循环步骤3

HTTP服务器之所以称为HTTP服务器,是因为编码解码协议是HTTP协议,如果协议是Redis协议,那它就成了Redis服务器,如果协议是WebSocket,那它就成了WebSocket服务器,等等。

使用Netty你就可以定制编解码协议,实现自己的特定协议的服务器。在高并发环境下,线程数量可能会创建太多,操作系统的任务调度压力大,系统负载也会比较高。那怎么办呢?

于是NIO诞生了,NIO并不是Java独有的概念,NIO代表的一个词汇叫着IO多路复用。它是由操作系统提供的系统调用,早期这个操作系统调用的名字是select,但是性能低下,后来渐渐演化成了Linux下的epoll和Mac里的kqueue。我们一般就说是epoll,因为没有人拿苹果电脑作为服务器使用对外提供服务。而Netty就是基于Java NIO技术封装的一套框架。为什么要封装,因为原生的Java NIO使用起来没那么方便,而且还有臭名昭著的bug,Netty把它封装之后,提供了一个易于操作的使用模式和接口,用户使用起来也就便捷多了。

NIO的全称是NoneBlocking IO,非阻塞IO,区别与BIO,BIO的全称是Blocking IO,阻塞IO。那这个阻塞是什么意思呢?

  1. Accept是阻塞的,只有新连接来了,Accept才会返回,主线程才能继
  2. Read是阻塞的,只有请求消息来了,Read才能返回,子线程才能继续处理
  3. Write是阻塞的,只有客户端把消息收了,Write才能返回,子线程才能继续读取下一个请求

所以传统的多线程服务器是BlockingIO模式的,从头到尾所有的线程都是阻塞的。这些线程就干等在哪里,占用了操作系统的调度资源,什么事也不干,是浪费。

那么NIO是怎么做到非阻塞的呢。它用的是事件机制。它可以用一个线程把Accept,读写操作,请求处理的逻辑全干了。如果什么事都没得做,它也不会死循环,它会将线程休眠起来,直到下一个事件来了再继续干活,这样的一个线程称之为NIO线程。

NIO的大致流程:

while true {
    events = takeEvents(fds)  // 获取事件,如果没有事件,线程就休眠
    for event in events {
        if event.isAcceptable {
            doAccept() // 新链接来了
        } elif event.isReadable {
            request = doRead() // 读消息
            if request.isComplete() {
                doProcess()
            }
        } elif event.isWriteable {
            doWrite()  // 写消息
        }
    }
}

Netty是建立在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象。

在Netty里面,Accept连接可以使用单独的线程池去处理,读写操作又是另外的线程池来处理。

Accept连接和读写操作也可以使用同一个线程池来进行处理。而请求处理逻辑既可以使用单独的线程池进行处理,也可以跟放在读写线程一块处理。线程池中的每一个线程都是NIO线程。用户可以根据实际情况进行组装,构造出满足系统需求的并发模型。

Netty提供了内置的常用编解码器,包括行编解码器[一行一个请求],前缀长度编解码器[前N个字节定义请求的字节长度],可重放解码器[记录半包消息的状态],HTTP编解码器,WebSocket消息编解码器等等

Netty提供了一些列生命周期回调接口,当一个完整的请求到达时,当一个连接关闭时,当一个连接建立时,用户都会收到回调事件,然后进行逻辑处理。

Netty可以同时管理多个端口,可以使用NIO客户端模型,这些对于RPC服务是很有必要的。

Netty除了可以处理TCP Socket之外,还可以处理UDP Socket。

在消息读写过程中,需要大量使用ByteBuffer,Netty对ByteBuffer在性能和使用的便捷性上都进行了优化和抽象。

目的:快速开发高性能、高可靠性的网络服务器和客户端程序

Netty 体系结构

使用Netty可以比直接使用底层的Java API容易使用;在之前讲网络I/O的文章中讲过Java BIO与Java NIO;Java NIO使用了事件驱动模型;相比于BIO单线程可以支持更多的连接,并且NIO是异步的,对于数据读取、连接、监听是无阻塞的。Netty且有Java NIO的这些特性,并且比Java NIO有更好的性能和更易用的API。

Netty的核心组件与Java NIO基本相同:

  • Channel
  • ChannelHandler
  • Future
  • 事件

Channel

在讲Java NIO时也有讲过Channel;在Java NIO中Channel的作用相当于一个通道,承载着数据的读与写以及4大事件。在Netty框架中Channel基本相同,它是Netty中可以代表一个硬件设备、一个文件、一个网络Socket或者一个可执行文件或者多个不同的I/O操作组件,它承载着读写操作。

ChannelHandler

一个ChannelHandler可以简单的理解为一个回调方法,在Netty内部使用回调来处理事件;当一个事件被触发时,与事件相关的ChannelHandler将被调用来响应这个事件的处理,下面可以通过一个服务端的示例来理解ChannelHandler的作用:

public class ConnectHandler extends ChannelInboundHandlerAdapter {
  @Override
  public void channelActive(ChannelHandlerContext ctx) throws Exception{
    System.out.println("Client :"+ctx.channel().remoteAddress() + " connected.");
  }
}

当有一个客户端连接服务端后,服务端的Netty内部将有产生一个连接的事件,事件将触发ConnectHandler的回调触发。

Future

在Netty中,Future提供了一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作结果的占位符;它将在未来某个时刻完成并提供对结果的访问。Netty框架提供了ChannelFuture,它是一个Netty自己实现的Future,继承自JDK的java.util.concurrent.Future。

ChannelFuture接口额外提供了注册一个或者多个GenericFutureListener实例的方法,监听器的回调方法operationComplete()将会在对应的操作完成时被调用。下面通过一个示例来说明:

EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
    Bootstrap b=new Bootstrap();
  b.group(workerGroup);
  b.channel(NioSocketChannel.class);
  ChannelFuture f=b.connect("127.0.0.1","8080").sync();
  //注册一个ChannelFutureListener以便在操作完成时获取通知
  f.addListener(new ChannelFutureListener(){
    @Override
    public void operationComplete(ChannelFuture future){
      if(future.isSuccess()){
        ByteBuf buffer=Unpooled.copiedBuffer("Hello XXX.",Charset.defaultCharset());
        future.channel().writeAndFlush(buffer);
      }else{
        future.cause().printStackTrace();
      }
    }
  });
}finally{
  workerGroup.shutdownGracefully();
}

ChannelHandler和Future是相互补充的机制;它们相互结合使用构成了Netty本身的关键构件之一。

事件

Netty的核心之一,事件用于触发ChannelHandler。在网络中通常会有入站与出站的流量,在Netty中也将事件按入站与出站进行了分类。通过这些不同的事件分类可以清晰的当前网络中有哪些动作发生并对这些动作做出响应。可以记录日志、对数据做转换、对流进行控制和应用程序的业务逻辑处理等。

入站事件:

  • 连接已经激活或者连接失活
  • 数据读取
  • 用户事件
  • 错误事件

出站事件:

  • 打开或者关闭远程连接
  • 将数据写到或者冲刷到网络Socket

入站与出站中的事件都可以对应到ChannelHandler中进行处理;这实现了一个完善的事件驱动范式。下面通过一张图来理解这些入站与出站中的事件:

Netty框架_第1张图片

ChnnelHandler为入站与出站处理器提供了基本的抽象,在Netty框架中提供了大量开箱即用的ChannelHeandler;如Http、SSL/TLS等。

Java NIO 与Netty

Java NIO的三大组件为Channel、Selector、Buffer;在Netty中只看到了Channel与Buffer;但Netty中也有Selector;只是Netty对Selector进行了包装,对应的包含类为EventLoop;EventLoop在Netty中的作用就如Selector在Java NIO中的作用。

你可能感兴趣的:(网络I/O,java,开发语言)