学习内容主要基于B站UP主,青空の霞光的视频,加上自己的理解。
Netty框架可以解决NIO框架的问题
Netty是由JBOSS提供的一个开源Java网络编程框架,针对Java的nio包进行了再次封装,提供了更加强大、稳定的功能和更易于使用的api。
可以用于游戏服务器,微服务之间的远程调用(比如Dubbo的RPC框架,包括最新的SpringWebFlux框架,也抛弃了内嵌的Tomcat而使用Netty作为通信框架)
导包
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.76.Final</version>
</dependency>
ByteBuf
Netty没有使用ByteBuffer来进行数据装载,而是自定义了一个ByteBuf
- 写操作完成后无需进行
flip()
翻转 - 具有比
ByteBuffer
更快的响应速度 - 动态扩容
public abstract class AbstractByteBuf extends ByteBuf {
...
int readerIndex; //index分为读和写两个指针
int writerIndex;
private int markedReaderIndex; //mark操作也分为两种
private int markedWriterIndex;
private int maxCapacity; //最大容量,可以动态扩容
没写入依次,writeIndex
向后移动一位;每读取依次,readerIndex
向后移动一位。
readerIndex
不能大于writeIndex
,这样就不需要翻转了
readerIndex
和writeIndex
之间的部分就是可读的内容,而writeIndex
到capacity
都是可写的部分
public static void main(String[] args) {
//创建一个初始容量为10的ByteBuf缓冲区,Unpooled是用于快速生成ByteBuf的工具类
//ByteBuf有池化和非池化两种,区别在于对内存的复用
ByteBuf buf = Unpooled.buffer(10);
System.out.println("初始状态:" + Arrays.toString(buf.array()));
buf.writeInt(-888888888); //写入一个Int数据
System.out.println("写入int后:" + Arrays.toString(buf.array()));
//无需反转,直接读取short数据
buf.readShort();
System.out.println("读取short后:" + Arrays.toString(buf.array()));
//丢弃操作:将当前的可读部分内容丢到最前面,并且读写指针向前移动丢弃的距离
buf.discardReadBytes();
System.out.println("丢弃之后:" + Arrays.toString(buf.array()));
//清空操作,清空后读写指针都归零
buf.clear();
System.out.println("清空之后:" + Arrays.toString(buf.array()));
}
初始状态:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
写入int后:[-53, 4, -95, -56, 0, 0, 0, 0, 0, 0]
读取short后:[-53, 4, -95, -56, 0, 0, 0, 0, 0, 0]
丢弃之后:[-95, -56, -95, -56, 0, 0, 0, 0, 0, 0]
清空之后:[-95, -56, -95, -56, 0, 0, 0, 0, 0, 0]
划分操作
public static void main(String[] args) {
ByteBuf buf = Unpooled.wrappedBuffer("abcdefg".getBytes());
//复制数据,copiedBuffer()会将数据拷贝到一个新的缓冲区中
buf.readByte(); //读取一个字节
//现在指针位于1,进行划分
ByteBuf slice = buf.slice();
//得到划分出来的ByteBuf的偏移地址
System.out.println(slice.arrayOffset());
System.out.println(Arrays.toString(slice.array()));
}
1
[97, 98, 99, 100, 101, 102, 103]
动态扩容
在写入一个超出当前容量的数据时,会进行动态扩容,扩容从64开始,之后每次触发都会×2
public static void main(String[] args) {
//申请一个容量为10的缓冲区
ByteBuf buf = Unpooled.buffer(10);
System.out.println(buf.capacity()); //10
//写一个字符
buf.writeCharSequence("睡觉的事件", StandardCharsets.UTF_8);
System.out.println(buf.capacity()); //64
}
如果不想扩容,可以指定最大容量
public static void main(String[] args) {
//申请一个容量为10的缓冲区
ByteBuf buf = Unpooled.buffer(10, 10);
System.out.println(buf.capacity());
//写一个字符
buf.writeCharSequence("睡觉的事件", StandardCharsets.UTF_8);
System.out.println(buf.capacity());
}
10
Exception in thread "main" java.lang.IndexOutOfBoundsException: writerIndex(0) + minWritableBytes(15) exceeds maxCapacity(10): UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 0, cap: 10/10)
缓冲区的三种实现模式
- 堆缓冲区模式:申请数组
- 直接缓冲区模式:使用堆外内存
- 复合缓冲区模式
//直接缓冲区
ByteBuf buf = Unpooled.directBuffer(10);
复合模式,可以任意地拼凑组合其他缓冲区
比如要将两个缓冲区组合的内容进行操作,不用单独创建一个新的缓冲区了,而是将其直接进行拼接操作。相当于是作为多个缓冲区组合的视图
public static void main(String[] args) {
//创建一个符合缓冲区
CompositeByteBuf buf = Unpooled.compositeBuffer();
buf.addComponent(Unpooled.copiedBuffer("abc".getBytes()));
buf.addComponent(Unpooled.copiedBuffer("def".getBytes()));
for (int i = 0; i < buf.capacity(); i++) {
System.out.print((char) buf.getByte(i) + " ");
}
}
a b c d e f
池化缓冲区和非池化缓冲区的区别
Unpooled工具类如何创建buffer
public final class Unpooled {
private static final ByteBufAllocator ALLOC; //内部使用ByteBufAllocator对象
public static final ByteOrder BIG_ENDIAN;
public static final ByteOrder LITTLE_ENDIAN;
public static final ByteBuf EMPTY_BUFFER;
public static ByteBuf buffer() {
return ALLOC.heapBuffer(); //缓冲区创建操作依赖ByteBufAllocator
}
static {
//ByteBufAllocator在静态代码块中进行指定,实际上真正的实现类是UnpooledByteBufAllocator
ALLOC = UnpooledByteBufAllocator.DEFAULT;
BIG_ENDIAN = ByteOrder.BIG_ENDIAN;
LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
EMPTY_BUFFER = ALLOC.buffer(0, 0); //空缓冲区容量和最大容量都为0
assert EMPTY_BUFFER instanceof EmptyByteBuf : "EMPTY_BUFFER must be an EmptyByteBuf.";
}
}
ByteBufAllocator
是负责分配缓冲区的。
它有两个具体实现类:
UnpooledByteBufAllocator
:非池化缓冲区生成器PooledByteBufAllocator
:池化缓冲区生成器。利用了池化思想,将缓冲区通过设置内存池来进行内存块的复用
,这样就不用频繁的进行内存的申请,尤其是使用堆外内存时,避免多次重复通过底层malloc()
函数系统调用申请内存造成性能损失
public static void main(String[] args) {
//创建一个池化缓冲区生成器
PooledByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;
//申请容量为10的直接缓冲区
ByteBuf buf = allocator.directBuffer(10);
buf.writeChar('T');
System.out.println(buf.readChar()); //T
buf.release();
//重新申请一个同样大小的直接缓冲区
ByteBuf buf2 = allocator.directBuffer(10);
System.out.println(buf == buf2); //true
}
PooledByteBufAllocator
是将ByteBuf
实例放在池中进行复用。
使用完一个缓冲区,将资源释放,再申请同样大小的缓冲区时,会直接得到之前申请好的缓冲区
零拷贝
零拷贝的三种方案:
- 使用虚拟内存
让内核空间和用户空间的虚拟地址指向同一个物理地址,这样就相当于是共用了这一块区域,就不用拷贝了
- 使用mmap/write内存映射
将内核空间中的缓存直接映射到用户空间缓存,比如在NIO中使用的MappedByteBuffer,就是直接作为映射存在,当需要将数据发送到Socket缓冲区时,直接在内核空间中进行操作就行了
- 使用sendfile方式
可以直接告诉内核要把哪个文件数据拷贝到Socket上,直接在内核空间中一步到位
比如在NIO中使用的transforTo()
方法
Netty工作模型
Netty的工作模型是以 主从Reactor多线程模型 为基础的
主从Reactor中,所有用户端需要连接到主Reactor
完成Accept操作后,其他的操作由从Reactor
完成
在Netty中:
- Netty抽象出两组线程池
BossGroup
和WorkGroup
BossGroup
专门负责接受客户端的连接WorkGroup
专门负责读写
BossGroup
和WorkGroup
都是使用EventLoop
来进行事件监听的,整个Netty也是使用事件驱动来运作的,比如当前客户端已经准备好读写、建立连接时,都会进行事件通知,就像多路复用那样。EventLoop
:事件循环,一个循环,不断的进行事件通知- 如果有多个
EventLoop
,会存放在EventLoopGroup
中,EventLoopGroup
就是BossGroup
和WorkGroup
的具体实现
- 在
BossGroup
之后,会正常将SocketChannel
绑定到WorkGroup
中的其中一个EventLoop
上,进行后续的读写操作监听。
创建Netty服务器
public static void main(String[] args) {
//使用NioEventLoopGroup实现类创建bossGroup和workerGroup
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
//创建服务端启动引导类
ServerBootstrap bootstrap = new ServerBootstrap();
//链式
bootstrap
.group(bossGroup, workerGroup) //指定事件循环组
.channel(NioServerSocketChannel.class) //指定为NIO的ServerSocketChannel
.childHandler(new ChannelInitializer<SocketChannel>() { //这里的SocketChannel是Netty的
@Override
protected void initChannel(SocketChannel channel) throws Exception {
//获取流水线,当需要处理客户端的数据时,像流水线一样处理,流水线中有很多Handler
channel.pipeline().addLast(new ChannelInboundHandlerAdapter() { //添加一个Handler
//ctx是上下文,msg是收到的消息,默认为ByteBuf形式
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
ByteBuf buf = (ByteBuf) msg; //类型转换
System.out.println(Thread.currentThread().getName() + ">> 接收到客户端发送的数据" + buf.toString(StandardCharsets.UTF_8));
//通过上下文可以直接发送数据回去,注意要writeAndFlush才能让客户端立即收到
ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()));
}
});
}
});
//绑定端口,启动
bootstrap.bind(8080);
}
Netty中的Channel
通道Channel可以用来进行数据的传输,并且通道支持双向传输
public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {
ChannelId id(); //通道ID
EventLoop eventLoop(); //通道所属的EventLoop,因为一个Channel在他的生命周期内只能注册到一个EventLoop中
Channel parent(); //Channel是具有层级关系的,这里返回的是 父Channel
ChannelConfig config();
boolean isOpen(); //通道当前状态
boolean isRegistered();
boolean isActive();
ChannelMetadata metadata(); //通道相关信息
SocketAddress localAddress();
SocketAddress remoteAddress();
ChannelFuture closeFuture(); //关闭通道,
boolean isWritable();
long bytesBeforeUnwritable();
long bytesBeforeWritable();
Unsafe unsafe();
ChannelPipeline pipeline(); //流水线
ByteBufAllocator alloc(); //可以直接从Channel拿到ByteBufAllocator的实例,来分配ByteBuf
Channel read();
Channel flush(); //刷新
Netty中的Channel相比NIO功能多了许多。
- 所有的IO操作都是
异步
的,并不是在当前线程同步运行,方法调用后就直接返回了 - 如何获取操作结果?
- 基于
ChannelFuture
,用来观察异步操作是否完成
- 基于
Channel的父接口ChannelOutboundInvoker
定义了大量IO操作
public interface ChannelOutboundInvoker { //通道出站调用(包含大量网络出站操作,比如写)
//Socket绑定、连接、断开、关闭等操作
ChannelFuture bind(SocketAddress var1);
ChannelFuture connect(SocketAddress var1);
ChannelFuture connect(SocketAddress var1, SocketAddress var2);
ChannelFuture disconnect();
ChannelFuture close();
ChannelFuture deregister();
//ChannelPromise是ChannelFuture的增强版
ChannelFuture bind(SocketAddress var1, ChannelPromise var2);
ChannelFuture connect(SocketAddress var1, ChannelPromise var2);
ChannelFuture connect(SocketAddress var1, SocketAddress var2, ChannelPromise var3);
ChannelFuture disconnect(ChannelPromise var1);
ChannelFuture close(ChannelPromise var1);
ChannelFuture deregister(ChannelPromise var1);
ChannelOutboundInvoker read();
//写操作,返回ChannelFuture,而不是直接给结果
ChannelFuture write(Object var1);
ChannelFuture write(Object var1, ChannelPromise var2);
ChannelOutboundInvoker flush();
ChannelFuture writeAndFlush(Object var1, ChannelPromise var2);
ChannelFuture writeAndFlush(Object var1);
ChannelPromise newPromise();
ChannelProgressivePromise newProgressivePromise();
ChannelFuture newSucceededFuture();
ChannelFuture newFailedFuture(Throwable var1);
ChannelPromise voidPromise();
}
还实现了AttributeMap
,类似于Session,添加一些属性
public interface AttributeMap {
<T> Attribute<T> attr(AttributeKey<T> var1);
<T> boolean hasAttr(AttributeKey<T> var1);
}
有了通道之后,要怎么操作?将需要处理的操作放在ChannelHandler
中,ChannelHandler
充当了所有入站和出站数据的应用程序逻辑的容器,实际上就是之前Reactor
模式中的Handler
,用来处理读写操作
通过ChannelPipeline
来进行流水线处理
ChannelHandler
顶层接口:
public interface ChannelHandler {
//当ChannelHandler被添加到流水线时调用
void handlerAdded(ChannelHandlerContext var1) throws Exception;
//当ChannelHandler从流水线中移出时调用
void handlerRemoved(ChannelHandlerContext var1) throws Exception;
/** @deprecated */
@Deprecated
void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;
@Inherited
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sharable {
}
}
下一级接口ChannelInboundHandler
//用于处理入站相关事件
public interface ChannelInboundHandler extends ChannelHandler {
//当Channel已经注册到自己的EventLoop上时调用(一个Channel只会被注册到一个EventLoop上,注册后,才会在发生对应时间时被通知
void channelRegistered(ChannelHandlerContext var1) throws Exception;
//在EventLoop上取消注册时
void channelUnregistered(ChannelHandlerContext var1) throws Exception;
//Channel处于活跃状态时别调用,此时Channel已经连接/绑定,且已经就绪
void channelActive(ChannelHandlerContext var1) throws Exception;
//不活跃了
void channelInactive(ChannelHandlerContext var1) throws Exception;
//当Channel读取数据时被调用,数据包被自动包装成了一个Object(默认是ByteBuf)
void channelRead(ChannelHandlerContext var1, Object var2) throws Exception;
//上一个读取操作完成后调用
void channelReadComplete(ChannelHandlerContext var1) throws Exception;
void userEventTriggered(ChannelHandlerContext var1, Object var2) throws Exception;
//Channel的可写状态发生改变时调用
void channelWritabilityChanged(ChannelHandlerContext var1) throws Exception;
//出现异常时调用
void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;
}
ChannelInboundHandlerAdapter
实际上就是对这些方法实现的抽象类,相比直接用接口,可以只重写我们需要的方法,没有重写的方法会默认向流水线下一个ChannelHander
发送
public class TestChannelHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelRegistered");
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelUnRegistered");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelInactive");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println(Thread.currentThread().getName() + ">>接收到客户端发送的数据" + buf.toString(StandardCharsets.UTF_8));
//使用ctx.alloc()生成缓冲区
ByteBuf back = ctx.alloc().buffer();
back.writeCharSequence("已收到", StandardCharsets.UTF_8);
ctx.writeAndFlush(back);
System.out.println("channelRead");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelReadComplete");
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
System.out.println("userEventTriggered");
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelWritabilityChanged");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exceptionCaught" + cause);
}
}
服务器端使用TestChannelHandler
public class Server {
public static void main(String[] args) {
//使用NioEventLoopGroup实现类创建bossGroup和workerGroup
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
//创建服务端启动引导类
ServerBootstrap bootstrap = new ServerBootstrap();
//链式
bootstrap
.group(bossGroup, workerGroup) //指定事件循环组
.channel(NioServerSocketChannel.class) //指定为NIO的ServerSocketChannel
.childHandler(new ChannelInitializer<SocketChannel>() { //这里的SocketChannel是Netty的
@Override
protected void initChannel(SocketChannel channel) throws Exception {
//获取流水线,当需要处理客户端的数据时,像流水线一样处理,流水线中有很多Handler
channel.pipeline().addLast(new TestChannelHandler());
}
});
//绑定端口,启动
bootstrap.bind(8080);
}
}
可以看到ChannelInboundHandler
的生命周期:
Channel
注册成功- 变为可用状态
- 等待客户端来数据
- 当客户端主动断开连接,会再次触发
ChannelReadComplete
- 然后不可用
- 最后取消注册
接下来是ChannelPipeline
,每个Channel都对应一个ChannelPipeline(在Channel初始化时就被创建了)
就像一条流水线,上面有多个Handler(包括入站和出站),整条流水线上的两端还有两个默认的处理器(用于预置操作和后续操作,比如释放资源等)。
如果现在希望创建两个入站ChannelHandler,一个用于接收请求并处理,还有一个用于处理当前接收请求过程中出现的异常:
public static void main(String[] args) {
//使用NioEventLoopGroup实现类创建bossGroup和workerGroup
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
//创建服务端启动引导类
ServerBootstrap bootstrap = new ServerBootstrap();
//链式
bootstrap
.group(bossGroup, workerGroup) //指定事件循环组
.channel(NioServerSocketChannel.class) //指定为NIO的ServerSocketChannel
.childHandler(new ChannelInitializer<SocketChannel>() { //这里的SocketChannel是Netty的
@Override
protected void initChannel(SocketChannel channel) throws Exception {
//获取流水线,当需要处理客户端的数据时,像流水线一样处理,流水线中有很多Handler
channel.pipeline()
.addLast(new ChannelInboundHandlerAdapter() { //第一个用于消息接收
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
throw new RuntimeException("发生异常");
}
})
.addLast(new ChannelInboundHandlerAdapter() { //第二个用于处理异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常处理:" + cause);
}
});
}
});
//绑定端口,启动
bootstrap.bind(8080);
}
那么他是如何运作的?
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//通过ChannelHandlerContext来向下传递
//ChannelHandlerContext是在Handler添加进Pipeline中时就被创建的
ctx.fireExceptionCaught(cause);
}
比如现在需要将一个消息在两个Handler中进行处理
.childHandler(new ChannelInitializer<SocketChannel>() { //这里的SocketChannel是Netty的
@Override
protected void initChannel(SocketChannel channel) throws Exception {
//添加两个Handler
channel.pipeline()
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("1-接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
ctx.fireChannelRead(msg); //通过ChannelHandlerContext
}
})
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("2-接收到客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
}
});
}
});
出站操作也可以加入到Pipeline上的,通过ChannelOutboundHandlerAdapter
完成
出站操作应该在入站操作前面,当使用ChannelOutboundHandlerAdapter
的write
方法时,是从流水线的当前位置倒着往前找下一个ChannelOutboundHandlerAdapter
,
而之前使用的ChannelInboundHandlerAdapter
是从前往后找下一个。
如果使用的是Channel的write
方法,那么会从整个流水线的最后倒着往前找ChannelOutboundHandlerAdapter
@Override
protected void initChannel(SocketChannel channel) throws Exception {
//添加两个Handler
channel.pipeline()
.addLast(new ChannelOutboundHandlerAdapter() {
//出站操作应该在入站操作的前面
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println(msg); //写的内容
//将其转为ByteBuf
ctx.writeAndFlush(Unpooled.wrappedBuffer(msg.toString().getBytes()))
}
})
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("1-接收到客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
ctx.fireChannelRead(msg);
}
})
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("2-接收到客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
ctx.writeAndFlush("从当前位置往后"); //从当前位置往后
// ctx.channel().writeAndFlush("从整个后面");
}
});
}
EventLoop和任务调度
EventLoop本质上就是一个事件等待/处理线程
一个EventLoopGroup,包含很多个EventLoop,每创建一个连接,就需要绑定到一个EventLoop上,之后EventLoop就会开始监听这个连接,而一个EventLoop可以同时监听很多个Channel,跟之前的Selector一样
public static void main(String[] args) {
//限制以下线程数
EventLoopGroup bossGroup = new NioEventLoopGroup(), workerGroup = new NioEventLoopGroup(1);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
.group(bossGroup, workerGroup) //指定事件循环组
.channel(NioServerSocketChannel.class) //指定为NIO的ServerSocketChannel
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
//卡10秒假装在处理
Thread.sleep(10000);
ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()));
}
});
}
});
bootstrap.bind(8080);
}
这里卡住了,就没法处理EventLoop绑定的其他Channel了,所以这里创建一个普通的EventLoop来专门处理读写之外的任务
public static void main(String[] args) {
//限制以下线程数
EventLoopGroup bossGroup = new NioEventLoopGroup(), workerGroup = new NioEventLoopGroup(1);
//处理其他任务
DefaultEventLoop handlerGroup = new DefaultEventLoop();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
.group(bossGroup, workerGroup) //指定事件循环组
.channel(NioServerSocketChannel.class) //指定为NIO的ServerSocketChannel
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
//使用handlerGroup来处理 耗时10秒钟的任务
handlerGroup.submit(() -> {
//由于继承自ScheduledExecutorServer,直接提交任务即可,相当于线程池
try {
//卡10秒假装在处理
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()));
});
}
});
}
});
bootstrap.bind(8080);
}
也可以写成一条流水线,进一步的将EventLoop利用起来
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
ctx.fireChannelRead(msg);
}
})
.addLast(handlerGroup,new ChannelInboundHandlerAdapter() { //添加时,可以直接指定使用哪个EventLoopGroup
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
try {
//卡10秒假装在处理
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()));
}
});
}
按照前面服务端的方式,将Netty客户端也写了
public class Client {
public static void main(String[] args) {
//客户端使用Bootstrap来启动
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(new NioEventLoopGroup()) //客户端一个EventLoop就行,用于处理发回来的数据
.channel(NioSocketChannel.class) //客户端使用NioSocketChannel
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println(">>接收服务端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
}
});
}
});
//连接后拿到对应的Channel对象
Channel channel = bootstrap.connect("localhost", 8080).channel();
//上面的操作都是异步的,调用后会往下走,
//客户端给服务器端发送的数据
try(Scanner scanner = new Scanner(System.in)) {
while (true) {
System.out.println("要发送给服务器端的内容:");
String text = scanner.nextLine();
if (text.isEmpty()) {
continue;
}
//通过Channel对象发送数据
channel.writeAndFlush(Unpooled.wrappedBuffer(text.getBytes()));
}
}
}
}
Future和Promise
ChannelFuture
,Netty中的Channel的相关操作都是异步进行的,并不是在当前线程同步执行,无法立即的到结果,如果需要结果,就必须利用到Futrue
public interface ChannelFuture extends Future<Void> {
//直接获取此任务的Channel
Channel channel();
//当任务完成时,会直接执行GenericFutureListener的任务,注意执行的位置也是在EventLoop中
ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> var1);
ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... var1);
ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> var1);
ChannelFuture removeListeners(GenericFutureListener<? extends Future<? super Void>>... var1);
//当前线程同步等待异步任务完成,失败抛出异常
ChannelFuture sync() throws InterruptedException;
//同上,但无法响应中断
ChannelFuture syncUninterruptibly();
//同上,但任务终端不会抛出异常,需要手动判断
ChannelFuture await() throws InterruptedException;
ChannelFuture awaitUninterruptibly();
//返回类型是否为void
boolean isVoid();
}
此接口继承自Netty中的Future
接口
public interface Future<V> extends java.util.concurrent.Future<V> {
boolean isSuccess(); //用于判断任务是否执行成功
boolean isCancellable();
Throwable cause(); //获取导致任务失败的异常
...
V getNow(); //立即获取结果,如还未产生结果,得到Null
boolean cancel(boolean var1); //取消任务
}
Channel的操作都是异步完成的,直接返回一个ChannelFuture
,比如Channel的write
操作
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("接收到客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
ChannelFuture future = ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()));
//通过ChannelFuture获取相关的信息
System.out.println("任务完成状态:" + future.isDone());
}
});
}
如果服务器端的启动比较慢,且需要等到服务器端启动完成后才能做一些事情,要怎么办?有两种方案:
-
第一种方案是直接让当前线程同步等待异步任务完成,可以使用
sync()
方法,这样当前线程会一直阻塞直到任务结束ChannelFuture future = bootstrap.bind(8080); future.sync(); System.out.println("服务端启动完成" + future.isDone());
-
添加一个监听器,等待任务完成时通知
ChannelFuture future = bootstrap.bind(8080); future.addListener((ChannelFutureListener) channelFuture -> { System.out.println("服务端启动完成" + future.isDone()); });
包括客户端的关闭也是异步操作的
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println(">>接收服务端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
}
});
}
});
//连接后拿到对应的Channel对象
Channel channel = bootstrap.connect("localhost", 8080).channel();
//上面的操作都是异步的,调用后会往下走,
//客户端给服务器端发送的数据
try(Scanner scanner = new Scanner(System.in)) {
while (true) {
System.out.println("要发送给服务器端的内容:");
String text = scanner.nextLine();
if (text.isEmpty()) {
continue;
}
if (text.equals("exit")) { //输入exit就退出
ChannelFuture future = channel.close();
future.sync(); //等待Channel完全关闭
break;
}
//通过Channel对象发送数据
channel.writeAndFlush(Unpooled.wrappedBuffer(text.getBytes()));
}
} finally {
loopGroup.shutdownGracefully(); //优雅退出
}
Promise接口,支持手动设定成功和失败的结果:
public interface Promise<V> extends Future<V> {
Promise<V> setSuccess(V var1);
boolean trySuccess(V var1); //手动设定成功
Promise<V> setFailure(Throwable var1);
boolean tryFailure(Throwable var1); //失败
boolean setUncancellable();
//其他与Future一样
Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> var1);
Promise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... var1);
Promise<V> removeListener(GenericFutureListener<? extends Future<? super V>> var1);
Promise<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... var1);
Promise<V> await() throws InterruptedException;
Promise<V> awaitUninterruptibly();
Promise<V> sync() throws InterruptedException;
Promise<V> syncUninterruptibly();
}
测试以下
public static void main(String[] args) throws ExecutionException, InterruptedException {
DefaultPromise<Object> promise = new DefaultPromise<>(new DefaultEventLoop());
System.out.println(promise.isSuccess()); //false
promise.setSuccess("hah");
System.out.println(promise.isSuccess()); //true
System.out.println(promise.get()); //hah
}
手动指定成功状态,包括ChannelOutboundInvoker
中的一些基本操作
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("接收到客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
DefaultChannelPromise promise = new DefaultChannelPromise(channel);
System.out.println(promise.isSuccess());
ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()), promise);
promise.sync(); //同步等待
System.out.println(promise.isSuccess()); //判断是否成功
}
});
编码器和解码器
Netty内置的一些编码器和解码器
之前的数据发送和接收都是使用ByteBuf形式传输。
能否在处理数据之前,过滤一次,将数据转换成想要的类型,也可以将数据进行转换,这就用到了编码器和解码器
字符串
直接添加一个字符串编码器到流水线中
//解码器本质上也是一个ChannelInboundHandlerAdapter,用于处理入站请求
.addLast(new StringDecoder()) //当客户端发来的数据只是简单的字符串转换的ByteBuf时,直接使用StringDecoder即可
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//ByteBuf buf = (ByteBuf) msg;
//经过StringDecoder转换后,msg就直接是一个字符串
System.out.println(">>接收服务端发送的数据:" + msg);
}
});
他是继承自MessageToMessageDecoder
,将传入的Message转为另一种类型。
可以自定义
public class TestDecoder extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
System.out.println("数据已收到,正在解码..");
String text = byteBuf.toString(StandardCharsets.UTF_8);
//将解析的数据丢进list中
list.add(text);
list.add(text+"2"); //一条消息被解码成三条消息
list.add(text+"3");
}
}
后面的Handler会依次对三条数据进行处理
编码器
有了解码器处理发过来的数据,要发出去的数据也需要编码器来进行处理
//解码器本质上也是一个ChannelInboundHandlerAdapter,用于处理入站请求
.addLast(new StringDecoder()) //当客户端发来的数据只是简单的字符串转换的ByteBuf时,直接使用StringDecoder即可
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//ByteBuf buf = (ByteBuf) msg;
//经过StringDecoder转换后,msg就直接是一个字符串
System.out.println(">>接收服务端发送的数据:" + msg);
}
})
.addLast(new StringEncoder());
StringEncoder本质上就是一个ChannelOutboundHandlerAdapter
当然还有 编解码器ChannelDuplexHandler
同时实现编码器和解码器,继承MessageToMessageCodec
类即可
public class StringCodec extends MessageToMessageCodec<ByteBuf, String> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, String s, List<Object> list) throws Exception {
list.add(Unpooled.wrappedBuffer(s.getBytes()));
}
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
list.add(byteBuf.toString(StandardCharsets.UTF_8));
}
}
拆包和粘包问题,也可以使用解码器解决
- 定长数据包
.addLast(new FixedLengthFrameDecoder(10))
- 指定分隔符
.addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.wrappedBuffer("!".getBytes())))
- 头部添加长度信息
.addLast(new LengthFieldBasedFrameDecoder(1024, 0 ,4))
实现Http协议通信
支持HTTP协议的编码器和解码器
.addLast(new HttpRequestDecoder()) //HTTP请求解码器
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到客户端的数据:" + msg.getClass()); //看类型
//收到浏览器请求后,返回一个响应
//HTTP版本为1.1,状态码为ok
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
//直接向响应内容中写入数据
response.content().writeCharSequence("Hello wordld", StandardCharsets.UTF_8);
ctx.channel().writeAndFlush(response); //发送响应
ctx.channel().close(); //HTTP请求是一次性的
}
})
.addLast(new HttpResponseEncoder());
使用浏览器访问
浏览器成功接收服务器响应,然后打印了以下类型
可以看到请求和数据是分开的,将他们整合成一个
.addLast(new HttpRequestDecoder()) //HTTP请求解码器
.addLast(new HttpObjectAggregator(Integer.MAX_VALUE)) //聚合器,将内容聚合成一个FullHttpRequest,参数是最大内容长度
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到客户端的数据:" + msg.getClass()); //看类型
FullHttpRequest request = (FullHttpRequest) msg;
System.out.println("浏览器请求路径:" + request.uri()); //获取请求相关信息
//收到浏览器请求后,返回一个响应
//HTTP版本为1.1,状态码为ok
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
//直接向响应内容中写入数据
response.content().writeCharSequence("Hello world", StandardCharsets.UTF_8);
ctx.channel().writeAndFlush(response); //发送响应
ctx.channel().close(); //HTTP请求是一次性的
}
})
.addLast(new HttpResponseEncoder());
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtNJyRHN-1689004709202)(image/image-20230710213450252.png)]
其他内置Handler
打印日志
.addLast(new HttpRequestDecoder()) //HTTP请求解码器
.addLast(new HttpObjectAggregator(Integer.MAX_VALUE)) //聚合器,将内容聚合成一个FullHttpRequest,参数是最大内容长度
.addLast(new LoggingHandler(LogLevel.INFO)) //添加要给日志Handler
对IP地址进行过滤,比如不希望某些IP地址连接服务器
.addLast(new HttpRequestDecoder()) //HTTP请求解码器
.addLast(new HttpObjectAggregator(Integer.MAX_VALUE)) //聚合器,将内容聚合成一个FullHttpRequest,参数是最大内容长度
.addLast(new RuleBasedIpFilter(new IpFilterRule() {
@Override
public boolean matches(InetSocketAddress inetSocketAddress) {
//进行匹配,匹配失败返回false
//若匹配失败,根据下面的类型决定干什么
return !inetSocketAddress.getHostName().equals("127.0.0.1");
}
@Override
public IpFilterRuleType ruleType() {
return IpFilterRuleType.REJECT; //拒绝连接
}
}))
对长期处于空闲的连接进行处理:
.addLast(new StringDecoder())
.addLast(new IdleStateHandler(10, 10, 0)) //IdleStateHandler能侦测空闲连接
//第一个参数表示:连接多少秒没有读操作
//第二个是写操作
//第三个是读写操作,0表示禁用
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到客户端数据"+msg);
ctx.channel().writeAndFlush("已收到");
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.WRITER_IDLE) {
System.out.println("111");
} else if (event.state() == IdleState.READER_IDLE) {
System.out.println("222");
}
}
}
});
启动流程源码
Netty是如何启动以及进行数据处理的
public static void main(String[] args) throws InterruptedException {
//使用NioEventLoopGroup实现类创建bossGroup和workerGroup
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
//创建服务端启动引导类
ServerBootstrap bootstrap = new ServerBootstrap();
//链式
bootstrap
.group(bossGroup, workerGroup) //指定事件循环组
.channel(NioServerSocketChannel.class) //指定为NIO的ServerSocketChannel
.childHandler(new ChannelInitializer<SocketChannel>() { //这里的SocketChannel是Netty的
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new StringDecoder())
.addLast(new IdleStateHandler(10, 10, 0)) //IdleStateHandler能侦测空闲连接
//第一个参数表示:连接多少秒没有读操作
//第二个是写操作
//第三个是读写操作,0表示禁用
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到客户端数据"+msg);
ctx.channel().writeAndFlush("已收到");
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.WRITER_IDLE) {
System.out.println("111");
} else if (event.state() == IdleState.READER_IDLE) {
System.out.println("222");
}
}
}
});
}
});
//绑定端口,启动
ChannelFuture future = bootstrap.bind(8080);
future.addListener((ChannelFutureListener) channelFuture -> {
System.out.println("服务端启动完成" + future.isDone());
});
}
首先,整个服务端是在bind
之后启动的
public ChannelFuture bind(int inetPort) {
// 转换成InetSocketAddress对象
return this.bind(new InetSocketAddress(inetPort));
}
之后,调用其他绑定方法
对应
.group(bossGroup, workerGroup)
//指定事件循环组
public ChannelFuture bind(SocketAddress localAddress) {
this.validate(); //验证,看EventLoopGroup和Channel指定了没有
return this.doBind((SocketAddress)ObjectUtil.checkNotNull(localAddress, "localAddress"));
}
之后doBind
private ChannelFuture doBind(final SocketAddress localAddress) {
//初始化然后注册
final ChannelFuture regFuture = this.initAndRegister();
//....
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
} else if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
promise.registered();
AbstractBootstrap.doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
如何注册:doBind->initAndRegister
对应
.channel(NioServerSocketChannel.class)
,指定通道
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
//通过channelFactory创建新的Channel,即开始设定的NioServerSocketChannel
channel = this.channelFactory.newChannel();
//对创建好的NioServerSocketChannel进行初始化
this.init(channel);
...
//将通道注册到bossGroup中的一个EventLoop中
ChannelFuture regFuture = this.config().group().register(channel);
...
return regFuture;
}
对创建好的ServerSocketChannel
进行初始化:doBind->initAndRegister->init
void init(Channel channel) {
setChannelOptions(channel, this.newOptionsArray(), logger);
setAttributes(channel, this.newAttributesArray());
ChannelPipeline p = channel.pipeline();
...
//在流水线上添加一个Handler,在Handler初始化的时候向EventLoop中提交一个任务,将ServerBootstrapAcceptor添加到流水线上
//这样ServerSocketChannel在客户端连接时就能Accept了
p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = ServerBootstrap.this.config.handler();
if (handler != null) {
pipeline.addLast(new ChannelHandler[]{handler});
}
//向eventLoop提交任务
ch.eventLoop().execute(new Runnable() {
public void run() {
//这里提交一个任务,将ServerBootstrapAcceptor添加到ServerSocketChannel的pipeline中
pipeline.addLast(new ChannelHandler[]{new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
}
});
}
}});
}
ServerBootstrapAcceptor
怎么处理的
对应前面的
.childHandler(new ChannelInitializer() { //这里的SocketChannel是Netty的
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new StringDecoder().addLast(…)将childHandler的内容添加到流水线中
注册到EventLoop中(NIO底层将其注册到Selector中,Selector才可以去监听它的读写事件)
//当底层NIO的ServerSocketChannel的Selector有ACCEPT事件到达时,NIOEventLoop会接收客户端连接,创建SocketChannel,并触发channelRead回调
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//msg是Accept连接创建后的Channel对象
final Channel child = (Channel)msg;
//将childHandler添加到新创建的客户端连接的流水线中
child.pipeline().addLast(new ChannelHandler[]{this.childHandler});
AbstractBootstrap.setChannelOptions(child, this.childOptions, ServerBootstrap.logger);
AbstractBootstrap.setAttributes(child, this.childAttrs);
try {
//直接向workGroup中的EventLoop注册新创建好的客户端连接Channel,等待读写事件
this.childGroup.register(child).addListener(new ChannelFutureListener() {
//异步操作后,如果注册没有成功,就强制关闭这个Channel
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
ServerBootstrap.ServerBootstrapAcceptor.forceClose(child, future.cause());
}
}
});
} catch (Throwable var5) {
forceClose(child, var5);
}
}
所以实际上就是前面的主从Reactor
多线程模型
初始化完成后,进行注册,在NIO时需要将Channel注册到对应的Selector
才可以开始选择:
回到前面的doBind->initAndRegister->register
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
//通过channelFactory创建新的Channel,即开始设定的NioServerSocketChannel
channel = this.channelFactory.newChannel();
//对创建好的NioServerSocketChannel进行初始化
this.init(channel);
...
//将通道注册到bossGroup中的一个EventLoop中
ChannelFuture regFuture = this.config().group().register(channel);
...
return regFuture;
}
public ChannelFuture register(Channel channel) {
//转换成ChannelPromise继续
return this.register((ChannelPromise)(new DefaultChannelPromise(channel, this)));
}
public ChannelFuture register(ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
//调用Channel的Unsafe接口实现进行注册
promise.channel().unsafe().register(this, promise);
return promise;
}
找到unsafe
的实现类Unsafe
,找到里面void register(EventLoop var1, ChannelPromise var2);
的实现AbstractUnsafe
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
...
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
//调用register0方法进行注册
this.register0(promise);
}
...
}
}
继续register0
private void register0(ChannelPromise promise) {
try {
...
boolean firstRegistration = this.neverRegistered;
//这里开始执行AbstractNioChannel中的doRegister方法进行注册
AbstractChannel.this.doRegister();
this.neverRegistered = false;
AbstractChannel.this.registered = true;
AbstractChannel.this.pipeline.invokeHandlerAddedIfNeeded();
this.safeSetSuccess(promise);
AbstractChannel.this.pipeline.fireChannelRegistered();
if (AbstractChannel.this.isActive()) {
if (firstRegistration) {
//关键,拿到selectionKey后继续向下传递事件
AbstractChannel.this.pipeline.fireChannelActive();
} else if (AbstractChannel.this.config().isAutoRead()) {
this.beginRead();
}
}
...
}
再进入到doRegister
中,最后一级
protected void doRegister() throws Exception {
boolean selected = false;
while(true) {
try {
//这里真正进行了注册,javaChannel()得到NIO的Channel对象,然后调用register方法
//这里和NIO是一样了,将Channel注册到Selector中,可以看到Selector也是EventLoop中的
//ops参数是0,即不见听任何数据
this.selectionKey = this.javaChannel().register(this.eventLoop().unwrappedSelector(), 0, this);
//nio的channel.register(selector, SelectionKey.OP_ACCEPT, new Acceptor)
//但是这里是0,说明没有监听任何事件
return;
}
...
}
}
返回上一级,在doRegister
完成后,拿到selectionKey
,此时还没有监听任何事件
接下来看下面的fireChannelActive
方法,继续向下传递事件
public final ChannelPipeline fireChannelActive() {
//传递的是流水线上的默认头节点
AbstractChannelHandlerContext.invokeChannelActive(this.head);
return this;
}
static void invokeChannelActive(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelActive(); //接着向下
} else {
executor.execute(new Runnable() {
public void run() {
next.invokeChannelActive();
}
});
}
}
private void invokeChannelActive() {
if (this.invokeHandler()) {
try {
//调用头节点的channelActive进行处理
((ChannelInboundHandler)this.handler()).channelActive(this);
} catch (Throwable var2) {
this.invokeExceptionCaught(var2);
}
} else {
this.fireChannelActive();
}
}
具体的头节点的channelActive
实现,在HeadContext
中
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive();
this.readIfIsAutoRead(); //继续向下
}
private void readIfIsAutoRead() {
if (DefaultChannelPipeline.this.channel.config().isAutoRead()) {
DefaultChannelPipeline.this.channel.read(); //继续向下
}
}
一直往下点,直到走到
public void read(ChannelHandlerContext ctx) {
//最后这里调用beginRead方法
this.unsafe.beginRead();
}
再进去
public final void beginRead() {
this.assertEventLoop();
try {
//调用AbstractNioChannel的doBeginRead方法
AbstractChannel.this.doBeginRead();
} catch (final Exception var2) {
this.invokeLater(new Runnable() {
public void run() {
AbstractChannel.this.pipeline.fireExceptionCaught(var2);
}
});
this.close(this.voidPromise());
}
}
进去,终于找到熟悉的NIO了
protected void doBeginRead() throws Exception {
//先拿到之前注册号的selectionKey
SelectionKey selectionKey = this.selectionKey;
if (selectionKey.isValid()) {
this.readPending = true;
int interestOps = selectionKey.interestOps(); //将监听的操作取出来
if ((interestOps & this.readInterestOp) == 0) { //如果没有监听任何操作
//就把readInterestOp事件进行监听
//这里的readInterestOp实际上就是OP_ACCEPT
selectionKey.interestOps(interestOps | this.readInterestOp);
}
}
}
这样,Channel在初始化完成之后也完成了底层的注册,已经可以开始等待事件了
回到上面的doBind
方法的注册位置,注册完成后,基本上真个主从Reactor结构就已经出来了,还需要做什么?
private ChannelFuture doBind(final SocketAddress localAddress) {
//此时,注册和初始化已经成功
final ChannelFuture regFuture = this.initAndRegister();
//....
//由于是异步操作,通过ChannelFuture拿到对应的ServerSocketChannel对象
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
} else if (regFuture.isDone()) { //如果初始化已经完成了
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise); //直接开始进一步的绑定
return promise;
} else {
//如果还没完成初始化,则创建Promise继续等待任务完成
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
promise.registered();
AbstractBootstrap.doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
最终都会走到doBind0
绑定方法
private static void doBind0(final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) {
//最后会向Channel已经注册到的EventLoop中提交一个新任务
channel.eventLoop().execute(new Runnable() {
public void run() {
if (regFuture.isSuccess()) {
//这里才是真正调用Channel底层进行绑定操作
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
至此,服务端的启动流程结束。
NIO空轮询问题解决方法
至于之前的NIO空轮询问题,可以看看Netty的解决方法
在NioEventLoop
中
while(true) {
boolean var34;
try {
...
try {
if (!this.hasTasks()) {
//首先会在这里进行Selector.select()操作,和NIO一样
strategy = this.select(curDeadlineNanos);
}
...
//每次唤醒都会让selectCnt自增
++selectCnt;
this.cancelledKeys = 0;
...
if (!ranTasks && strategy <= 0) {
//判断是否会出现空轮询BUG
if (this.unexpectedSelectorWakeup(selectCnt)) {
...
看看如何进行判断的
private boolean unexpectedSelectorWakeup(int selectCnt) {
if (Thread.interrupted()) {
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely because Thread.currentThread().interrupt() was called. Use NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
}
return true;
//若selectCnt大于等于SELECTOR_AUTO_REBUILD_THRESHOLD(默认为512),则会直接重建Selector
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
logger.warn("Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.", selectCnt, this.selector);
this.rebuildSelector(); //当前的Selector出现BUG了,会重建一个Selector
return true;
} else {
return false;
}
}
每当空轮询发生时会有专门的计数器+1,如果空轮询的次数超过了512次,就认为其触发了空轮询BUG,触发BUG后,Netty会直接重建一个Selector,将原来的Channel重新注册到新的Selector上,将旧的Selector关掉,这样就放置了无限循环