Netty 源码分析_ChannelHandlerContext和ChannelInitializer_(4)写数据

 

 

 

可以看到,Channel被包装为ChannelPromise,它持有ChannelEventLoop

在上节中介绍了ChannelPipeline,其中常用的方法是addLast(handler)将各类ChannelHandler实例添加到ChannelChannelPipeline中。下面我们来看addLast(handler)方法的实现:


 

最后调用:callHandlerAdded0 方法:

执行刚刚被添加的ChannelHander对象的handlerAdded回调方法。

总结:真正添加到ChannelPipeline中的是ChannelHandlerContext对象,它封装并持有ChannelHandler对象。添加完成后,会调用ChannelHandlerhandlerAdded回调方法。

 

channelInitializer 

channelInitizlizer 是一类特殊的ChannelHander,当注册到EventLoop 时候,可以初始化Channel.

在ServerBootStrap绑定端口号的过程中,在initAndRegister方法中:一个ChanneIitizlerlist 被添加到channelPipeline中。

 

我们来看一下:ChannelInitialize法:

从上述代码可以看到,handlerAdd 方法首先调用InitChnel 方法添加到各种ChannelHander,然后在finally中调用,把当期的ChanneInitializser从ChannelPipeline 中移除,因此ChannelInitilie 可以理解是一次性的。

10. Channel注册到EventLoop

ServeBootStrape 绑定端口时候,很重要的一个方法是initAndRegister,当Channel初始化完成后,会进行注册。。

group 方法返回NioEventLoopGroup,即是bossGroup.

@Override
public EventLoop next() {
    return (EventLoop) super.next();
}

@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

进入子类:SingleThreadEventLoop

@Override
public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}

@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}

可ch以看到,channel被包装为channel和EventLoop.

最终进入Abstract的register方法:

 

javachanel()方法返回一个SelecttableChannel对象,然后调用register方法,注册到Selector.

eventLoop().unwrappedSelected() 类型是selector.

可以看到channel的注册流程最终使用Java NIO实现。

 

Channel.EventLoop 的关系

EventLoop 继承了MultithreadEventLoopGroup,其中有register方法。

@Override
public EventLoop next() {
    return (EventLoop) super.next();
}

@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

chanelle.EventLOOP的关系:

eventLoop 继承了EventExecuteor,可以提交和执行RUnable任务。

一个EventLoopGroup中会有多个EventLoop。

NioEventLoop:

 

  • 一个Channel只会注册到一个EventLoop

  • 一个EventLoop上可以注册多个Channel

  • 由于EventLoop由单线程处理I/O事件,因此在ChannelHandler的回调方法中不能执行耗时较长的任务,否则会阻塞其他Channel中事件的处理。


总结:inEventLoop()的逻辑就是判断当前线程是否是NioEventLoopSingleThreadEventExecutor)所持有的那个Thread对象。如果不是,就提交给NioEventLoop,让任务稍后由持有的那个Thread对象执行。

 

11.promise接口

  • 任务执行成功完成时,调用setSuccess,同时通知所有监听器notifyListeners
  • 添加监听器时,如果此时任务已经完成,则立即通知监听器notifyListeners

 

12.Net体育源码分析系列:12. 自定义编解码器

编解码器基类

ByteToMessageDecoder:将Bytebuf转换为另一种数据类型

