diff --git a/src/main/java/org/jboss/netty/handler/codec/frame/ZeroCopyFrameDecoder.java b/src/main/java/org/jboss/netty/handler/codec/frame/ZeroCopyFrameDecoder.java new file mode 100644 index 0000000000..e74bc0f63c --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/codec/frame/ZeroCopyFrameDecoder.java @@ -0,0 +1,532 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.jboss.netty.handler.codec.frame; + +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.List; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBufferFactory; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.buffer.CompositeChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandler; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.ChannelUpstreamHandler; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.LifeCycleAwareChannelHandler; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.handler.codec.replay.ReplayingDecoder; + +/** + * Decodes the received {@link ChannelBuffer}s into a meaningful frame object. + *
+ * In a stream-based transport such as TCP/IP, packets can be fragmented and + * reassembled during transmission even in a LAN environment. For example, + * let us assume you have received three packets: + *
+ * +-----+-----+-----+ + * | ABC | DEF | GHI | + * +-----+-----+-----+ + *+ * because of the packet fragmentation, a server can receive them like the + * following: + *
+ * +----+-------+---+---+ + * | AB | CDEFG | H | I | + * +----+-------+---+---+ + *+ *
+ * {@link ZeroCopyFrameDecoder} helps you defrag the received packets into one or more + * meaningful frames that could be easily understood by the + * application logic. In case of the example above, your {@link ZeroCopyFrameDecoder} + * implementation could defrag the received packets like the following: + *
+ * +-----+-----+-----+ + * | ABC | DEF | GHI | + * +-----+-----+-----+ + *+ *
+ * The following code shows an example handler which decodes a frame whose + * first 4 bytes header represents the length of the frame, excluding the + * header. + *
+ * MESSAGE FORMAT + * ============== + * + * Offset: 0 4 (Length + 4) + * +--------+------------------------+ + * Fields: | Length | Actual message content | + * +--------+------------------------+ + * + * DECODER IMPLEMENTATION + * ====================== + * + * public class IntegerHeaderFrameDecoder extends {@link ZeroCopyFrameDecoder} { + * + * {@code @Override} + * protected Object decode({@link ChannelHandlerContext} ctx, + * {@link Channel} channel, + * {@link ChannelBuffer} buf) throws Exception { + * + * // Make sure if the length field was received. + * if (buf.readableBytes() < 4) { + * // The length field was not received yet - return null. + * // This method will be invoked again when more packets are + * // received and appended to the buffer. + * return null; + * } + * + * // The length field is in the buffer. + * + * // Mark the current buffer position before reading the length field + * // because the whole frame might not be in the buffer yet. + * // We will reset the buffer position to the marked position if + * // there's not enough bytes in the buffer. + * buf.markReaderIndex(); + * + * // Read the length field. + * int length = buf.readInt(); + * + * // Make sure if there's enough bytes in the buffer. + * if (buf.readableBytes() < length) { + * // The whole bytes were not received yet - return null. + * // This method will be invoked again when more packets are + * // received and appended to the buffer. + * + * // Reset to the marked position to read the length field again + * // next time. + * buf.resetReaderIndex(); + * + * return null; + * } + * + * // There's enough bytes in the buffer. Read it. + * {@link ChannelBuffer} frame = buf.readBytes(length); + * + * // Successfully decoded a frame. Return the decoded frame. + * return frame; + * } + * } + *+ * + *
+ * Please note that you can return an object of a different type than + * {@link ChannelBuffer} in your {@code decode()} and {@code decodeLast()} + * implementation. For example, you could return a + * POJO so that the next + * {@link ChannelUpstreamHandler} receives a {@link MessageEvent} which + * contains a POJO rather than a {@link ChannelBuffer}. + * + *
+ * If you are going to write a protocol multiplexer, you will probably want to + * replace a {@link ZeroCopyFrameDecoder} (protocol detector) with another + * {@link ZeroCopyFrameDecoder} or {@link ReplayingDecoder} (actual protocol decoder). + * It is not possible to achieve this simply by calling + * {@link ChannelPipeline#replace(ChannelHandler, String, ChannelHandler)}, but + * some additional steps are required: + *
+ * public class FirstDecoder extends {@link ZeroCopyFrameDecoder} { + * + * public FirstDecoder() { + * super(true); // Enable unfold + * } + * + * {@code @Override} + * protected Object decode({@link ChannelHandlerContext} ctx, + * {@link Channel} channel, + * {@link ChannelBuffer} buf) { + * ... + * // Decode the first message + * Object firstMessage = ...; + * + * // Add the second decoder + * ctx.getPipeline().addLast("second", new SecondDecoder()); + * + * // Remove the first decoder (me) + * ctx.getPipeline().remove(this); + * + * if (buf.readable()) { + * // Hand off the remaining data to the second decoder + * return new Object[] { firstMessage, buf.readBytes(buf.readableBytes()) }; + * } else { + * // Nothing to hand off + * return firstMessage; + * } + * } + * } + *+ * + * @apiviz.landmark + */ +public abstract class ZeroCopyFrameDecoder + extends SimpleChannelUpstreamHandler implements LifeCycleAwareChannelHandler { + + private final boolean unfold; + protected List
0
is used, which means it will
+ * always copy to try to reduce memory usage
+ *
+ *
+ * @param copyThreshold
+ * the threshold (in bytes) or {@link Integer#MAX_VALUE} to
+ * disable it. The value must be at least 0
+ * @throws IllegalStateException
+ * get thrown if someone tries to change this setting after the
+ * Decoder was added to the {@link ChannelPipeline}
+ */
+ public final void setMaxUnusedBufferCapacity(int copyThreshold) {
+ if (copyThreshold < 0) {
+ throw new IllegalArgumentException("MaxUnusedBufferCapacity must be >= 0");
+ }
+ if (ctx == null) {
+ this.copyThreshold = copyThreshold;
+ } else {
+ throw new IllegalStateException("MaxWastedBufferCapacity " +
+ "can only be changed before the Decoder was added to the ChannelPipeline");
+ }
+ }
+
+ /**
+ * Returns a compact slice of this buffer's readable bytes.
+ *
+ * The returned buffer may or may not share the content area with the buffer
+ * given as an argument while they maintain separate indexes and marks.
+ * If more than the maximal unused buffer capacity is unused then the
+ * content is copied to a new buffer to conserve memory.
+ *
+ * @param buffer ChannelBuffer to compact
+ * @return a compact slice of buffer
+ */
+ private ChannelBuffer compactBuffer(ChannelBuffer buffer) {
+ if (buffer.capacity() - buffer.readableBytes() > copyThreshold) {
+ ChannelBuffer copy = newCumulationBuffer(ctx, buffer.readableBytes());
+ copy.writeBytes(buffer);
+ return copy;
+ } else {
+ return buffer.slice();
+ }
+ }
+
+ @Override
+ public void messageReceived(
+ ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+
+ Object m = e.getMessage();
+ if (!(m instanceof ChannelBuffer)) {
+ ctx.sendUpstream(e);
+ return;
+ }
+
+ ChannelBuffer input = (ChannelBuffer) m;
+ if (!input.readable()) {
+ return;
+ }
+
+ if (cumulation == null) {
+ // Wrap in try / finally.
+ //
+ // See https://github.com/netty/netty/issues/364
+ try {
+ // the cumulation buffer is not created yet so just pass the input to callDecode(...) method
+ callDecode(ctx, e.getChannel(), input, e.getRemoteAddress());
+ } finally {
+ if (input.readable()) {
+ // unread data is left so create a cumulation buffer
+ cumulation = new ArrayList