一、什么是Netty?
关于这个问题,我贴一段官网的阐述:
Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients.
大概意思是:
Netty 是一个 NIO 客户端服务器框架,使用它可以快速简单地开发网络应用程序,比如服务器和客户端的协议。
具体来说,Netty就是对于Java NIO 的封装。NIO是Java 1.4 后引入的基于事件模型的非阻塞IO框架,在NIO之前,对于数据的处理都是基于BIO(Blocking IO)的,闻其名知其意,BIO是以阻塞的形式来对数据进行处理的,虽然这种方式处理起来比较简单,但是由于其阻塞特性会涉及到线程的上下文切换操作,导致 BIO在高并发场景下略显吃力。
相对的,NIO能够较好的应对这些场景。但是Java推出的NIO类库和API繁杂,使用起来比较麻烦且容易出现Bug。因此出现了一系列解决NIO问题的框架,Netty就是其中最著名的一个。
Netty有哪些特点?
- 基于事件机制(Pipeline-Hhandler)达成关注点分离(消息编解码、协议编解码、业务处理);
- 可定制的线程处理模型、单线程、多线程池等;
- 屏蔽NIO本身的Bug;
- 性能上的优化;
- 相较于NIO接口功能更丰富;
- 对外提供统一的接口,底层支持BIO与NIO两种方式,可自由切换。
二、Netty总体架构
首先从官网摘一张结构图:
总体来说,Netty分为两大模块:核心模块和服务模块
2.1 核心模块
核心模块主要提供Netty的一些基础接类和底层接口,由三部分组成:
Zero-Copy-Capable Rich Byte Buffer
(零拷贝缓冲区):用来提升性能、减少资源占用。Universal Communication API
(统一通信API):Netty为同步和异步IO提供了统一的编程接口。Extensible Event Model
(易扩展的事件模型)
2.2 服务模块
由于Netty的核心是IO,因此其服务模块与IO操作息息相关,主要有:
- 网络接口数据处理相关服务:如报文的粘包、拆包处理,数据的加密、解密等。
- 各网络层协议实现服务:包括传输层和应用层相关网络协议的实现
- 文件处理相关服务
三、Netty处理架构
介绍完Netty总体架构,再来瞧瞧Netty的处理架构,如下图所示:
Netty处理架构很清晰,分为三层:
- 底层的IO复用层(
Reactor
):负责实现多路复用。 - 中间层的通用数据处理层(
PipeLine
):主要作用是对传输层的数据在进出口进行拦截并处理,比如粘包处理、编解码等操作。 - 顶层的应用实现层:开发者使用Netty的时候基本是在这一层进行操作,同时Netty本身已经在这一层提供了一些常用的实现,如
HTTP协议
,FTP协议
等。
整体流程:
数据从网络传输到IO复用层,IO复用层收到数据后将数据传递给通用数据处理层进行处理,这一层会通过一系列的处理 Handler 以及应用服务器对数据进行处理,然后返回给IO复用层,再通过它传回网络。
四、Netty线程模型介绍
不同的线程模型对程序的性能有很大的影响,目前存在的线程模型有传统阻塞I/O服务模型 Reactor 模式。根据 Reactor 的数量和处理资源池线程的数量不同又可以分为三种典型实现,分别是单 Reactor 单线程、单 Reactor 多线程、主从 Reactor 多线程。Netty 主要基于主从 Reactor 多线程模型做了一定的改进,其中主从 Reactor 多线程模型有多个 Reactor 。下图为主从 Reactor 多线程模型图:
其运行流程如下:
- 首先
Reactor
主线程MainReactor
对象通过select
监听连接事件,收到事件后,通过Acceptor
处理连接事件。 - 当
Acceptor
处理连接事件后,MainReactor
将连接分配给SubReactor
,然后SubReactor
将连接加入到连接队列进行监听,并创建handler
进行各种事件处理。 - 当有新的事件发生时,
SubReactor
就会调用对应的handler
来进行处理。handler
通过read
读取数据,分发给后面的worker
线程处理。 worker
线程池分配独立的worker
线程进行业务处理,并返回结果。handler
收到响应结果后,再通过send
将结果返回给client
。
优点: 父线程与子线程的数据交互简单、职责明确,父线程只负责接受新连接,子线程完成后续的业务处理。
Netty工作原理示意图如下:
Netty 抽象出了两组线程池BossGroup
和WorkerGroup
,其中BossGroup
专门负责接受客户端的连接,WorkerGroup
专门负责网络的读写。BossGroup
和WorkerGroup
的类型都是NioEventLoopGroup
。NioEventLoopGroup
相当于一个事件循环组,这个组种含有多个事件循环,每一个事件循环是一个NioEventLoop
。NioEeventLoop
表示一个不断循环执行处理任务的线程,可以有多个线程,也就是说可以含有多个NioEventLoop
,每个NioEventLoop
都有一个Selector
,用于监听绑定在其上的socket
的网络通讯。
每个BossNioEventLoop
循环执行的步骤有3步:①轮询accept
事件 ②处理accept
事件,与client
建立连接,生成 NioSocketChannel
,并将其注册到某个WorkerNioEventLoop
上的Selector
③处理任务队列的任务,即runAllTasks
。
每个WorkerNioEventLoop
循环执行的步骤也有3步:①轮询read、write
事件 ②处理I/O事件,即read、write
事件,在对应NioSocketChannel
处理 ③处理任务队列的任务,即runAllTasks
。
每个WorkerNioEventLoop
处理业务时,会使用pipeline
(管道),pipeline
中包含了channel
,也就是说通过pipeline
可以获取到对应通道,管道中维护了很多的处理器。
五、Netty光速入门一:TCP服务
接下来使用Netty实现TCP服务,首先Netty服务器监听9999
端口,客户端发送消息给服务器 "hello, world!"
。服务器回复消息 “hello,please stop the war!”
给客户端。
- 导入Netty依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.18.Final</version>
</dependency>
- NettyServer.java
package org.lwz.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
public static void main(String[] args) {
/**
* 流程:
* 1.创建两个线程组 bossGroup 和 workerGroup
* 2.bossGroup只负责连接请求的处理,workerGroup完成客户端业务处理
* 3.两个线程组都置为无线循环
* 4.bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数默认为 实际cpu核数*2
*/
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 线程数1
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 线程数8
try{
// 创建服务端的启动对象
ServerBootstrap bootStrap = new ServerBootstrap();
// 配置相应参数
bootStrap.group(bossGroup, workerGroup) // 设置两个线程组
.channel(NioServerSocketChannel.class) // 使用NioSocketChannel
.option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列的连接个数
.childOption(ChannelOption.SO_KEEPALIVE, true) // 设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() { // 创建通道初始化对象
// 给pipeline设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
System.out.println("客户端socketChannel hashCode = " + socketChannel.hashCode());
socketChannel.pipeline().addLast(new NettyServerHandler());
}
}); // 给workerGroup的EventLoop对应的管道配置处理器
System.out.println("!!服务器已准备好!!");
// 绑定端口并且同步,生成一个ChannelFuture对象
// 启动服务器(并绑定端口)
ChannelFuture channelFuture = bootStrap.bind(9999).sync();
// 为channelFuture注册监听器,监控关心的事件是否发生
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if(channelFuture.isSuccess()){
System.out.println("监听端口 9999 成功");
} else {
System.out.println("监听端口 9999 失败");
}
}
});
// 对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
- NettyServerHandler.java
package org.lwz.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 1. ChannelHandlerContext ctx:上下文对象,含有管道pipeline,通道channel,地址
* 2. Object msg:客户端发来的数据,默认Object
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务器读取线程 " + Thread.currentThread().getName() + " channel = " + ctx.channel());
System.out.println("server ctx = " + ctx);
// 获取channel
Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline();
// 将 msg 转成 ByteBuf
ByteBuf buf = (ByteBuf) msg;
System.out.println("客户端发来消息:" + buf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址:" + channel.remoteAddress());
}
// 数据读取完毕
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// writeAndFlush 是 write + flush 操作, 将数据写入到缓存并刷新
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,please stop the war!", CharsetUtil.UTF_8));
}
// 有异常时回调到这里,一般是需要关闭通道
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
- NettyClient.java
package org.lwz.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyClient {
public static void main(String[] args) {
// 客户端需要一个事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try{
// 创建客户端启动对象
Bootstrap bootStrap = new Bootstrap();
// 配置相关参数
bootStrap.group(group) // 设置线程组
.channel(NioSocketChannel.class) // 设置客户端通道的实现类
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandler()); // 加入客户端的handler
}
});
System.out.println("!!客户端已准备好!!");
// 启动客户端去连接服务端
ChannelFuture channelFuture = bootStrap.connect("127.0.0.1", 9999).sync();
// 监听关闭通道
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
group.shutdownGracefully();
}
}
}
- NettyClientHandler.java
package org.lwz.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
// 当通道就绪时就会触发该方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端: " + ctx);
// 向服务端发送数据
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,world!", CharsetUtil.UTF_8));
}
// 当通道有读取事件时,会触发该方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("服务端回复的消息: " + buf.toString(CharsetUtil.UTF_8));
System.out.println("服务器的地址: " + ctx.channel().remoteAddress());
}
// 发生异常时回调,关闭通道
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- 启动
NettyServer.java
以及NettyClient.java
,得到测试结果如下: