《Netty实战》阅读笔记
第一章:基础构件
基础构建的核心部门有:Channel、回调、Future、事件、ChannelHandler
阻塞I/O:socket -> 读写 -> Thread 非阻塞I/O:socket -> 读写 -> Selector(多路复用) -> Thread
Channel: 传入数据和传出数据的载体,可以被打开或者关闭 回调:回调方法引用被提供给另一个方法内部使用 ChannelFuture:异步执行使用,通过注册监听器ChannelFutureListener,监听器被回调来触发调用, 每个Netty I/O操作都会返回ChannelFuture
Channel channel = ...;
// 异步连接到远程节点
ChannelFuture future = channel.connect(
new InetSocketAddress("192.168.0.1", 25));
// 注册监听器
future.addListener(new ChannelFutureListener() {
// 完成触发
@Override
public void operationComplete(ChannelFuture future) {
// 如操作是成功的,则创建一个ByteBuf以持有数据
if (future.isSuccess()){
ByteBuf buffer = Unpooled.copiedBuffer(
"Hello",Charset.defaultCharset());
// 将数据异步发送到远程节点。 返回一个ChannelFuture
ChannelFuture wf = future.channel()
.writeAndFlush(buffer);
....
} else {
Throwable cause = future.cause();
cause.printStackTrace();
}
}
});
入站事件:连接激活、连接失活、数据读取、用户事件、错误事件
出站事件:连接打开、连接关闭、数据写到或者冲刷套接字
ChannelHandler:注册为事件响应的回调
事件 -> ChannelHandler
第二章:启动组装
Bootstrap
每个Channel 都拥有一个与之相关联的ChannelPipeline ,其持有一个ChannelHandler的实例链 ChannelHandler 挂钩到事件的生命周期,保持业务逻辑与网络处理代码的分离
EchoServer
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
//(1) 创建EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();
try {
//(2) 创建ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
b.group(group)
//(3) 指定所使用的 NIO 传输 Channel
.channel(NioServerSocketChannel.class)
//(4) 使用指定的端口设置套接字地址
.localAddress(new InetSocketAddress(port))
//(5) 添加一个EchoServerHandler到于Channel的 ChannelPipeline
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
//EchoServerHandler 被标注为@Shareable,所以我们可以总是使用同样的实例
//这里对于所有的客户端连接来说,都会使用同一个 EchoServerHandler,因为其被标注为@Sharable,
//这将在后面的章节中讲到。
ch.pipeline().addLast(serverHandler);
}
});
//(6) 异步地绑定服务器;调用 sync()方法阻塞等待直到绑定完成
ChannelFuture f = b.bind().sync();
System.out.println(EchoServer.class.getName() +
" started and listening for connections on " + f.channel().localAddress());
//(7) 获取 Channel 的CloseFuture,并且阻塞当前线程直到它完成
f.channel().closeFuture().sync();
} finally {
//(8) 关闭 EventLoopGroup,释放所有的资源
group.shutdownGracefully().sync();
}
}
核心思想
1.绑定eventLoop,ServerBootstrap.group(EventLoopGroup)
2.绑定channel,ServerBootstrap.channel(NioServerSocketChannel.class)
3.绑定localAddress, ServerBootstrap.localAddress(new InetSocketAddress(port))
4.绑定childHandler,ServerBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {...ch.pipeline().addLast(serverHandler);}});
5.绑定服务器,阻塞等待,ChannelFuture f = ServerBootstrap.bind().sync();
6.绑定关闭阻塞,ChannelFuture.channel().closeFuture().sync();
EchoClient
public void start()
throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建 Bootstrap
Bootstrap b = new Bootstrap();
//指定 EventLoopGroup 以处理客户端事件;需要适用于 NIO 的实现
b.group(group)
//适用于 NIO 传输的Channel 类型
.channel(NioSocketChannel.class)
//设置服务器的InetSocketAddress
.remoteAddress(new InetSocketAddress(host, port))
//在创建Channel时,向 ChannelPipeline中添加一个 EchoClientHandler实例
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new EchoClientHandler());
}
});
//连接到远程节点,阻塞等待直到连接完成
ChannelFuture f = b.connect().sync();
//阻塞,直到Channel 关闭
f.channel().closeFuture().sync();
} finally {
//关闭线程池并且释放所有的资源
group.shutdownGracefully().sync();
}
}
核心思想
1.绑定eventLoop,Bootstrap.group(EventLoopGroup)
2.绑定channel,Bootstrap.channel(NioSocketChannel.class)
3.绑定remoteAddress, Bootstrap.remoteAddress(new InetSocketAddress(host, port))
4.绑定handler,Bootstrap.handler(new ChannelInitializer<SocketChannel>() {...ch.pipeline().addLast(clientHandler);}});
5.连接到远程节点,阻塞等待,ChannelFuture f = ServerBootstrap.connect().sync();
6.绑定关闭阻塞,ChannelFuture.channel().closeFuture().sync();
ChannelHandler
public interface ChannelHandler {
// 在将{@link ChannelHandler}添加到实际上下文并准备好处理事件后调用。
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
// 在将{@link ChannelHandler}删除从实际上下文并不会有调用事件后调用。
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
// 发生了异常后调用
@Deprecated
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Sharable {
// no value
}
}
ChannelInboundHandlerAdapter
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
// 对于每个传入的消息都要调用
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
// 通知ChannelInboundHandler最后一次对channelRead()的调用时当前批量读取中的最后一条消息
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelReadComplete();
}
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelRegistered();
}
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelUnregistered();
}
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
}
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelInactive();
}
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
ctx.fireUserEventTriggered(evt);
}
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelWritabilityChanged();
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.fireExceptionCaught(cause);
}
}
第三章:技术体系
Channel 、EventLoop 、ChannelFuture、ChannelHandler、ChannelPipeline
Channel: Socket Channel:基本的I/O操作(bind()、connect()、read()、write())基于底层网络传输
部分清单:EmbeddedChannel、LocalServerChannel、NioDatagramChannel、NioSctpChannel、NioSocketChannel
EventLoop: 控制流、多线程处理、并发
Channel和EventLoop的关系
一个EventLoopGroup包含一个或多个EventLoop 一个EventLoop在生命周期中只和一个Thread绑定 所有由EventLoop处理的I/O事件都将在它专有的Thread上被处理 一个Channel在它的生命周期中只注册于一个EventLoop 一个EventLoop可能被分配给一个或多个Channel
ChannelFuture: 异步通知,addListener()方法注册一个ChannelFutureListener,以便完成得到通知
ChannelHandler:充当了所有处理入站和出战数据的逻辑容器,由网络事件触发
ChannelPipeline:提供了ChannelHandler链的容器,并定义了用于在该链上传播入站和出战事件流的API。当Channel被创建,会自动分配到对应专属的ChannelPipeline
ChannelHandler被注册到ChannelPipeline的过程
- 一个ChannelInitializer的实现注册到ServerBootstrap
- 当ChannelInitializer.initChannel()被调用,将在ChannelPipeline中安装一组自定义的ChannelHandler
- ChannelInitializer将自己从ChannelPipeline移除
ChannelInboundHandler、ChannelInboundHandlerAdapter
ChannelOutboundHandler、ChannelOutboundHandlerAdapter
当ChannelHandler被添加到ChannelPipeline时,它将被分配一个ChannelHandlerContext
ChannelHandlerAdapter、ChannelDuplexAdapter
io.netty.channel.ChannelInitializer#channelRegistered
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
// Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove
// the handler.
if (initChannel(ctx)) {
// we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
// miss an event.
ctx.pipeline().fireChannelRegistered();
// We are done with init the Channel, removing all the state for the Channel now.
removeState(ctx);
} else {
// Called initChannel(...) before which is the expected behavior, so just forward the event.
ctx.fireChannelRegistered();
}
}
实现如 编码器、解码器:字节与对象的转换
ByteToMessageDecoder,MessageToByteEncoder ProtobufEncoder、ProtobufDecoder SimpleChannelInboundHandler
引导类
ServerBootstrap 绑定本地端口, 2个EventLoopGroup,需要两组channel 第一组只包含一个ServerChannel,代表服务器自身绑定到本地端口监听的套接字 第二组包含所有用来处理客户端连接的channel
Bootstrap 连接远程主机端口,1个EventLoopGroup
第四章:网络传输
BIO: 阻塞传输 NIO:异步传输 Local:JVM内部通信
Channel
ChannelPipeline持有所有入站、出站数据和事件的ChannelHandler
ChannelHandler用途
- 数据格式转换
- 提供异常通知
- Channel活动/非活动通知
- Channel注册/注销到EventLoop通知
- 用户自定义事件通知
Channel
- eventLoop
- pipeline
- isActive
- localAddress/remoteAddress
- write:数据写入远程节点
- flush: 数据冲刷
- writeAndFlush:数据写入并冲刷
io.netty.channel
- io.netty.channel.nio:使用java.nio.channel包,基于selector
- io.netty.channel.oio:使用java.net,使用阻塞流
- io.netty.channel.epoll:使用jni驱动的epoll和非阻塞I/O,比nio快,linux支持
- io.netty.channel.local:在vm内部本地通信
- io.netty.channel.embedded:embedded传输,测试使用
java.nio.channels.SelectionKey OP_ACCEPT:请求在接受新连接并创建Channel获得通知 OP_CONNECT:请求在建立一个连接时获得通知 OP_READ: 请求当数据已经就绪,可以从Channel中读取获得通知 OP_WRITE:请求当可以向Channel中写更多数据时,得到通知。这处理了套接字缓冲区满的情况,发送速率比远程接收速率,快的时候发生
NioEventLoopGroup、EpollEventLoopGroup NioServerSocketChannel、EpollServerSocketChannel
第五章:数据处理,内存分配
ByteBuf,与ByteBuffer区别
ByteBuf优点
- 可以被用户自定义缓冲区类型扩展
- 通过内置的复合缓冲区类型实现透明的零拷贝
- 容量可以按需增长
- 读和写两种模式之间切换不需要调用ByteBuffer的flip()
- 读和写使用不同的索引
- 支持方法的链式调用
- 支持引用计数
- 支持池化
字节级操作
随机访问
ByteBuf的索引都是从0开始,到capacity()-1为止,getByte(i)不会改变readerIndex,需要使用readIndex(index)
顺序访问
不调用flip()方法切换读、写模式
可丢弃字节 是 已经读过的字节,随着readerIndex执行增加
丢弃字节
调用clear()比调用discardReadBytes()轻量,只是重置索引,不复制内存
io.netty.buffer.AbstractByteBuf#clear
public ByteBuf clear() {
readerIndex = writerIndex = 0;
return this;
}
io.netty.buffer.AbstractByteBuf#discardReadBytes
public ByteBuf discardReadBytes() {
if (readerIndex == 0) {
ensureAccessible();
return this;
}
if (readerIndex != writerIndex) {
setBytes(0, this, readerIndex, writerIndex - readerIndex);
writerIndex -= readerIndex;
adjustMarkers(readerIndex);
readerIndex = 0;
} else {
ensureAccessible();
adjustMarkers(readerIndex);
writerIndex = readerIndex = 0;
}
return this;
}
查找索引
int forEachByteAsc0(int start, int end, ByteProcessor processor) throws Exception {
for (; start < end; ++start) {
if (!processor.process(_getByte(start))) {
return start;
}
}
return -1;
}
派生缓冲区
duplicate() slice() Unpooled.unmodifiableBuffer() order(ByteOrder) readSlice(int)
public ByteBuf duplicate() {
ensureAccessible();
return new UnpooledDuplicatedByteBuf(this);
}
public ByteBuf slice(int index, int length) {
ensureAccessible();
return new UnpooledSlicedByteBuf(this, index, length);
}
public ByteBuf order(ByteOrder endianness) {
if (endianness == order()) {
return this;
}
ObjectUtil.checkNotNull(endianness, "endianness");
return newSwappedByteBuf();
}
public ByteBuf readSlice(int length) {
checkReadableBytes(length);
ByteBuf slice = slice(readerIndex, length);
readerIndex += length;
return slice;
}
public static ByteBuf unmodifiableBuffer(ByteBuf buffer) {
ByteOrder endianness = buffer.order();
if (endianness == BIG_ENDIAN) {
return new ReadOnlyByteBuf(buffer);
}
return new ReadOnlyByteBuf(buffer.order(BIG_ENDIAN)).order(LITTLE_ENDIAN);
}
ByteBufHolder
content()返回持有的 copy()返回深复制 duplicate()返回浅复制
ByteBuf分配
按需分配ByteBufAllocator
提供了两种实现 PooledByteBufAllocate -> jemalloc 池化管理内存, 默认 UnpooledByteBufAllocate -> 每次调用新实例
Unpooled缓冲区
ByteBufUtil类
equals判断两个ByteBuf是否相等
引用计数
inteferface ReferenceCounted
ByteBuf.refCnd()
第六章:核心组件
ChannelHandler ChannelPipeline
ChannelHandler
Channel生命周期
channelUnregistered: channel已被创建,未注册到eventBus channelRegistered: channel已被创建注册到eventBus channelActive:channel处于活动状态(已连接远程节点),可以收发数据 channelInactive:channel处于非活动状态(未连接远程节点)
channelRegistered -> ChannelActive -> ChannelInactive -> channelUnregistered
ChannelHandler生命周期
handlerAdded: 添加到ChannelPipeline handlerRemoved: 删除ChannelPipeline exceptionCaught: 处理中错误调用
ChannelInboundHandler
channelRegistered channelUnregistered channelActive channelInactive channelReadComplete:channel上一个读操作完成被调用 channelRead:当channel读取数据被调用 channelWritabilityChanged:当Channel的可写状态发生改变时被调用。
用户可以确保写操作不会完成太快(以避免发生OutOfMemoryError)或者可以Channel 变为再次可写时恢复写入。可以通过调用Channel的isWritable() 方法识别Channel 的可写性。与可写性相关的阈值可以通过Channel.config().setWriteHighWaterMark()和Channel.config().setWriteLowWaterMark()方法设置
useEventTriggered:当ChannelInboundHandler.fireUseEventTriggered被调用时背调用,因为一个POJO已被传入ChannelPipeline
重写ChannelInboundHandler,需要重写channelRead()方法,要显示释放相关ByteBuf内存
ReferenceCountUtil.release(msg)
也可以使用SimpleChannelInboundHandler,无需显示释放
ChannelOutboundHandler
bind: 绑定地址端口被调用 connect:连接远程节点被调用 disconect:断开远程节点被调用 close:关闭channel被调用 deregister:注销channel被调用 read:读取数据被调用 flush:冲刷数据被调用 write:写数据被调用
ChannelPromise与ChannelFuture 子父类,回调包装使用
ChannelHandlerAdapter
继承ChannelHandler,提供了实用的isSharable(),表示可被添加到多个ChannelPipeline
资源泄漏检测
ResouceLeakDetector,对应用程序缓冲区采样1%,检测到泄漏会有日志
LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable
advanced leak reporting to find out where the leak occurred. To enable
advanced leak reporting, specify the JVM option
'-Dio.netty.leakDetectionLevel=ADVANCED' or call
ResourceLeakDetector.setLevel().
调整检测策略(DISABLED/SIMPLE/AVANCED/PARANOID)
java -Dio.netty.leakDetectionLevel=ADVANCED
ChannelPipeline
ChannelHandler实例链,每个Channel都会有个Pipeline
ChannelHandlerContext使每个ChannelHandler可以和它的Pipeline/Handler交互 一般通知处理下一个Handler,可以动态修改Pipeline
链操作 addFirst addBefore addAfter addLast remove replace
获取操作 get context names
通常ChannelPipeline中的每一个ChannelHandler都是通过它的EventLoop (I/O线程)处理传递给它的事件的。
入站触发事件 fireChannelRegistered fireChannelUnregistered fireChannelActive fireChannelInactive fireExceptionCaught fireUseEventTriggered fireChannelRead fireChannelReadComplete fireChannelWritabilityChanged
出站出发事件 bind connect disconnect close deregister flush write writeAndFlush read
API alloc:返回相关的ByteBufAllocator executor:返回调度的EventExecutor
第七章:线程模型
EventLoop Netty是如何实现异步的、事件驱动的网络
线程池
EventLoop事件循环
while (!terminated) {
List<Runnable> readyEvents = blockUntilEventsReady();
for (Runnable ev: readyEvents) {
ev.run();
}
}
EventLoop包结构
ScheduledExecutorService
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
ScheduledFuture<?> future = executor.schedule(
new Runnable() {
@Override
public void run() {
System.out.println("60 seconds later");
}
}, 60, TimeUnit.SECONDS);
...
executor.shutdown();
eventLoop
Channel ch = ...
ScheduledFuture<?> future = ch.eventLoop().schedule(
new Runnable() {
@Override
public void run() {
System.out.println("60 seconds later");
}
}, 60, TimeUnit.SECONDS);
Channel ch = ...
ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
System.out.println("Run every 60 seconds");
}
}, 60, 60, TimeUnit.Seconds);
线程优势
卓越性能取决于对当前线程thread的身份确定 每个EventLoop都有自己的任务队列,当调用线程是EventLoop的线程,提交的任务会直接执行,否则放在队列等待
异步传输
阻塞传输
第八章:启动引导
Bootstrap
第九章:单元测试
ChannelHandler
第十、十一章:编解码
Codec