1
votes

Looking at the docs, it says this:

https://netty.io/4.0/api/io/netty/channel/ChannelPipeline.html

A user is supposed to have one or more ChannelHandlers in a pipeline to receive I/O events (e.g. read) and to request I/O operations (e.g. write and close). For example, a typical server will have the following handlers in each channel's pipeline, but your mileage may vary depending on the complexity and characteristics of the protocol and business logic:

Protocol Decoder - translates binary data (e.g. ByteBuf) into a Java object. Protocol Encoder - translates a Java object into binary data.

Business Logic Handler - performs the actual business logic (e.g. database access). and it could be represented as shown in the following example: static final EventExecutorGroup group = new DefaultEventExecutorGroup(16); ...

ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast("decoder", new MyProtocolDecoder());

pipeline.addLast("encoder", new MyProtocolEncoder());

// Tell the pipeline to run MyBusinessLogicHandler's event handler methods // in a different thread than an I/O thread so that the I/O thread is not blocked by // a time-consuming task. // If your business logic is fully asynchronous or finished very quickly, you don't // need to specify a group.

pipeline.addLast(group, "handler", new MyBusinessLogicHandler());

In a lot of the examples on Github I see this same pattern. I was wondering if someone can explain why the businessHandler is not in between Decoder and Encoder. I would think that you would get your POJO, then do work on it in the business handler, then encode it.

3

3 Answers

3
votes

The Decoder and encoder are usually at the beginning of the pipeline because of order in which handlers are called. For incoming data it's bottom-up and for outgoing top-down.

E.g.

pipeline.addLast(new MyEncoder());
pipeline.addLast(new MyDecoder());
pipeline.addLast(new MyBusiness());

In this case, for incoming data call order is: MyDecoder (transforming data to POJO) -> MyBusiness (the encoder is not called for incoming stream) and for outgoing data: MyBusiness -> MyEncoder (the decoder is not called for outgoing stream).

If you receive an incoming stream in the business handler (actually, the POJOs after decoder) work on it and write it back, it looks like MyBusiness is located between encoder and decoder because data is turning back to the encoder.

1
votes

Of course the business handler is between the decoder and the encoder.Take the example of the Factorial example.

   public void initChannel(SocketChannel ch) {
    ChannelPipeline pipeline = ch.pipeline();
    if (sslCtx != null) {
        pipeline.addLast(sslCtx.newHandler(ch.alloc()));
    }
    // Enable stream compression (you can remove these two if unnecessary)
    pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
    pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
    // Add the number codec first,
    pipeline.addLast(new BigIntegerDecoder());
    pipeline.addLast(new NumberEncoder());
    // and then business logic.
    // Please note we create a handler for every new channel
    // because it has stateful properties.
    pipeline.addLast(new FactorialServerHandler());
}`

thought in the function of initChannelthe pipeline first add the encoder and the decoder, and finally add the handler. the execution flow is actually sorted by decoder,handler and encoder. The handlers like decoder,handler and encoder are actually stored in AbstractChannelHandlerContextclass. There is a linked list of AbstractChannelHandlerContext in Netty. The list is arranged like decoder context-->handler context-->encoder context, and the execution is the same!

1
votes

In fact, if you add 1. decoder, 2. businessHandler, 3. encoder in your server, and you write ctx.channel().writeAndFlush() or ctx.pipeline().writeAndFlush(), the encoder will be called then. It is bc in this case, it will go from the tail to look for the prev outboundChannel. However, if you write ctx.writeAndFlush(), it will look for the prev outboundChannel from the businessHandler's position. Add an breakpoint in the first line of findContextOutbound() of AbstractChannelHandlerContext, you will get it.

private AbstractChannelHandlerContext findContextOutbound(int mask) {
        AbstractChannelHandlerContext ctx = this;
        EventExecutor currentExecutor = executor();
        do {
            ctx = ctx.prev;
        } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));
        return ctx;
    }