您好,登录后才能下订单哦!
这篇文章主要介绍“netty的使用方法是什么”,在日常操作中,相信很多人在netty的使用方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”netty的使用方法是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
Netty 是由 JBOSS 提供的一个异步的、 基于事件驱动的网络编程框架。Netty 可以帮助你快速、 简单的开发出一 个网络应用, 相当于流程化及简化了 NIO 的开发过程。知名的 Elasticsearch 、 Dubbo 框架内部都采用了 Netty。
NIO缺点
NIO 的类库和 API 繁杂,使用麻烦。你需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等.
可靠性不强,开发工作量和难度都非常大
NIO 的 Bug。例如 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。
Netty优点
对各种传输协议提供统一的 API
高度可定制的线程模型——单线程、一个或多个线程池
更好的吞吐量,更低的等待延迟
更少的资源消耗
最小化不必要的内存拷贝
Reactor模式是基于事件驱动开发的,核心组成部分包括Reactor和线程池,其中Reactor负责监听和分配事件,线程池负责处理事件。Netty线程模型就是Reactor模式的一个实现。
无论是 C++ 还是 Java 编写的网络框架,大多数都是基于 Reactor 模式进行设计和开发,Reactor 模式基于事件驱动,特别适合处理海量的 I/O 事件。
Reactor的数量和线程池的数量,又将Reactor分为三种模型:
单线程模型 (单Reactor单线程)
多线程模型 (单Reactor多线程)
主从多线程模型 (多Reactor多线程)。
Reactor 单线程模型,指的是所有的 IO 操作(接收请求、业务逻辑处理)都在同一个 NIO 线程上面完成。
NIO 线程的特点如下:
作为 NIO 服务端,接收客户端的 TCP 连接
作为 NIO 客户端,向服务端发起 TCP 连接;
读取通信另一端的请求或者应答消息;
向通信另一端发送请求消息或者应答消息
Reactor内部通过selector 监控连接事件,收到事件后通过dispatch进行分发,如果是连接建立的事件,则由Acceptor处理,Acceptor通过accept接受连接,并创建一个Handler来处理连接后续的各种事件,如果是读写事件,直接调用连接对应的业务Handler来处理。
适用场景:
对于一些小容量应用场景,可以使用单线程模型。
但是对于高负载、大并发的应用场景却不合适。 一个 NIO 线程同时处理成百上千的链路,性能上无法支撑,即便 NIO 线程的 CPU 负荷达到 100%,也无法满足海量消息的编码、解码、读取和发送。
Reactor多线程模型,仍然只有一个Nio线程用于监听连接请求;而业务逻辑处理由一个线程池负责。
主线程中,Reactor对象通过selector监控连接事件,收到事件后通过dispatch进行分发,如果是连接建立事件,则由Acceptor处理,Acceptor通过accept接收连接,并创建一个Handler来处理后续事件,而Handler只负责响应事件,不进行业务操作,也就是只进行read读取数据和write写出数据,业务处理交给一个线程池进行处理。
线程池分配一个线程完成真正的业务处理,然后将响应结果交给主进程的Handler处理,Handler将结果发送给客户端
适用场景:
在绝大多数场景下,Reactor 多线程模型都可以满足性能需求;
但是,在极个别特殊场景中,一个 NIO 线程负责监听和处理所有的客户端连接可能会存在性能问题。例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能。在这类场景下,单独一个线程可能会存在性能不足问题。
相对于多线程模型,服务端用于接收客户端连接的不再是个 1 个单独的 NIO 线程,而是一个独立的 NIO 线程池。Acceptor 线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 IO 线程上,由 IO 线程负责后续的 IO 操作。
存在多个Reactor,每个Reactor都有自己的selector选择器,线程和dispatch
主线程中的mainReactor通过自己的selector监控连接建立事件,收到事件后通过Accpetor接收,将新的连接分配给某个子线程
子线程中的subReactor将mainReactor分配的连接加入连接队列中通过自己的selector进行监听,并创建一个Handler用于处理后续事件
Handler完成read->业务处理->send的完整业务流程
单线程模型就是只指定一个线程执行客户端连接和读写操作,也就是在一个Reactor中完成,对应在Netty中的实现就是将NioEventLoopGroup线程数设置为1,核心代码是:
NioEventLoopGroup group = new NioEventLoopGroup(1); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(group) .channel(NioServerSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ServerHandlerInitializer());
工作流程如下:
多线程模型就是在一个单Reactor中进行客户端连接处理,然后业务处理交给线程池,核心代码如下:
NioEventLoopGroup eventGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(eventGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ServerHandlerInitializer());
工作流程如下:
主从多线程模型是有多个Reactor,也就是存在多个selector,所以我们定义一个bossGroup和一个workGroup,核心代码如下:
NioEventLoopGroup bossGroup = new NioEventLoopGroup(); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup,workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ServerHandlerInitializer());
工作流程如下:
注意:其实在Netty中,bossGroup线程池最终还是只会随机选择一个线程用于处理客户端连接,与此同时,NioServerSocetChannel绑定到bossGroup的线程中,NioSocketChannel绑定到workGroup的线程中
ChannelHandler 接口定义了许多事件处理的方法,可以通过重写这些方法去实现具体的业务逻辑。经常需要自定义一个 Handler 类去继承 ChannelInboundHandlerAdapter, 然后通过重写相应方法实现业务逻辑
public void channelActive(ChannelHandlerContext ctx), 通道就绪事件 public void channelRead(ChannelHandlerContext ctx, Object msg), 通道读取数据事件 public void channelReadComplete(ChannelHandlerContext ctx) , 数据读取完毕事件 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause), 通道发生异常事件
ChannelPipeline 是一个 Handler 的集合, 它负责处理和拦截 inbound 或者 outbound 的事 件和操作, 相当于 一个贯穿 Netty 的链。
ChannelPipeline addFirst(ChannelHandler... handlers), 把一个业务处理类(handler) 添加到链中的第一个位置 ChannelPipeline addLast(ChannelHandler... handlers), 把一个业务处理类(handler) 添加到链中的最后一个位置
ChannelHandlerContext是事件处理器上下文对象 ,Pipeline链中的实际处理节点 。每个处理节点ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时ChannelHandlerContext中也绑定了对应的pipeline和Channel的信息
ChannelFuture close(), 关闭通道 ChannelOutboundInvoker flush(), 刷新 ChannelFuture writeAndFlush(Object msg) , 将数据写到ChannelPipeline中,当前ChannelHandler的下一个ChannelHandler 开始处理(出站)
ChannelFuture用来表示 Channel 中异步 I/O 操作的结果, 在 Netty 中所有的 I/O 操作都是异步的, I/O 的调用会直接返回, 调用者并不能立刻获得结果, 但是可以通过 ChannelFuture 来获取 I/O 操作的处理状态。
Channel channel(), 返回当前正在进行 IO 操作的通道 ChannelFuture sync(), 等待异步操作执行完毕
EventLoopGroup 是一组 EventLoop 的抽象, Netty 为了更好的利用多核 CPU 资源, 一般会有多个 EventLoop 同时工作, 每个EventLoop 维护着一个Selector 实例。
EventLoopGroup 提供 next 接口, 可以从组里面按照一定规则获取其中一个 EventLoop 来处理任务。
在 Netty 服务器端编程中,我们一般都需要提供两个EventLoopGroup, 例如: BossEventLoopGroup和WorkerEventLoopGroup。
ServerBootstrap 是 Netty 中的服务器端启动助手,通过它可以完成服务器端的各种配置; Bootstrap 是 Netty 中 的客户端启动助手, 通过它可以完成客户端的各种配置。
ServerBootstrap:
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup),该方法用于服务器端, 用来设置两个 EventLoop public B channel(Class<? extends C> channelClass), 该方法用来设置一个服务器端的通道实现 public <T> B option(ChannelOption<T> option, T value), 用来给 ServerChannel 添加配置 public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value), 用来给接收到的通道添加配置 public ServerBootstrap childHandler(ChannelHandler childHandler), 该方法用来设置业务处理类(自定义的 handler) public ChannelFuture bind(int inetPort) , 该方法用于服务器端, 用来设置占用的端口号
Bootstrap:
public ChannelFuture connect(String inetHost, int inetPort) 该方法用于客户端, 用来连接服务器端 public B group(EventLoopGroup group) , 该方法用于客户端, 用来设置一个 EventLoop
服务端:
public static void main(String[] args) throws InterruptedException { // 1. 创建二个线程池对象 /** * bossGroup 负责接受用户连接 */ NioEventLoopGroup bossGroup = new NioEventLoopGroup(); /** * workGroup 负责用户的io读写操作 */ NioEventLoopGroup workGroup = new NioEventLoopGroup(); /** * 2.创建启动引导类 */ ServerBootstrap serverBootstrap = new ServerBootstrap(); // 3.设置启动引导类 // 设置组,第一个bossGroup负责连接, workerGroup负责连接之后的io处理 serverBootstrap.group(bossGroup,workGroup) // channel方法指定服务器监听的通道类型 .channel(NioServerSocketChannel.class) //设置channel handler , 每一个客户端连接后,给定一个监听器进行处理 .childHandler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception { //获取传输通道 ChannelPipeline pipeline = nioSocketChannel.pipeline(); //在通道上添加对通道的处理器 , 该处理器可能还是一个监听器 pipeline.addFirst(new StringEncoder()); pipeline.addLast(new StringDecoder()); //监听器队列上添加我们自己的处理方式.. pipeline.addLast(new SimpleChannelInboundHandler<String>() { @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception { System.out.println(msg); } }); } }); // 4.启动引导类绑定端口 ChannelFuture future = serverBootstrap.bind(8888).sync(); // 5.关闭通道 Channel channel = future.channel(); channel.closeFuture().sync(); }
客户端:
public static void main(String[] args) throws InterruptedException { // 1.创建连接池对象 NioEventLoopGroup group = new NioEventLoopGroup(); // 2. 创建客户端的启动引导类 Bootstrap bootstrap = new Bootstrap(); // 3. 配置启动引导类 bootstrap.group(group) // 设置通道为nio .channel(NioSocketChannel.class) // 设置channel初始化监听 .handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel channel) throws Exception { // 设置编码 channel.pipeline().addLast(new StringEncoder()); } }); // 4.使用启动引导类连接服务器,获取一个channel Channel channel = bootstrap.connect("127.0.0.1", 8888).channel(); // 5.循环写数据给服务器 while(true){ // 给服务器写数据 channel.writeAndFlush("hello server.. this is clint say to you.."); Thread.sleep(2000); } }
使用Netty自定义实现Rpc
到此,关于“netty的使用方法是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。