文章目录

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步:

  1. 通道注册: IO源于通道,IO和通道是强相关的,一个IO事件一定属于某个通道,如果想要查询通道的事件,首先要将通道注册到选择器
  2. 查询选择
  3. 事件分发
  4. 完成真正的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反应器模式中各个组件之间的关系

  1. 反应器 和通道之间是一对多的关系
  2. 通道和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选择器,内部轮询线程以及负责查询的所有子通道,在子通道关闭后,会释放底层的资源。