Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assembling a Netty Message in the Handler

Tags:

java

netty

I am in the process of prototyping Netty for my project. I am trying to implement a simple Text/String oriented protocol on top of Netty. In my pipeline I am using the following:

public class TextProtocolPipelineFactory implements ChannelPipelineFactory
{
@Override
public ChannelPipeline getPipeline() throws Exception 
{
    // Create a default pipeline implementation.
    ChannelPipeline pipeline = pipeline();

    // Add the text line codec combination first,
    pipeline.addLast("framer", new DelimiterBasedFrameDecoder(2000000, Delimiters.lineDelimiter()));
    pipeline.addLast("decoder", new StringDecoder());
    pipeline.addLast("encoder", new StringEncoder());

    // and then business logic.
    pipeline.addLast("handler", new TextProtocolHandler());

    return pipeline;
}
}

I have a DelimiterBasedFrameDecoder, a String Decoder, and a String Encoder in the pipeline.

As a result of this setup my incoming message is split into multiple Strings. This results in multiple invocations of the "messageReceived" method of my handler. This is fine. However , this requires me to accumulate these messages in memory and re-construct the message when the last string packet of the message is received.

My question is, what is the most memory efficient way to "accumulate the strings" and then "re-construct them into the final message". I have 3 options so far. They are:

  • Use a StringBuilder to accumulate and toString to construct. (This gives the worst memory performance. In fact for large payloads with lots of concurrent users this gives non-acceptable performance)

  • Accumulate into a ByteArray via a ByteArrayOutputStream and then construct using the byte-array (this gives a much better performance than option 1, but it still hogs quite a bit of memory)

  • Accumulate into a Dymamic Channel Buffer and use toString(charset) to construct. I have not profiled this setup yet but I am curious how this compares to the above two options. Has anyone solved this issue using the Dynamic Channel Buffer?

I am new to Netty and its possible I may be doing something wrong architecturally. Your input will be greatly appreciated.

Thanks in advance Sohil

Adding my implementation of a custom FrameDecoder for Norman to review

public final class TextProtocolFrameDecoder extends FrameDecoder 
{
public static ChannelBuffer messageDelimiter() 
{
      return ChannelBuffers.wrappedBuffer(new byte[] {'E','O','F'});
    }

@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel,ChannelBuffer buffer) 
throws Exception 
{
    int eofIndex = find(buffer, messageDelimiter());

    if(eofIndex != -1)
    {
        ChannelBuffer frame = buffer.readBytes(buffer.readableBytes());
        return frame;
    }

    return null;
}

private static int find(ChannelBuffer haystack, ChannelBuffer needle) {
    for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
        int haystackIndex = i;
        int needleIndex;
        for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
            if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
                break;
            } else {
                haystackIndex ++;
                if (haystackIndex == haystack.writerIndex() &&
                    needleIndex != needle.capacity() - 1) {
                    return -1;
                }
            }
        }

        if (needleIndex == needle.capacity()) {
            // Found the needle from the haystack!
            return i - haystack.readerIndex();
        }
    }
    return -1;
   }
  }
like image 656
openmobster Avatar asked Nov 16 '12 18:11

openmobster


1 Answers

I think you would get the best performance if you would implement your own FrameDecoder. This would allow you to buffer all the data till you really need to dispatch it to the next Handler in the chain. Please refer to the FrameDecoder apidocs.

If you don't want to handle the detect of CRLF by yourself it would also be possible to keep the DelimiterBasedFrameDecoder and just add a custom FrameDecoder behind it to assemble the ChannelBuffers that represent a line of text.

In both cases FrameDecoder will take care to minimize memory copies as much as possible by try to just "wrap" buffers and not copy them each time.

That said if you want to have the best performance go with the first approach, if you want it easy go with the second ;)

like image 163
Norman Maurer Avatar answered Sep 24 '22 08:09

Norman Maurer