目录
Netty的概念
什么是Netty
Netty是一个用Java开发的开源框架,是一个基于NIO的异步的、基于事件驱动的网络IO应用框架,使用Netty可以快速开发高性能、高可靠的网络IO程序,Netty被使用在各种中间件中为各节点提供通信服务,比如dubbo,以及在游戏行业中实现服务器之间的通信
简单的Java实现
1、引入Maven依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.35.Final</version>
</dependency>
2、NettyServerDemo
public class NettyServerDemo {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("NettyServer start....");
ChannelFuture channelFuture = bootstrap.bind(9000).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
/**
* 业务实现类,我们只需要在各种重写方法中实现业务逻辑即可
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("Server收到消息:" + buf.toString(CharsetUtil.UTF_8));
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ByteBuf byteBuf = Unpooled.copiedBuffer("this is from server", CharsetUtil.UTF_8);
ctx.writeAndFlush(byteBuf);
}
}
3、NettyClientDemo
public class NettyClientDemo {
public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("netty client start...");
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9000).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
/**
* 客户端的业务实现类
*/
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf byteBuf = Unpooled.copiedBuffer("this is from client", CharsetUtil.UTF_8);
ctx.writeAndFlush(byteBuf);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("收到客户端消息:" + byteBuf.toString(CharsetUtil.UTF_8));
}
}
Netty底层原理
Netty线程模型
这是转载自网上的一张Netty线程架构图,对应这张图我们可以逐步理解NettyDemo的代码,以下是转载的内容:
Netty使用两个NioEventLoopGroup分别处理客户端的连接请求和读写请求,一个叫BossGroup,一个叫WorkerGroup,NioEventLoopGroup是一个由多个事件循环线程NioEventLoop组成的事件循环线程组,每个NioEventLoop都包含一个selector,用于监听注册在selector上的socketChannel网络通讯,每个BossGroup内的NioEventLoop线程执行以下步骤:
1、处理客户端的accept事件,与client建立连接,每个client生成一个NioSocketChannel
2、将NioSocketChannel注册到WorkGroup中的某个NioEventLoop中的selector上
3、处理任务队列中的任务
每个WorkerGroup内的NioEventLoop线程循环执行以下步骤:
1、轮询注册到自己的selector上的所有NioSocketChannel的读写事件
2、处理读写事件,执行业务逻辑
3、处理任务队列中的任务
每个Worker NioEventLoop处理NioSocketChannel业务时,会使用pipeline,pipeline维护了很多handler处理器用来处理channel中的数据,同理我们也可以将自定义的handler加入到管道中来实现自己的业务逻辑
Netty中的术语
Bootstrap、ServerBootstrap:
Netty的client和server引导类,负责添加netty的配置和自定义handler
Future、ChannelFutrure:
通过Future和ChannelFuture实现对Netty启动或关闭操作结果的监听,因为这些操作都是异步的,不会直接返回结果
Channel:
实现网络通信的通道,客户端和服务端通过Channel发送接收请求和执行IO操作,这些操作都是异步的,常用的Channel类型:
NioServerChannel 异步的客户端TCP Socket连接
NioServerSocketChannel 异步的服务器端TCP Socket连接
NioDatagramChannel 异步的UDP连接
NioSctpChannel 异步的客户端Sctp连接
NioSctpServerChannel 异步的Sctp服务器端连接
Selector:多路复用器,Netty是基于NIO的,客户端的socketchannel都会被注册到工作线程中的selector上,selector会不停的轮询注册的socketchannel是否发生IO事件
NioEventLoop:内部封装了一个线程和一个队列,线程负责执行注册在selector上的channel的IO事件(accept、read、write等)和队列中的task(register、bind等)
NioEventLoopGroup:由NioEventLoop组成的集合,负责为socketChannel分配NioEventLoop
ChannelHandler:顶层接口,负责处理IO事件,链式调用多个handler,一般是继承它的子类ChannelInboundHandler和ChannelOutboundHandler
ChannelHandkerContext:保存Channel的上下文信息,同时关联一个ChannelHandler,可以通过该对象获取channel
ChannelPipline:管道本质是一个由ChannelHandlerContext组成的双向链表,通过链式调用管道中的handler处理IO事件,每个channel都有一个自己的管道,read事件从head到tail顺序执行,write事件从tail到head顺序执行
Netty源码流程
可以参考我这张图理解服务端核心源码
https://www.processon.com/view/link/5f35f87407912920b49b766e
大致流程如下:
Netty启动时会创建一个serversocketChannel,并对应初始化一个pipeline,往pipeline添加一个new ChannelInitializer,然后将serversocketChannel注册到bossGroup中的一个nioEventLoop上的selector上,同时添加异步任务(一个Runnable)到队列里,selector阻塞等待接收client的accept事件,队列中有任务则启动线程执行任务,任务里会执行pipeline中的ChannelInitializer的initChannel方法添加ServerBootHandler到管道里,客户端启动后连接Netty服务端触发accept事件,服务端执行processSelectedKeys()轮询处理accept事件,处理accept事件的实现类为NioMessageUnsafe,首先为连接构造一个socketChannle并初始化一个pipeline,然后回调ServerBootHandler的channelRead方法添加我们new的ChannelInitializer(),然后将socketChannel注册到workerGroup中的一个NioEventLoop上的selector,接下来的流程就和serversocketChannel注册的流程类似了,依然是提交异步任务,selector阻塞等待客户端的read和wirte事件,异步任务中会执行我们new的ChannelInitializer()的initChannle方法把我们的业务handler添加到pipeline中,客户端发送消息触发read事件,处理read事件的实现类为NioByteUnsafe,循环调用pipeline的channelRead方法,最后调用readComplete等方法
Netty架构设计亮点
主从Reator架构模型
架构模型理论图:
Netty架构图:
Netty底层基于NIO实现,架构模型采用主从Reator架构模型,主节点负责处理连接事件,从节点负责处理读写事件,可以有有多个线程
Netty中存储数据的缓存数组
ByteBuf:Netty使用ByteBuf来存储数据,ByteBuf底层是一个字节数组,自带两个索引(指针),一个指向当前将要读取的位置,一个指向当前已写入的位置,Netty读取client发送的数据到byteBuf时是直接申请的堆外内存(直接内存),避免用户态和内核态的转换,即使用零拷贝的方式,大大提高性能
扩容机制:默认阈值4MB,扩容时如果需要使用的容量等于阈值则直接扩容到阈值,如果大于阈值,则以4MB为单位累加,4MB->8MB->12MB->…,如果小于阈值则以64字节为基础,每次翻倍,64->128->256->…,直到到达阈值或达到需要使用的容量
零拷贝提高读写速度
零拷贝不等于不拷贝,只是节省了内核态和用户态之间的拷贝过程,Netty直接操作堆外内存,不需要将数据读到堆内存,但是从堆外内存到网卡也是需要拷贝的
Nio空轮询bug修复
//Netty解决Nio空轮询bug就是用了一个自增的selectCnt
int selectedKeys = selector.select(timeoutMillis);
++selectCnt;
...
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
//正常情况下跳出重置selectCnt
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
//selectCnt超过阈值重新构建一个selector关闭旧的selector
selector = this.selectRebuildSelector(selectCnt);
selectCnt = 1;
break;
}