《Netty实战》阅读笔记

89 阅读10分钟

《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();
        }
    } 
});

入站事件:连接激活、连接失活、数据读取、用户事件、错误事件

出站事件:连接打开、连接关闭、数据写到或者冲刷套接字

image.png

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的关系 image.png

一个EventLoopGroup包含一个或多个EventLoop 一个EventLoop在生命周期中只和一个Thread绑定 所有由EventLoop处理的I/O事件都将在它专有的Thread上被处理 一个Channel在它的生命周期中只注册于一个EventLoop 一个EventLoop可能被分配给一个或多个Channel

ChannelFuture: 异步通知,addListener()方法注册一个ChannelFutureListener,以便完成得到通知

ChannelHandler:充当了所有处理入站和出战数据的逻辑容器,由网络事件触发

ChannelPipeline:提供了ChannelHandler链的容器,并定义了用于在该链上传播入站和出战事件流的API。当Channel被创建,会自动分配到对应专属的ChannelPipeline

ChannelHandler被注册到ChannelPipeline的过程

  1. 一个ChannelInitializer的实现注册到ServerBootstrap
  2. 当ChannelInitializer.initChannel()被调用,将在ChannelPipeline中安装一组自定义的ChannelHandler
  3. 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

image.png

第四章:网络传输

BIO: 阻塞传输 NIO:异步传输 Local:JVM内部通信

Channel

image.png

ChannelPipeline持有所有入站、出站数据和事件的ChannelHandler

ChannelHandler用途

  1. 数据格式转换
  2. 提供异常通知
  3. Channel活动/非活动通知
  4. Channel注册/注销到EventLoop通知
  5. 用户自定义事件通知

Channel

  1. eventLoop
  2. pipeline
  3. isActive
  4. localAddress/remoteAddress
  5. write:数据写入远程节点
  6. flush: 数据冲刷
  7. writeAndFlush:数据写入并冲刷

io.netty.channel

  1. io.netty.channel.nio:使用java.nio.channel包,基于selector
  2. io.netty.channel.oio:使用java.net,使用阻塞流
  3. io.netty.channel.epoll:使用jni驱动的epoll和非阻塞I/O,比nio快,linux支持
  4. io.netty.channel.local:在vm内部本地通信
  5. io.netty.channel.embedded:embedded传输,测试使用

java.nio.channels.SelectionKey OP_ACCEPT:请求在接受新连接并创建Channel获得通知 OP_CONNECT:请求在建立一个连接时获得通知 OP_READ: 请求当数据已经就绪,可以从Channel中读取获得通知 OP_WRITE:请求当可以向Channel中写更多数据时,得到通知。这处理了套接字缓冲区满的情况,发送速率比远程接收速率,快的时候发生

image.png

NioEventLoopGroup、EpollEventLoopGroup NioServerSocketChannel、EpollServerSocketChannel

第五章:数据处理,内存分配

ByteBuf,与ByteBuffer区别

ByteBuf优点

  • 可以被用户自定义缓冲区类型扩展
  • 通过内置的复合缓冲区类型实现透明的零拷贝
  • 容量可以按需增长
  • 读和写两种模式之间切换不需要调用ByteBuffer的flip()
  • 读和写使用不同的索引
  • 支持方法的链式调用
  • 支持引用计数
  • 支持池化

image.png

字节级操作

随机访问

ByteBuf的索引都是从0开始,到capacity()-1为止,getByte(i)不会改变readerIndex,需要使用readIndex(index)

顺序访问

不调用flip()方法切换读、写模式

image.png

可丢弃字节 是 已经读过的字节,随着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

image.png

提供了两种实现 PooledByteBufAllocate -> jemalloc 池化管理内存, 默认 UnpooledByteBufAllocate -> 每次调用新实例

Unpooled缓冲区

image.png

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

image.png

第七章:线程模型

EventLoop Netty是如何实现异步的、事件驱动的网络

线程池 image.png

EventLoop事件循环

while (!terminated) {  
    List<Runnable> readyEvents = blockUntilEventsReady();
    for (Runnable ev: readyEvents) { 
        ev.run();
    }
}

EventLoop包结构 image.png

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的线程,提交的任务会直接执行,否则放在队列等待

image.png

异步传输

image.png

阻塞传输

image.png

第八章:启动引导

Bootstrap

第九章:单元测试

ChannelHandler

第十、十一章:编解码

Codec

第十二章:Websocket示例

第十三章:UDP示例

第十四章:Droplr、Firebase以及Urban Airship案例

第十五章:Facebook、Twitter案例