Netty in action读书笔记
关于netty的内存管理:
netty在4.1.x版本默认使用的allocator是PooledByteBufAllocator
,在分配ByteBuf
的时候,引入了新的alloc机制——jemalloc
,netty内部自己按照jemalloc
的机制,实现了一个java版的(这个有待考证,目前我翻了一下源码,好像是java实现的,没有调用native的部分)
关于Channel.write和ChannelHandlerContext.write:
ChannelHandlerContext
的write
方法,把值写到channelPipline
的后一个channelHandler
节点中,而channel.write
不同,因为write是个outbound事件,所以DefaultChannelPipeline
直接找到tail部分的context,调用其write()方法是从尾部向前传递的。(这个时候,头指的是朝向外部的一端,尾部指朝向本地的一端)
更详细的说明:
blog.csdn.net/zxhoo/artic…
关于netty的ByteBuf:
netty大块来说有两种buf,一种分配在堆内存中,一种分配在堆外内存(就是系统内存)里。各有各的好处。
1.分配在堆外,避免了每次调用I/O操作之前或者之后,将缓冲区的内容复制到一个中间缓冲区,缺点也很明显——堆外收集和分配比较昂贵,使用需要复制到堆内存中。
2.分配在堆内存中的,byteBuf.hasArray()
将会返回true,说明在堆内存中有一个支持数组
。
关于ChannelHandlerContext:
每当有ChannelHandler
被添加到ChannelPipeline
中,都会创建一个ChannelHandlerContext
,二者之间的绑定关系是永远不会改变的,所以可以缓存某个ChannelHandler
的ChannelHandlerContext
。但是一个channelHandler
可以被安装到多个ChannelPipeline
中,但是需要加@Sharable
注解。
关于netty的异常处理:
如果入站处理中出现了异常,异常会像所有入站事件一样向后传递,如果一直传递到底都没有被处理,那么netty将会warn级别记录,并且尝试释放该异常。
关于netty的EventLoop:
Netty in action这本书中讲的是:一个EventLoop
由一个线程来支撑,这个我翻了一下源码,看到的确实是这个样子。后面接着说:如果提交任务的线程就是支撑EventLoop
的线程,那么任务将直接被执行;如果不是,那么EventLoop
将调度该任务以便稍后执行,并将它放到内部的队列中。
这个队列是个LinkedBlockingQueue
,我把DefaultEventLoop
的execute方法(继承自SingleThreadEventExecutor
)拿出来:
@Override public void execute(Runnable task) { if (task == null) { throw new NullPointerException("task"); } boolean inEventLoop = inEventLoop(); if (inEventLoop) { addTask(task); } else { startThread(); addTask(task); if (isShutdown() && removeTask(task)) { reject(); } } if (!addTaskWakesUp && wakesUpForTask(task)) { wakeup(inEventLoop); } }
再看看inEventLoop方法:
@Override public boolean inEventLoop(Thread thread) { //这里我省略了一个方法,入参的thread是从Thread.currentThread()方法返回的,即当前线程 return thread == this.thread; }
这么来看我们可以看到,应该是:是支撑EventLoop
的线程,把任务放到队列中,不是的直接执行。不知道是作者笔误?
继续debug了一下netty,我发现,startThread()
这个方法的调用是在AbstractChannel
初始化的时候就执行了的,这个时候整个线程以及跑起来了,所以这个时候只要添加任何一个任务,就会直接拿出来执行,不知道作者的意思是不是这样的。但是其实还有一个问题,如果这个时候taskQueue
中不是空的,那么就算调用任务的线程是支撑EventLoop
的线程,这个任务也不会马上执行(因为这个任务不管怎么样都是在taskQueue中),只能保证比其他别的线程调用的任务稍微快一点点执行(其他线程的任务要先调用startThread
这个方法)。
关于Netty的传输类型和EventLoopGroup:
我们看一下这种代码:EventLoopGroup e = new NioEventLoopGroup(); BootStrap b = new BootStrap(); b.channel(OioSocketChannel.class) ...
以上这段代码将会报错,因为我们选用不兼容的传输。BootStrap.handler
方法尤其重要,因为它要配置好ChannelPipeline
。
Netty的优雅关闭:
EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(group)... Future<?> f = group.shutdownGracefully(); f.syncUninterruptibly()
关于netty的解码器的一些细节:
对于编码器和解码器来说,一旦消息被编码或者解码,它就会被ReferenceCountUtil.release(message)
调用自动释放。如果不想消息被释放,可以调用ReferenceCountUtil.retain(message)
增加引用计数。
关于netty中的WebSocket:
WebSocketServerHandshaker
有好多种实现,比如WebSocketServerHandshaker13
,就是13版的WebSocket实现。WebSocketServerProtocolHandler
按照WebSocket规范的要求,处理WebSocket升级握手、PingWebSocketFrame
、PongWebSocketFrame
和CloseWebSocketFrame
。WebSocket
升级之前的ChannelPipeline
中的状态:(这里只是根据我们的例子中的handler)
HtthRequestDecoder -> HttpResponseEncoder -> HttpObjectAggregator
-> HttpRequestHandler -> WebSocketServerProtocolHandler
-> TextWebSocketFrameHandler
HttpObjectAggregator
:将一个HttpMessage和跟随它的多个HttpContent聚合为单个FullHttpRequest或者FullHttpResponse(取决于它是被用来处理请求还是响应)。安装这个之后,ChannelPipeline中的下一个ChannelHandler将只会收到完整的HTTP请求或响应。
WebSocket协议升级完成之后,WebSocketServerProtocolHandler
将会把HttpRequestDecoder
替换为WebSocketFrameDecoder
,把HTTPResponseEncoder
替换为WebSocketFrameEncoder
。为了性能最大化,它将移除任何不再被WebSocket连接所需要的ChannelHandler
。也包含了HttpObjectAggregator
和HttpRequestHandler
。Netty根据客户端支持的版本,自动选择WebSocketFrameEncoder
和Decoder,下面是升级之后的Pipeline:
WebSocketFrameDecoder13 -> WebSocketFrameEncoder13
-> WebSocketServerProtocolHandler -> TextWebSocketFrameHandler
(假设我们选用13版的WebSocket协议)
关于WebSocket的握手:
我们上文说道WebSocketServerProtocolHandler
会做很多替换Handler和移除Handler的方法,WebSocketServerProtocolHandler
重写了handlerAdded
方法,会在管道中添加一个WebSocketServerProtocolHandshakeHandler
,这个方法在channelRead的时候会执行握手handshaker.handshake
,这个handshaker是WebSocketServerHandshaker
的实体,我们可以看到这个握手过程中,会进行handler的替换、移除。
关于WebSocket的Idle状态:
触发IdleStateEvent
的是IdleStateHandler
,这个Handler的channelActive
、channelRegistried
和handlerAdded
的时候schedule一个Task,不停地来触发IdleStateEvent
。
关于Netty的bind过程:
ServerBootStrap为例(简称sbs),new一个sbs的时候,什么也不会做,但是在调用sbs的channel方法的时候,会初始化ChannelFactory,默认用的是ReflectiveChannelFactory。bind的时候会初始化一个channel,这个channel是从ChannelFactory中创建出来的,然后在initAndRegister中调用:ChannelFuture regFuture = config().group().register(channel);
这里这个group方法返回的就是group方法传入的EventLoopGroup。这就会在EventLoopGroup中注册一个Channel。我们以NioEventLoopGroup
为例,调用了父类MultithreadEventLoopGroup
的register方法,这里会先使用EventExecutorChooser
来选择一个EventLoop,然后调用EventLoop的Channel注册到一个EventLoop钟,这就是我们之前说的,一个EventLoop会绑定很多Channel,但是一个Channel只会绑在一个EventLoop上。
blog.shiwuliang.com/2017/09/07/… (其他代码可以关注我的github)