可以看到,Channel
被包装为ChannelPromise
,它持有Channel
和EventLoop
。
在上节中介绍了ChannelPipeline
,其中常用的方法是addLast(handler)
将各类ChannelHandler
实例添加到Channel
的 ChannelPipeline
中。下面我们来看addLast(handler)
方法的实现:
最后调用:callHandlerAdded0 方法:
执行刚刚被添加的ChannelHander
对象的handlerAdded
回调方法。
总结:真正添加到ChannelPipeline
中的是ChannelHandlerContext
对象,它封装并持有ChannelHandler
对象。添加完成后,会调用ChannelHandler
的handlerAdded
回调方法。
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事件,因此在Channel的Handler的回调方法中不能执行耗时较长的任务,否则会阻塞其他Channel中事件的处理。
总结:inEventLoop()
的逻辑就是判断当前线程是否是NioEventLoop
(SingleThreadEventExecutor
)所持有的那个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
类型均无法发送出去,除了两种类型ByteBuf
和FileRegion
。
13.ReplayingDecoder
public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder
ReplayingDecoder
使用了特殊的ByteBuf
:ReplayingDecoderByteBuf
,当数据不够时会抛出一类特殊的错误,然后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
。
可以调用如下方法获取readerIndex
和writerIndex
:
int writerIndex = byteBuf.writerIndex();
int readerIndex = byteBuf.readerIndex();
可读取:
public boolean isReadable() {
return writerIndex > readerIndex;
}
通过duplicate
,slice
等方法可以创建新的ByteBuf
,其readerIndex
和writerIndex
是独立的,但是数据和原来的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());
}
}
ByteBuf
和ByteBuffer
:
ByteBuf
使用两个索引readerIndex
、writerIndex
;ByteBuffer
使用position
、limit
、capacity
。ByteBuf
用write
写入数据,用read
读取数据;ByteBuffer
用put
放入数据,用get
读取数据。ByteBuffer
的flip
方法很重要,切换读写状态。
??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