/**
 * in:输入的Bytebuf
 * out:解码后的数据需要添加到out集合中,传递给下一个处理器
/
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;

MessageToMessageDecoder:

MessageToMessageEncoder

MessageToByteEncoder

解码器Decoder:

继承ByteToMessageDecoder,重写decode方法:

编码器Encoder

public class MyLongEncoder extends MessageToByteEncoder<Long> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
        out.writeLong(msg);
    }
}

 

因此非Long类型均无法发送出去,除了两种类型ByteBufFileRegion

 

13.ReplayingDecoder

public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder
  • ReplayingDecoder使用了特殊的ByteBufReplayingDecoderByteBuf,当数据不够时会抛出一类特殊的错误,然后ReplayingDecoder会重置readerIndex并且再次调用decode方法。
  • 泛型<S>使用 枚举Enum 来表示状态,其内部存在状态管理。如果是无状态的,则使用 Void



继承基类ByteToMessageDecoder的方式

public class LongHeaderFrameDecoder extends ByteToMessageDecoder {
  
    @Override
    protected void decode(ChannelHandlerContext ctx,
                         ByteBuf buf, List<Object> out) throws Exception {

        //总字节数<8,不够Long的长度,返回
        if (buf.readableBytes() < 8) {
          return;
        }
        
        buf.markReaderIndex();
        //读取head的值,例如6,说明body的长度是6个字节
        int length = buf.readLong();
        
        //body的总字节数不够6,返回
        if (buf.readableBytes() < length) {
          buf.resetReaderIndex();
          return;
        }
        
        //读取6个长度的body
        out.add(buf.readBytes(length));
    }
}

状态管理和checkpoint方法

状态可以使用枚举Enum来表示,如:

public enum MyDecoderState {
     READ_HEAD,
     READ_BODY;
}

当调用checkpoint(MyDecoderState state)时,ReplayingDecoder会将当前readerIndex赋值给int类型的成员变量checkpoint,在后续数据读取过程中方便重置。

protected void checkpoint(S state) {
    checkpoint();
    state(state);
}

protected void checkpoint() {
    checkpoint = internalBuffer().readerIndex();
}

 

使用状态管理后的LongHeaderFrameDecoder:

11.打自定义通信协议:

public class ServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        System.out.printf("调用服务handler");
    
        byte[] data = new byte[msg.readableBytes()];
    
        msg.readBytes(data);
    
        String text = new String(data, Charset.forName("utf-8"));
        System.out.println(text);
    }
}

public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        //NOOP
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 10; i++) {
            ctx.writeAndFlush(Unpooled.copiedBuffer("hello world", Charset.forName("utf-8")));
        }
    }
}

使用自定义协议

  • 定义协议MyProtocol如下:协议包含头部head和数据本身body:
  • public class MyProtocol {
        private int head;
        private byte[] body;
    
        public int getHead() {
            return head;
        }
    
        public void setHead(int head) {
            this.head = head;
        }
    
        public byte[] getBody() {
            return body;
        }
    
        public void setBody(byte[] body) {
            this.body = body;
        }
    }
    
    有了自定义协议后,就要有对应的编解码器。

解码器MyProtocolDecoder继承前文中的ReplayingDeoder;而编码器MyProtocolEncoder继承Netty提供的MessageToByteEncoder

 

 

15.创建:

// 非池化,使用完后销毁
ByteBuf byteBuf = Unpooled.buffer(10);

//复合类型
CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();

 

技术:

当向ByteBuf写入部分数据后,writerIndex会增加;当从ByteBuf中读取部分数据时,readerIndex增加。显然,readableBytes的值等于writerIndex - readerIndex


可以调用如下方法获取readerIndexwriterIndex:

int writerIndex = byteBuf.writerIndex();
int readerIndex = byteBuf.readerIndex();

可读取:

public boolean isReadable() {
    return writerIndex > readerIndex;
}

通过duplicateslice等方法可以创建新的ByteBuf,其readerIndexwriterIndex是独立的,但是数据和原来的ByteBuf是共享的。

 

public static void main(String[] args) {

    ByteBuf byteBuf = Unpooled.buffer(10);

    // 写入数据
    for (int i = 0; i < 10; i++) {
        byteBuf.writeByte(i);
    }

    // 读取数据
    while (byteBuf.isReadable()) {
        System.out.println(byteBuf.readByte());
    }

}
  • ByteBufByteBuffer
  1. ByteBuf使用两个索引readerIndexwriterIndexByteBuffer使用positionlimitcapacity
  2. ByteBufwrite写入数据,用read读取数据;ByteBufferput放入数据,用get读取数据。
  3. ByteBufferflip方法很重要,切换读写状态。

 

??netty 如何发送数据呢?

1.写数据要点:

Netty 写数据,写不进去,然后注册一个OP_Write事件,来通知什么可以写进去的再写。

 

2.Netty 批量写数据,超过一定的水位线,回家过可写的标示位改成false,让应用自己做决定要不要发送数据,

追加到队尾的方式。

 

single write: sun.nio.ch.SocketChangnelImpl#write

gather wrtiet write:sun

 

只要有护具要写,且嗯捏在,则一直尝试,知道16次,写16次还没有写完,就直接schedule 一个task 来继续写。而不是写注册写时间来出发。更简洁有力。

 

???netty 读完写完之后主线在哪里呢?

正常关闭OP_READ事件,

关闭拦截的本质:

java.nio.chanel.spi.abstractinterruptChannel();

   java.nio.channels.sleecctkey

本质就是写完数据,返回-1 ,然后对特殊特性的处理。

 

 

 

???Netty 如何关闭服务:

NIOEventLOOOp 循环起点,   是所有的channel ,去执行存储的TasksHookl

没有超过最大关闭事件,超过关闭,退出循环,静默期是否有task/hook执行过。

不能关闭停100s.

连接被创建出来了,work看一下camcel 代码,close channel, inactivate.

进入workGroup 的关闭,最大优美关闭时间。nioloop 循环的方法,改变值的状态。

本地的端口,循环关闭channel,  取消关闭的taskCannel.循环没有任务执行。

最重要的事件,执行java,nio,channel.selectKeys#

 

关闭服务要点:

deflaut_shutdown_quiete_period

default_shut_tieout

 

 

 

 

 

 

 



参考资料: https://www.jianshu.com/p/fadf2f44e892

               

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

执于代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值