网络编程 - 3 (Netty-简介、线程模型、核心API)

118 阅读7分钟

1.1 Netty概述

Netty是一个被广泛使用的,基于NIO的Java网络应用编程框架,Netty框架可以帮助开发者快速、简单的实现客户端和服务端的网络应用程序。

特点:

  • API简单易用:支持阻塞和非阻塞的socket
  • 基于事件模型:可扩展性和灵活性更强
  • 高度定制化的线程模型:支持单线程和多线程
  • 高通吐、低延迟、资源占用率低
  • 完整支持SSL和TLS
  • 学习难度低

应用场景:

  • 互联网行业:分布式系统远程调用,高性能的RPC框架
  • 游戏行业:大型网络游戏高性能通信
  • 大数据:hadoop的高性能通信和序列化组件Avro的RPC框架

1.2 线程模型

1.2.1 单线程模型

image.png

服务端用一个线程通过多路复用完成所有的IO操作(连接、读、写等),编码简单,清晰明了,但是如果客户端连接数量较多,将无法支撑,前面NIO案例就是属于这种模型。

1.2.2 线程池模型

image.png

服务端用一个线程专门处理客户端连接的请求,用一个线程池负责IO操作。在绝大多数场景下,该模型都能满足网络编程需求。

1.2.3 Netty线程模型

image.png

各组件之间的关系:

Netty抽象出两组线程池:BossGroup 和 WorkGroup

  • BossGroup专门用来接受客户端的请求
  • WorkGroup专门负责网络读写操作
  • BossGroup和WorkGroup类型都是NioEventLoopGroup,相当于一个事件循环组

NioEventLoopGroup:可以有多个线程,即含有多个NioEventLoop

NioEventLoop:表示一个不断循环的执行处理任务的线程

  • 每个NioEventLoop中包含一个Selector, 一个taskQueue
    • Selector上可以注册监听多个NioChannel,也就是监听Socket网络通信
    • 每个NioChannel只会绑定在为一个NioEventLoop上
    • 每个NioChannel都绑定有一个自己的ChannelPipeline
  • NioEventLoop内部采用串行化(Pipeline)设计:责任链模式
    • 消息读取 -》解码 -》处理(handlers)-》编码 -》 发送,始终由IO线程NioEventLoop处理

image.png

一个Client连接的执行流程:

  1. BossGroup的NioEventLoop的循环执行步骤:

    1.1 轮询accept事件

    1.2 处理accept事件:与client建立连接,生成NioSocketChannel,并将其注册到某个worker的NioEventLoop的selector

    1.3 处理任务队列的任务,即runTasks

  2. WorkGroup的NioEventLoop的循环执行步骤:

    2.1 轮询read、write事件

    2.2 在对应的NioSocketChannel中,处理业务相关操作(ChannelHandler)

    2.3 处理任务队列的任务,即runTasks

  3. 每个Worker的NioEventLoop处理业务时,会使用管道pipeline。Pipeline中包含了Channel,通过管道可以获取到对应Channel,Channel中维护了很多的Handler处理器。

1.3 核心API

1.3.1 ServerBootstrap和BootStrap

ServerBootStrap : 是Netty中的服务端启动助手,通过它可以完成服务端的各种配置。

BootStrap : 是Netty中的客户端启动助手,通过它可以完成客户端的各种配置。

常用方法:

服务端ServerBootstrap

ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup chileGroup); 
该方法用于设置两个EventLoopGroup,连接线程组和工作线程组
B channel(Class<? extends C> channelClass); 该方法用于设置服务端或客户端通道的实现类型
B option(ChannelOption option, T value); 用于给ServerChannel添加配置
ServerBootstrap childOption(ChannelOption childOption, T value); 用于给接收通道添加配置
ServerBootstrap childHandler(ChannelHandler childHandler); 该方法用于设置业务处理类(自定义handler)
ChannelFuture bind(int inetPort); 设置占用端口号

客户端BootStrap

B group(EventLoopGroup group); 用于设置客户端的EventLoopGroup
B channel(Class<? extends C> channelClass); 设置服务端或客户端通道的实现类型
ChannelFutrue connect(String inetHost, int inetPort); 用来配置连接服务端的地址信息 host:port

1.3.2 EventLoopGroup(Boss\WorkerGroup)

在Netty服务端编程中,一般需要提供两个EventLoopGroup:

