In the previous article, we have to understand the role of the pipeline located in netty in, like an assembly line, control the read and write byte stream, this paper, we continue to dig deep pipeline in the event spread on this basis
Unsafe
As the name suggests, unsafe is unsafe means, is to tell you not to use Unsafe and his derived class object directly in the application inside.
netty official explanation is as follows
Unsafe operations that should never be called from user-code. These methods are only provided to implement the actual transport, and must be invoked from an I/O thread
Unsafe at Channel definition, belong to the inner classes Channel, and Channel show closely related Unsafe
Here are all the unsafe method interfaces
interface Unsafe { RecvByteBufAllocator.Handle recvBufAllocHandle(); SocketAddress localAddress(); SocketAddress remoteAddress(); void register(EventLoop eventLoop, ChannelPromise promise); void bind(SocketAddress localAddress, ChannelPromise promise); void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise); void disconnect(ChannelPromise promise); void close(ChannelPromise promise); void closeForcibly(); void beginRead(); void write(Object msg, ChannelPromise promise); void flush(); ChannelPromise voidPromise(); ChannelOutboundBuffer outboundBuffer(); }
Can be divided by function allocates memory, Socket quad information, registration event loop, binding NIC port, Socket connection and close, read and write the Socket, look out, and these operations are related to the underlying jdk
Unsafe inheritance structure
NioUnsafe
On Unsafe
the basis of an increase of the following interfaces
public interface NioUnsafe extends Unsafe { SelectableChannel ch(); void finishConnect(); void read(); void forceFlush(); }
From the interfaces and increasing the class name of view, NioUnsafe
increased access to the underlying jdk SelectableChannel
function defined from the SelectableChannel
data read read
method
Unsafe classification
Inherited from the above structure, we can conclude that both types of Unsafe classification, is associated with a byte of data read and write connection NioByteUnsafe
, a new connection is established with the relevant operationsNioMessageUnsafe
NioByteUnsafe
Is read: delegate to the outer class NioSocketChannel
protected int doReadBytes(ByteBuf byteBuf) throws Exception { final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle(); allocHandle.attemptedBytesRead(byteBuf.writableBytes()); return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead()); }
The last line has been associated with the underlying and netty jdk in ByteBuf, the jdk the SelectableChannel
read bytes of data into the netty ByteBuf
in
NioMessageUnsafe
Is read: delegate to the outer class NioSocketChannel
protected int doReadMessages(List<Object> buf) throws Exception { SocketChannel ch = javaChannel().accept(); if (ch != null) { buf.add(new NioSocketChannel(this, ch)); return 1; } return 0; }
NioMessageUnsafe
Read operation is very simple, it is to call the jdk accept()
methods, establish a new connection
NioByteUnsafe
In writing: delegate to the outer class NioSocketChannel
@Override protected int doWriteBytes(ByteBuf buf) throws Exception { final int expectedWrittenBytes = buf.readableBytes(); return buf.readBytes(javaChannel(), expectedWrittenBytes); }
The last line has been associated with the underlying and netty jdk in ByteBuf, the netty of ByteBuf
the bytes of data written to the jdk SelectableChannel
in
pipeline in the head
NioEventLoop
Private void processSelectedKey (the SelectionKey K, AbstractNioChannel CH) { Final AbstractNioChannel.NioUnsafe the unsafe = ch.unsafe (); // new connection is ready or access an existing data connection-readable IF ((the readyOps & (SelectionKey.OP_READ ! | SelectionKey.OP_ACCEPT)) = 0 || the readyOps == 0 ) { unsafe.read (); } }
NioByteUnsafe
@Override public Final void Read () { Final ChannelConfig config = config (); Final the ChannelPipeline = Pipeline Pipeline (); // Create ByteBuf dispenser Final ByteBufAllocator the allocator = config.getAllocator (); Final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle () ; allocHandle.reset (config); ByteBuf byteBuf = null ; do { // allocate a ByteBuf byteBuf = allocHandle.allocate (the allocator); // read data to go to the assigned ByteBuf allocHandle.lastBytesRead(doReadBytes(byteBuf)); if (allocHandle.lastBytesRead() <= 0) { byteBuf.release(); byteBuf = null; close = allocHandle.lastBytesRead() < 0; break; } // 触发事件,将会引发pipeline的读事件传播 pipeline.fireChannelRead(byteBuf); byteBuf = null; } while (allocHandle.continueReading()); pipeline.fireChannelReadComplete(); }
Again, I pulled out the core code, the minutiae of the first cut, NioByteUnsafe
things to do can be simply divided into the following steps
- After getting the ByteBuf get config Channel dispenser, a dispenser to dispense ByteBuf, ByteBuf is netty bytes inside the data carrier, the read data is read back inside the object
- Channel data is read into ByteBuf
- After the data reading, invoke
pipeline.fireChannelRead(byteBuf);
began to spread throughout the pipeline from the head node to the - Last call fireChannelReadComplete ();
Here, our focus is actually pipeline.fireChannelRead(byteBuf);
DefaultChannelPipeline
final AbstractChannelHandlerContext head; //... head = new HeadContext(this); public final ChannelPipeline fireChannelRead(Object msg) { AbstractChannelHandlerContext.invokeChannelRead(head, msg); return this; }
In conjunction with this figure
You can see, the data begins to flow from the head node, before proceeding, we first head node function over again
HeadContext
final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler { private final Unsafe unsafe; HeadContext(DefaultChannelPipeline pipeline) { super(pipeline, null, HEAD_NAME, false, true); unsafe = pipeline.channel().unsafe(); setAddComplete(); } @Override public ChannelHandler handler() { return this; } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // NOOP } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { // NOOP } @Override public void bind( ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception { unsafe.bind(localAddress, promise); } @Override public void connect( ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception { unsafe.connect(remoteAddress, localAddress, promise); } @Override public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { unsafe.disconnect(promise); } @Override public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { unsafe.close(promise); } @Override public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { unsafe.deregister(promise); } @Override public void read(ChannelHandlerContext ctx) { unsafe.beginRead(); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { unsafe.write(msg, promise); } @Override public void flush(ChannelHandlerContext ctx) throws Exception { unsafe.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.fireExceptionCaught(cause); } @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { invokeHandlerAddedIfNeeded(); ctx.fireChannelRegistered(); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelUnregistered(); // Remove all handlers sequentially if channel is closed and unregistered. if (!channel.isOpen()) { destroy(); } } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelActive(); readIfIsAutoRead(); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelInactive(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ctx.fireChannelRead(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelReadComplete(); readIfIsAutoRead(); } private void readIfIsAutoRead() { if (channel.config().isAutoRead()) { channel.read(); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { ctx.fireUserEventTriggered(evt); } @Override public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelWritabilityChanged(); } }
Both interfaces inherit from the head node to see, TA is both a ChannelHandlerContext, while belonging to inBound and outBound Handler
When the spread of literacy events, head of the event function simply spread it, asctx.fireChannelRead(msg);
When you actually perform read and write operations, such as calling writeAndFlush()
time and other methods, we will eventually be entrusted to unsafe execution, and when a data read, channelReadComplete
the method will be called
pipeline in the event spread inBound
We then above AbstractChannelHandlerContext. InvokeChannelRead (head, msg); This static method of view, the parameters passed in the head, we know all inbound data from the head start in order to ensure that all handler behind by the opportunity to process the data stream.
We look at the internal static method is how:
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) { final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelRead(m); } else { executor.execute(new Runnable() { public void run() { next.invokeChannelRead(m); } }); } }
Call this Context (ie head) of invokeChannelRead method, and incoming data. Let us look to achieve head in invokeChannelRead method, in fact, is the parent class in AbstractChannelHandlerContext headContext of:
AbstractChannelHandlerContext
private void invokeChannelRead(Object msg) { if (invokeHandler()) { try { ((ChannelInboundHandler) handler()).channelRead(this, msg); } catch (Throwable t) { notifyHandlerException(t); } } else { fireChannelRead(msg); } } public ChannelHandler handler() { return this; }
On handler()
就是
headContext中的handler,也就是headContext自身,也就是调用 head 的 channelRead 方法。那么这个方法是怎么实现的呢?
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ctx.fireChannelRead(msg); }
Did not do anything, call the Context of fire series method, the request is forwarded to the next node. We are here fireChannelRead method, note that this method are quite like the name. We need to be carefully distinguished. Here we look at members of the method Context of fireChannelRead:
AbstractChannelHandlerContext
@Override public ChannelHandlerContext fireChannelRead(final Object msg) { invokeChannelRead(findContextInbound(), msg); return this; }
This is the realization of head abstract superclass AbstractChannelHandlerContext, the method is called again fire series of static methods, but the difference is that last time, no longer head into the argument, but the use of findContextInbound method's return value. From the name of this method can be seen, it is to find the type of inbound handler. We look at ways:
private AbstractChannelHandlerContext findContextInbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.next; } while (!ctx.inbound); return ctx; }
The method is simple, find the next node (inbound type) current Context and returns. This will pass the request to the back of the inbound handler. We take a look invokeChannelRead (findContextInbound (), msg) ;
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) { final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelRead(m); } else { executor.execute(new Runnable() { public void run() { next.invokeChannelRead(m); } }); } }
Above we find the next node (inbound type), then call next.invokeChannelRead (m); if the next is our custom handler, this time our custom handler parent is AbstractChannelHandlerContext, then went back to the AbstractChannelHandlerContext implemented invokeChannelRead, code is as follows:
AbstractChannelHandlerContext
private void invokeChannelRead(Object msg) { if (invokeHandler()) { try { ((ChannelInboundHandler) handler()).channelRead(this, msg); } catch (Throwable t) { notifyHandlerException(t); } } else { fireChannelRead(msg); } } public ChannelHandler handler() { return this; }
At this point the handler () is our custom handler, and then call our custom handler in channelRead ( the this , msg);
When a request comes in, the pipeline will begin transporting head node, the method by fire series invoker mating interface, perfect transmission pipeline in the Context chain. Eventually reach our custom handler.
Note: At this point if we want to continue to pass back how to do it? As we have said, can call the Context of fire series of methods, like channelRead method head, like calling fire series method, passed directly back ok.
If all the handler methods are called fire series, will be delivered to the last inbound type of handler, which is --tail node, then we take a look at the tail node
pipeline in the tail
final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler { TailContext(DefaultChannelPipeline pipeline) { super(pipeline, null, TAIL_NAME, true, false); setAddComplete(); } @Override public ChannelHandler handler() { return this; } @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { } @Override public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { // This may not be a configuration error and so don't log anything. // The event may be superfluous for the current pipeline configuration. ReferenceCountUtil.release(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { onUnhandledInboundException(cause); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { onUnhandledInboundMessage(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { } }
As we mentioned earlier, most of the action is terminated tail node propagation of an event (method body is empty)
channelRead
protected void onUnhandledInboundMessage(Object msg) { try { logger.debug( "Discarded inbound message {} that reached at the tail of the pipeline. " + "Please check your pipeline configuration.", msg); } finally { ReferenceCountUtil.release(msg); } }
Business Objects tail node after the discovery of bytes of data (ByteBuf) or decoder is not consumed in the pipeline transfer process, fell to the node tail, tail node will issue a warning to you, tell you, I have your unprocessed to lost data
In summary, the role of the node is the tail end of the event spread, and some important events to do some gentle reminder
outBound event in the pipeline spread
When the previous section, we explained the function tail node, ignoring parent class AbstractChannelHandlerContext
has the function of this section, we have the most common operating writeAndFlush look at how the pipeline in the event is spread out outBound
A typical message push system, will be similar to the following piece of code
Channel channel = getChannel(userInfo);
channel.writeAndFlush(pushInfo);
The meaning of this code is to get the user information corresponding to the Channel, and push messages to the user, follow channel.writeAndFlush
NioSocketChannel
public ChannelFuture writeAndFlush(Object msg) { return pipeline.writeAndFlush(msg); }
Began to spread out from the pipeline
public final ChannelFuture writeAndFlush(Object msg) { return tail.writeAndFlush(msg); }
Channel Most outBound events are from the tail began to spread out, writeAndFlush()
is a method of tail inherited, we go in with
AbstractChannelHandlerContext
public ChannelFuture writeAndFlush(Object msg) { return writeAndFlush(msg, newPromise()); } public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { write(msg, true, promise); return promise; }
AbstractChannelHandlerContext
private void write(Object msg, boolean flush, ChannelPromise promise) { AbstractChannelHandlerContext next = findContextOutbound(); final Object m = pipeline.touch(msg, next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { if (flush) { next.invokeWriteAndFlush(m, promise); } else { next.invokeWrite(m, promise); } } else { AbstractWriteTask task; if (flush) { task = WriteAndFlushTask.newInstance(next, m, promise); } else { task = WriteTask.newInstance(next, m, promise); } safeExecute(executor, task, promise, m); } }
First call the findContextOutbound()
method to find the next outBound()
node
AbstractChannelHandlerContext
private AbstractChannelHandlerContext findContextOutbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.prev; } while (!ctx.outbound); return ctx; }
Find outBound process nodes and node inBound find similar pipeline in the reverse direction to traverse the doubly linked list until the first outBound node next
, then callnext.invokeWriteAndFlush(m, promise)
AbstractChannelHandlerContext
private void invokeWriteAndFlush(Object msg, ChannelPromise promise) { if (invokeHandler()) { invokeWrite0(msg, promise); invokeFlush0(); } else { writeAndFlush(msg, promise); } }
write method is called the node of ChannelHandler, flush method we ignore for the time being, will be devoted to complete the process behind the writeAndFlush
AbstractChannelHandlerContext
private void invokeWrite0(Object msg, ChannelPromise promise) { try { ((ChannelOutboundHandler) handler()).write(this, msg, promise); } catch (Throwable t) { notifyOutboundHandlerException(t, promise); } }
Can be seen, the outbound data start, starts to flow forwardly from the rear, and the inbound direction is reversed. So where it will eventually come to it, of course, went to the head node, because the head node is the type of outbound handler.
HeadContext
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { unsafe.write(msg, promise); }
Call the unsafe operation of the underlying data, here, to deepen our understanding of the head node, that is, all the data will be written after the head node
When performing this write method, the method began to retreat stack. Gradually retreated unsafe read method, place the original back to the beginning, and then continue to call pipeline.fireChannelReadComplete () method
to sum up
To summarize a transfer request in the pipeline in the process:
- Call the pipeline of fire series of methods invoker is the interface design, pipeline implements all the methods of the invoker, inbound event begins to flow from the head, outbound events from the tail begins to flow.
- pipeline passes the request to the Context, and then invoke Context series by methods AbstractChannelHandlerContext abstract parent class (static and non-static) method with AbstractChannelHandlerContext series of fire and then with findContextInbound findContextOutbound method for data transfer in each Context.
- When the inbound process, an outbound call to a method, then the request will not go back. Behind the processor will not have any effect. Want to continue to meet delivery fire series of method calls Context, so Netty help you pass data to the next node in the interior. If you want to transfer the entire channel, the method invokes the corresponding handler in the pipeline or channel, these two methods of data will start to finish from the tail or the head of the circulation again.