文章目录
Netty原理与基础
Netty简介
引用Netty官网的介绍
Netty是为了快速开发可维护的高性能,高可扩展,网络服务器和客户端程序而提供的异步事件驱动基础框架和工具
第一个Netty的案例实践DiscardServer
案例功能
读取客户端的输入数据,直接丢弃,不给客户端任何回复
Netty项目依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
第一个Netty服务器端程序
package com.wangyg.netty.ch06.NettyDiscardServer;
import com.wangyg.netty.basic.NettyDemoConfig;
import com.wangyg.netty.basic.NettyDiscardHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import util.Logger;
public class NettyDiscardServer {
private final int serverPort;
/**
* netty服务器启动类serverBootstrap, 它的职责是一个组黄和集成器,
* 将不同的Netty组件组装在一起
*/
ServerBootstrap b =new ServerBootstrap();
//通过构造函数
public NettyDiscardServer(int port){
this.serverPort = port;
}
public void runServer(){
//创建反应器线程组
//boss线程
EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);
EventLoopGroup workerLoopGroup = new NioEventLoopGroup(); //自动根据当前机器创建2*n的线程个数
try {
//设置反应器线程组
b.group(bossLoopGroup, workerLoopGroup);
//设置nio类型的通道
b.channel(NioServerSocketChannel.class); //设置对应的Nio通道类型
//设置监听端口
b.localAddress(serverPort);
//设置通道的参数
b.option(ChannelOption.SO_KEEPALIVE, true); //设置长连接
b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
//配置子通流水线
b.childHandler(new ChannelInitializer<SocketChannel>() {
//初始化方法
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//流水线管理子通道中的handler处理器
//向子通道流水线添加一个handler 处理器
ch.pipeline().addLast(new NettyDiscardHandler());
}
});
//开始绑定服务器
ChannelFuture channelFuture = b.bind().sync();
Logger.info("服务器启动陈宫,监听端口:" + channelFuture.channel().localAddress());
//
ChannelFuture close = channelFuture.channel().closeFuture();
close.sync();
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭所有资源包括创建的线程
workerLoopGroup.shutdownGracefully();
bossLoopGroup.shutdownGracefully();
}
}
//main函数
public static void main(String[] args) {
int port = 8888;
new NettyDiscardServer(port).runServer();
}
}
Reactor反应器
反应器的作用是进行IO事件的查询和dispatch分发,Netty中的反应器组件有多种,应用场景不同,用到的反应器也各不相同,一般来说,对于多线程的JAVA Nio通信场景,Netty的反应器类型为: NioEventLoopGroup
NettyDiscardHandler
package com.wangyg.netty.basic;
import com.wangyg.netty.basic.util.Logger;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
/**
* 业务处理器nettyDiscardHandler
**/
public class NettyDiscardHandler extends ChannelInboundHandlerAdapter { //入栈和出栈,入栈就是输入,出栈就是输出
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
try {
Logger.info("收到消息,丢弃如下:");
while (in.isReadable()) {
System.out.print((char) in.readByte());
}
System.out.println();
} finally {
ReferenceCountUtil.release(msg);
}
}
}
解密Netty中的Reactor反应器模式
设计模式是Java代码或者程序的重要组织方式,如果不了解设计模式,学习Java程序往往找不到头绪,所以,首先要了解Netty中的反应器模式的实现
Reactor反应器的IO事件的处理流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7o0kp6Sz-1574496521141)(78363C70968A4195AB3726B3DF01169E)]
流程分为4步:
- 通道注册: IO源于通道,IO和通道是强相关的,一个IO事件一定属于某个通道,如果想要查询通道的事件,首先要将通道注册到选择器
- 查询选择
- 事件分发
- 完成真正的IO操作和业务处理
Netty中的Channel通道组件
Channel通道组件是Netty中非常重要的组件,因为反应器模式和通道紧密相关,反应器的查询和分发的IO事件都来自于Channel通道组件
Netty对通道进行了自己的封装,在Netty中有一系列的channel通道组件,对于每一种通信协议,Netty都实现了自己的通道
Netty中的Reactor反应器
反应器模式中,一个反应器会负责一个事件处理器,不断轮询, 通过Selector选择器不断查询注册过的IO事件,则分发Handler业务处理器
Netty中的Handler处理器
Java NIO的时间类型,可供选择器监控的通道IO事件类型包括以下4种:
- 可读: OP_READ
- 可写: OP_WRITE
- 连接: OP_CONNECT
- 接收: OP_ACCEPT
Netty中,EventLoop反应器内部有一个Java NIO选择器成员 执行以上事件的查询,然后进行对应的事件分发, 事件分发(dispatch)的目标就是Netty自己的Handler处理器
Netty的Handler处理器分类
- 入站处理器: 从通道到InboundHandler入站处理器
- 出栈处理器 : 将数据写入通道
Netty的流水线(pipeline)
Netty反应器模式中各个组件之间的关系
- 反应器 和通道之间是一对多的关系
- 通道和Handler处理器实例之间,是多对多关系
通道和Handler处理器实例的绑定是如何组织?
通道的多个Handler实例
Netty设计了一个特殊的组件,叫做ChannelePipeline(通道流水线), 将绑定到一个通道的多个Handler处理器实例,穿在一起,形成一个流水线, pipeline流水线,默认被设计成一个双向链表,Handler处理器实例被包装成节点,加入到Pipeline中
入栈处理,每一个来自通道的IO事件,都会进入一次pipeline刘书贤,进入第一个Handler处理器后,IO事件会从前向后,不断流动,继续执行下一个Handler处理器
入站处理器Handler执行次序:
从前向后
出栈处理器Handler执行次序
从后向前
为了方便开发者,Netty提供Bootstrap启动器,提升开发效率
详解Bootstrap启动器类
Netty中的,有两个启动器类,分别在服务器客户端
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jYobdQYu-1574496521142)(92A7B14EE4D3489FBA076F8AA48A2F83)]
父子通道
Netty中,每一个NioSocketChannel通道所封装的是Java NIO通道,在往下就是对应到了操作系统底层的socket操作符
操作系统底层的socket操作符分为两类:
- 连接监听类型
负责接收客户独胆的套接字连接,服务器端,一个连接舰艇类型的socket描述符可以接收成千上万的传输类的socket描述符
- 传输数据类型
数据传输类的socket描述符负责传输数据,同一条tcp的socket传输链路,在服务器和客户端,都分别有一个与之对应的数据传输类型的socket描述符
EventLoopGroup线程组
Netty中,一个EventLoop相当于一个子反应器(subReactor), 一个NioEventLoop子反应器拥有一个线程,同时拥有一个Java NIO选择器
使用EventLoopGroup线程组,多个EventLoop线程组成一个EventLoopGroup线程组
Netty的EventLoopGroup线程组就是一个多线程版本的反应器,其中单个EventLoop线程对应一个子反应器(subReactor)
构造函数
构造器初始化时,会按照传入线程数量,在内部构造多个Thread线程和多个EventLoop子反应器(一个线程对应一个EventLoop子反应器) ,进行多线程的IO事件查询和分发
没有传入参数或调用无参构造函数
EventLoopGroup内部线程数为最大可用的CPU处理器的2倍,假设有4个CPU,那么会启动8个EventLoop线程,相当于8个子反应器实例(SubReactor)
Bootstrap的启动流程
Bootstrap的启动流程,也就是Netty组件的组装,配置,以及Netty服务器或客户端的使用。
创建一个服务器端的启动器实例
//创建服务器启动类
ServerBootstrap b = new ServerBootstrap();
Bootstrap启动流程的8个步骤:
第一步:
创建反应器线程组,并赋值给ServerBootstrap启动器类实例
//创建反应器线程组
//创建boss线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//创建worker线程组
EventLoopGroup workerLoopGroup = new NioEventLoopGroup();
//设置反应器线程组
b.group(bossGroup, workerLoopGroup);
设置反应器线程组之前,创建两个NioEventLoopGroup线程组, 一个负责处理连接监听IO事件,名为bossLoopGroup, 另一个负责IO事件和Handler业务处理,名为workerLoopGroup
配置启动器实例
调用b.group()方法
b.group(bossGroup, workerLoopGroup);
第二步:设置通道的IO类型
Netty不仅支持 Java NIO, 也支持阻塞式OIO
b.channel(NioServerSocketChannel.class);
第三步:设置监听端口
int port = 8888;
b.localAddress(new InetSocketAddress(port));
第四步:设置传输通道的配置选项
b.option(ChannelOption.SO_KEEPALIVE, true);
b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
给父通道设置
给子通道设置:
childOption()
设置方法
第5步: 装配子通道的pipeline流水线
//配置子通流水线
b.childHandler(new ChannelInitializer<SocketChannel>() {
//初始化方法
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//流水线管理子通道中的handler处理器
//向子通道流水线添加一个handler 处理器
ch.pipeline().addLast(new NettyDiscardHandler());
}
});
仅装配子通道的流水线,而不需要装配父通道的流水线
原因: 父通道也就是NioServerSocketChannel连接接收通道,内部业务处理是固定的: 接收新连接后,创建子通道,然后初始化子通道,所以不需要特别的配置,如果需要完成特殊的业务处理,可以使用ServerBootstrap的handler()方法,为父通道设置ChannelInitializer
第六步: 开始绑定服务器新连接的监听端口
//开始绑定服务器
ChannelFuture channelFuture = b.bind().sync();
Logger.info("服务器启动陈宫,监听端口:" + channelFuture.channel().localAddress());
b.bind()方法的功能: 返回一个端口绑定Netty的异步任务ChannelFuture, 在这里,并没有给channelFuture异步任务增加回调监听器,而是阻塞channelFuture异步任务,直到端口绑定任务执行完成
第7步: 自我阻塞,直到通道关闭
ChannelFuture close = channelFuture.channel().closeFuture();
close.sync();
如果要阻塞当前线程直到通道关闭,可以使用通道closeFuture()方法,以获取通道关闭的异步任务,当通道关闭时,closeFuture实例的sync()方法会放回
第8步: 关闭EventLoopGroup
//关闭所有资源包括创建的线程
workerLoopGroup.shutdownGracefully();
bossLoopGroup.shutdownGracefully();
关闭Reactor反应器线程组,同时会关闭内部的subReactor子反应器线程,也会关闭内部的Selector选择器,内部轮询线程以及负责查询的所有子通道,在子通道关闭后,会释放底层的资源。