Analysis Netty source (V) ----- how the data flow in the pipeline

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  Unsafethe 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 SelectableChannelfunction defined from the SelectableChanneldata read readmethod

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

NioByteUnsafeIs 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  SelectableChannelread bytes of data into the netty ByteBufin

NioMessageUnsafeIs 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

NioByteUnsafeIn 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 ByteBufthe bytes of data written to the jdk  SelectableChannelin

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

  1. 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
  2. Channel data is read into ByteBuf
  3. After the data reading, invoke  pipeline.fireChannelRead(byteBuf); began to spread throughout the pipeline from the head node to the
  4. 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, channelReadCompletethe 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 AbstractChannelHandlerContexthas 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:

  1. 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.
  2. 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.
  3. 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.

 

Guess you like

Origin www.cnblogs.com/java-chen-hao/p/11469098.html