第一节,Netty初认识
Netty的优势
- 网络应用框架的关注点:
- I/O模型,线程模型和事件处理模型
- 易用性API接口
- 对数据协议、序列化的支持
IO模型
- IO请求分为两个阶段,调用阶段和执行阶段
- IO调用阶段:用户进程向内核发起系统调用,即请求数据发出的过程
- IO执行阶段:内核等待IO请求处理完成(包括网络IO和磁盘IO)返回。又可分为两个阶段
- 等待数据就绪,写入内核缓存区,即你需要的数据,已经写入的内存中,但是无法给用户来使用
- 将内核缓存区数据拷贝到用户态缓存区,此过程正是将数据拷贝到用户可以使用的内存中,让用户使用
- 五种IO模型
- 同步阻塞IO
- 同步非阻塞IO
- IO多路复用
- 信号驱动IO:内核通过信号通知用户态可以获取结果,用户态发起请求,获取结果
- 异步IO:数据处理完成,直接从内核缓冲区拷贝到用户缓冲区,无需由用户缓存区,再发起一次获取数据的请求
- Netty的IO模型
- 基于非阻塞IO模型,底层依赖的是JDK NIO框架的多路复用器Selector,一个多路复用器Selector可以同时轮询多个Channel,采用epoll模式后,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端
- 当数据准备就绪时,需要一个事件分发器,有两种设计模式
- Reactor:采用同步IO,适合处理耗时短的场景,对于耗时长的IO操作容易造成阻塞
- Proactor:采用异步IO,实现逻辑复杂
- Netty相比于JDK NIO的优势
- 易用性,JDK NIO很多复杂的概念,Netty进行了封装
- 稳定性
- 可扩展性
- 可定制化线程模型(源码重点)
- 可扩展的事件驱动模型,将框架层和业务层的关注点分离(额。。。,后面关注源码看看)
- 更低的资源消耗
- 对象池复用技术
- 零拷贝技术
第二节、Netty开始
Netty整体架构
- 三部分:Protocol负责服务之间的协议定义,相当于语言,而Transport用于将数据进行传递,而核心逻辑有Core服务提供支持
- Core核心层
- 提供了底层网络通信的通用抽象和实现,包括可扩展的事件模型、通用的通信API、支持零拷贝的ByteBuf等
- Protocol Support协议支持层
- 基本上支持了主流协议的编码实现
- Transport Service传输服务层
- 提供了网络传输能力的定义和实现方法。支持Socket HTTP隧道
- Core核心层
- 那么Netty如何分层呢???
Netty的逻辑处理架构
- 为典型的网络分层架构设计
- 网络通信层
- 事件调度层
- 服务编排层
网络通信层
- 核心组件
- BootStrap
- ServerBootStrap
- Channel
BootStrap & ServerBootStrap
- BootStrap是引导的意思,主要负责整个Netty程序的启动、初始化、服务器连接等过程,相当于一条主线,串联了Netty的其他核心组件
- BootStrap用于连接远端服务器,只绑定一个EventLoopGroup
- ServerBootStrap用于服务端启动绑定本地端口,会绑定两个EventLoopGroup,通常称为Boss和Worker
- Boss用来不断接受新的连接
- Worker用来进行做事情
Channel
- 通道,是指网络通信的载体,提供了基本的API用于网络I/O操作,提供了与底层Socket交互的能力。
- 存在多个状态:连接建立,连接注册,数据读写,连接销毁,所以,对于连接而已就是通道,就是Channel相关的状态,随着状态的变化,Channel处于不同的生命周期中,每一个状态都会有一个绑定的回调事件
- 那么Channel在不同生命周期时,具体执行的业务逻辑,或者谁来执行Channel所绑定的事件呢???答案就是事件调度层来执行
事件调度层
- 事件调度层就是执行Channel中绑定的事件,职责是通过Reactor线程模型对各类事件进行聚合处理,通过Selector主循环线程集成多种事件(I/O事件、信号事件、定时事件等),实际的业务处理逻辑是交由服务编排层相关的Handler完成。
- 核心组件:EventLoopGroup、EventLoop
EventLoopGroup & EventLoop
- EventLoopGroup本质是一个线程池,负责接收I/O请求,并分配线程执行处理请求
- EventLoopGroup、EventLoop、Channel的关系
- 一个EventLoopGroup往往包含一个或者多个EventLoop.EventLoop用于处理Channel生命周期内的所有I/O事件,如accept、connect等I/O事件
- EventLoop同一时间会与一个线程绑定,每个EventLoop负责处理多个Channel
- 当一个连接到达时,Netty 就会注册一个 Channel,然后从 EventLoopGroup 中分配一个 EventLoop 绑定到这个Channel上,在该Channel的整个生命周期中都是由这个绑定的 EventLoop 来服务的。
事件编排层
- 职责:负责组装各类服务,是Netty的核心处理链,用以实现网络事件的动态编排和有序传播(感觉有点责任链的样子呀,后面看看具体的源码逻辑)
- 核心组件
- ChannelPipeline
- ChannelHandler
- ChannelHandlerContext
ChannelPipeline
- ChannelPipeline负责组装各种ChannelHandler,实际数据的操作交由ChannelHandler进行处理完成。ChannelPipeline可以理解为ChannelHandler的实例列表–内部通过双向链表将ChannelHandler链接在一起
- ChannelPipeline是线程安全的,因为每一个新的Channel都会对应绑定一个新的ChannelPipeline。一个ChannelPipeline关联一个EventLoop,一个EventLoop仅会绑定一个线程
ChannelHandler & ChannelHandlerContext
- 没创建一个Channel就会绑定一个新的ChannelPipeline,ChannelPipeline中每加入一个ChannelHandler都会绑定一个ChannelHandlerContext。那么为何要绑定呢
- ChannelHandlerContext用于保存ChannelHandler上下文,通过ChannelHandlerContext
- 可以知道ChannelPipeline和ChannelHandler的关联关系(源码看看)
- ChannelHandlerContext可以实现ChannelHandler之间的交互
- ChannelHandlerContext包含了ChannelHandler生命周期的所有时间,如connect,bind,read等等
第三节、客户端和服务端启动都干了啥???
基本案例
- 流程分为:
- 创建引导器
- 配置线程模型
- 通过引导器绑定业务逻辑处理器
- 配置一些网络参数
- 绑定端口
- 代码
- 启动类
public class HttpServer { public void start(int port) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); //1. 配置线程池 b.group(bossGroup, workerGroup) //2. 设置Channel //2.1 设置Channel类型 .channel(NioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) //2.2 设置ChannelPipeline和ChannelHandler .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ch.pipeline() .addLast("codec", new HttpServerCodec()) // HTTP 编解码 .addLast("compressor", new HttpContentCompressor()) // HttpContent 压缩 .addLast("aggregator", new HttpObjectAggregator(65536)) // HTTP 消息聚合 .addLast("handler", new HttpServerHandler()); // 自定义业务逻辑处理器 } }) .childOption(ChannelOption.SO_KEEPALIVE, true); //3. 绑定端口 ChannelFuture f = b.bind().sync(); System.out.println("Http Server started, Listening on " + port); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { new HttpServer().start(8088); } }
- 逻辑处理类
public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { String content = String.format("Receive http request,uri:%s,method:%s,content:%s%n",msg.getUri(),msg.getMethod(),msg.content().toString(CharsetUtil.UTF_8)); FullHttpResponse response = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(content.getBytes())); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } }
Netty服务端启动
- 启动三个步骤
- 配置线程池
- Channel初始化
- 端口绑定
配置线程池
单线程模式
- Reactor单线程模型所有I/O操作都由一个线程完成,所以只需要启动一个EventLoopGroup即可
- 代码
EventLoopGroup group = new NioEventLoopGroup(1); ServerBootstrap b = new ServerBootstrap(); b.group(group)
- 问题:
- 所有I/O都是一个线程完成,所以单线程模型有非常严重的性能瓶颈
多线程模型
- 因为Reactor单线程存在问题,那么多线程就出现了,在Netty中使用Reactor多线程模型与单线程模型非常相似,区别在于NioEventLoopGroup可以不需要任何参数,他默认会启动2倍的CPU核数的线程,当然,也可以自定义线程数
- 代码
EventLoopGroup group = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(group)
主从多线程模式
- 主从多线程Reactor模型。Boss是主Reactor,Worker是从Reactor。他们分别使用不同的NioEventLoopGroup,主Reactor负责处理Accept,然后把Channel注册到从Reactor上,从Reactor主要负责Channel生命周期内的所有I/O事件。
- 代码
EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup)
Channel初始化
设置Channel类型
- NIO模型是Netty中最成熟且被广泛使用的模型,因此,推荐Netty服务端小勇NioServerSocketChannel作为Channel类型,客户端采用NioSocketChannel。
注册ChannelHandler
- Netty中通过ChannelPipeline去注册多个ChannelHandler,每个ChannelHandler各司其职,这样就可以实现最大化的代码复用
- 如何通过引导器添加多个ChannelHandler或者说是,如何添加ChannelPipeline呢???
- Channel初始化时都会绑定一个Pipeline,它主要用于服务编排。Pipeline管理了多个ChannelHandler。I/O事件依次在ChannelHandler中传播,ChannelHandler负责业务逻辑处理。
设置Channel参数
端口绑定
- 代码:bind方法会真正触发启动,sync方法则会阻塞,直至整个启动过程完成
ChannelFuture f = b.bind().sync();