1. BoosEventLoopGroup: 专门用来负责客户端连接
2. WorkerEventLoopGroup: 专门负责网络读写操作
  • Netty为了更好的利用多核CPU资源,一般会有多个EventLoop同时工作,每个EventLoop维护着一个Selector实例。
  • EventLoopGroup提供next接口,可以从组里面按照一定规则获取其中一个EventLoop来处理任务。
  • EventLoopGroup本质上是一组EventLoop, 池化管理思想。

通常一个服务端即一个ServerSocketChannel对应一个Seletor和一个EventLoop线程。 BoosEventLoop负责接受客户端的连接,并将SocketChannel交给WorkerEventLoopGroup进行IO处理。

image.png

  • BossEventLoopGroup通常是单线程的EventLoop, EventLoop维护着一个注册了ServerSocketChannel的Selector实例。
  • Boss的EventLoop不断轮询Selector, 将连接事件分离出来,通常是OP_ACCEPT事件,然后将收到的SocketChannel交给WorkerEventLoopGroup。
  • WorkerEventGroup会通过next方法选择其中一个EventLoop,来将这个SocketChannel注册到其维护的Selector上,并对后续的事件进行处理。

1.3.3 ChannelHandler及实现类

ChannelHandler接口定义了许多事件处理的方法,我们通过重写这些方法实现业务功能。 API关系如下:

image.png

我们经常需要自定义一个Handler类去继承ChannelInBoundHandlerAdapter,通过重写相应方法实现业务逻辑

void channelActive(ChannelHandlerContext ctx); 通道就绪事件
void channelRead(ChannelHandlerContext ctx, Object msg); 通道读取数据事件
void channelReadComplete(ChannelHandlerContext ctx); 通道读取完毕事件
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause); 通道发生异常事件

1.3.4 ChannelPipeline

ChannelPipeline是一个Handler的集合,它负责处理和拦截inbound或者outbound的事件和操作,相当于一个贯穿netty的链(责任链模式)。

image.png

  1. 事件传递到ChannelPipeline中的第一个ChannelHandler
  2. channelHandler使用分配的ChannelHandlerContext将事件传递给channelPipeline中的下一个channelHandler

图中的绿线串起来就是pipeline,它包含3个处理不同业务的ChannelHandler,依次通过这3个ChannelHandler。因为这3个ChannelHandler不知道彼此,所以要用ChannelHandlerContext上下文来说明,ChannelHanderContext包含ChannelHandler、Channel、pipeline的信息。

ChannelPipeline addFirst(ChannelHandler... handlers); 把业务处理类(handler)添加到pipeline链中的第一个位置
ChannelPipeline addLast(ChannelHandler... handlers); 把业务处理类(handler)添加到pipeline链中的最后一个位置

1.3.5 ChannelHandlerContext

ChannelHandlerContext是事件处理器上下文对象,Pipeline链中的实际处理节点。每个处理节点ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时ChannelHandlerContext绑定了对应的pipeline和channel信息,方便对ChannelHandler进行调用。

常用方法:

ChannelFutrue close(); 关闭通道
ChannelOutboundInvoker flush(); 刷新
ChannelFutrue writeAndFlush(Object msg); 将数据写入到ChannelPipeline中当前ChannelHandler的下一个ChannelHandler开始处理(出栈交给下一个handler继续处理)

1.3.6 ChannelOption

Netty在创建channel实例后,一般都需要设置channelOption参数。ChannelOption是Socket的标准化参数而非netty独创。

常配置的参数:

ChannelOption.SO_BACKLOG: 用来初始化服务器可连接队列大小,对应TCP/IP协议listen函数中的backlog参数
    服务端处理客户端请求是顺序处理的,所以同一时间只能处理一个客户端请求。
    如果请求连接过多,服务端将不能及时处理,多余连接放在队列中等待,backlong参数指定了等待队列大小
    
ChannelOption.SO_LEEPALIVE: 连接是否一直保持(是否长连接)

1.3.7 ChannelFuture

ChannelFuture表示Channel中异步IO操作的未来结果,在Netty中异步IO操作都是直接返回,调用者并不能立刻获取结果,但是可以通过ChannelFuture来获取IO操作的处理状态。N etty异步非阻塞处理事件,如果事件很费时,会通过Future异步处理,不会阻塞。

常用方法:

Channel channel(); 返回当前正在进行IO操作的通道
ChannelFuture sync(); 等待异步操作执行完成

1.3.8 Uppooled类

Unpooled是Netty提供的一个专门用来操作缓冲区的工具类

常用方法:

ByteBuf copiedBuffer(CharSequenece string, Charset charset); 通过给定的数据和字符编码返回一个ByteBuf对象(类似于NIO中的ByteBuffer对象)