Fix bugs in ZlibDecoder / Port the factorial example

- Fixed IndexOutOfBoundsException in ZlibDecoder
- Fixed a bug where ZlibDecoder raises an exception when a connection
  is closed
This commit is contained in:
Trustin Lee 2012-05-29 00:06:00 -07:00
parent e00f303e4f
commit 81e8c49931
9 changed files with 170 additions and 224 deletions

View File

@ -52,11 +52,9 @@ public class ZlibDecoder extends StreamToStreamDecoder {
throw new NullPointerException("wrapper"); throw new NullPointerException("wrapper");
} }
synchronized (z) { int resultCode = z.inflateInit(ZlibUtil.convertWrapperType(wrapper));
int resultCode = z.inflateInit(ZlibUtil.convertWrapperType(wrapper)); if (resultCode != JZlib.Z_OK) {
if (resultCode != JZlib.Z_OK) { ZlibUtil.fail(z, "initialization failure", resultCode);
ZlibUtil.fail(z, "initialization failure", resultCode);
}
} }
} }
@ -73,12 +71,10 @@ public class ZlibDecoder extends StreamToStreamDecoder {
} }
this.dictionary = dictionary; this.dictionary = dictionary;
synchronized (z) { int resultCode;
int resultCode; resultCode = z.inflateInit(JZlib.W_ZLIB);
resultCode = z.inflateInit(JZlib.W_ZLIB); if (resultCode != JZlib.Z_OK) {
if (resultCode != JZlib.Z_OK) { ZlibUtil.fail(z, "initialization failure", resultCode);
ZlibUtil.fail(z, "initialization failure", resultCode);
}
} }
} }
@ -95,30 +91,34 @@ public class ZlibDecoder extends StreamToStreamDecoder {
ChannelInboundHandlerContext<Byte> ctx, ChannelInboundHandlerContext<Byte> ctx,
ChannelBuffer in, ChannelBuffer out) throws Exception { ChannelBuffer in, ChannelBuffer out) throws Exception {
synchronized (z) { if (!in.readable()) {
return;
}
try {
// Configure input.
int inputLength = in.readableBytes();
boolean inHasArray = in.hasArray();
z.avail_in = inputLength;
if (inHasArray) {
z.next_in = in.array();
z.next_in_index = in.arrayOffset() + in.readerIndex();
} else {
byte[] array = new byte[inputLength];
in.readBytes(array);
z.next_in = array;
z.next_in_index = 0;
}
int oldNextInIndex = z.next_in_index;
// Configure output.
int maxOutputLength = inputLength << 1;
boolean outHasArray = out.hasArray();
if (!outHasArray) {
z.next_out = new byte[maxOutputLength];
}
try { try {
// Configure input.
int inputLength = in.readableBytes();
boolean inHasArray = in.hasArray();
z.avail_in = inputLength;
if (inHasArray) {
z.next_in = in.array();
z.next_in_index = in.arrayOffset() + in.readerIndex();
} else {
byte[] array = new byte[inputLength];
in.readBytes(array);
z.next_in = array;
z.next_in_index = 0;
}
int oldNextInIndex = z.next_in_index;
// Configure output.
int maxOutputLength = inputLength << 1;
boolean outHasArray = out.hasArray();
if (!outHasArray) {
z.next_out = new byte[maxOutputLength];
}
loop: for (;;) { loop: for (;;) {
z.avail_out = maxOutputLength; z.avail_out = maxOutputLength;
if (outHasArray) { if (outHasArray) {
@ -131,14 +131,7 @@ public class ZlibDecoder extends StreamToStreamDecoder {
int oldNextOutIndex = z.next_out_index; int oldNextOutIndex = z.next_out_index;
// Decompress 'in' into 'out' // Decompress 'in' into 'out'
int resultCode; int resultCode = z.inflate(JZlib.Z_SYNC_FLUSH);
try {
resultCode = z.inflate(JZlib.Z_SYNC_FLUSH);
} finally {
if (inHasArray) {
in.skipBytes(z.next_in_index - oldNextInIndex);
}
}
int outputLength = z.next_out_index - oldNextOutIndex; int outputLength = z.next_out_index - oldNextOutIndex;
if (outputLength > 0) { if (outputLength > 0) {
if (outHasArray) { if (outHasArray) {
@ -175,13 +168,17 @@ public class ZlibDecoder extends StreamToStreamDecoder {
} }
} }
} finally { } finally {
// Deference the external references explicitly to tell the VM that if (inHasArray) {
// the allocated byte arrays are temporary so that the call stack in.skipBytes(z.next_in_index - oldNextInIndex);
// can be utilized. }
// I'm not sure if the modern VMs do this optimization though.
z.next_in = null;
z.next_out = null;
} }
} finally {
// Deference the external references explicitly to tell the VM that
// the allocated byte arrays are temporary so that the call stack
// can be utilized.
// I'm not sure if the modern VMs do this optimization though.
z.next_in = null;
z.next_out = null;
} }
} }
} }

View File

@ -15,13 +15,12 @@
*/ */
package io.netty.example.factorial; package io.netty.example.factorial;
import java.math.BigInteger;
import io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffer;
import io.netty.channel.Channel; import io.netty.channel.ChannelInboundHandlerContext;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.CorruptedFrameException; import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.FrameDecoder; import io.netty.handler.codec.StreamToMessageDecoder;
import java.math.BigInteger;
/** /**
* Decodes the binary representation of a {@link BigInteger} prepended * Decodes the binary representation of a {@link BigInteger} prepended
@ -29,36 +28,35 @@ import io.netty.handler.codec.FrameDecoder;
* {@link BigInteger} instance. For example, { 'F', 0, 0, 0, 1, 42 } will be * {@link BigInteger} instance. For example, { 'F', 0, 0, 0, 1, 42 } will be
* decoded into new BigInteger("42"). * decoded into new BigInteger("42").
*/ */
public class BigIntegerDecoder extends FrameDecoder { public class BigIntegerDecoder extends StreamToMessageDecoder<BigInteger> {
@Override @Override
protected Object decode( public BigInteger decode(ChannelInboundHandlerContext<Byte> ctx, ChannelBuffer in) {
ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
// Wait until the length prefix is available. // Wait until the length prefix is available.
if (buffer.readableBytes() < 5) { if (in.readableBytes() < 5) {
return null; return null;
} }
buffer.markReaderIndex(); in.markReaderIndex();
// Check the magic number. // Check the magic number.
int magicNumber = buffer.readUnsignedByte(); int magicNumber = in.readUnsignedByte();
if (magicNumber != 'F') { if (magicNumber != 'F') {
buffer.resetReaderIndex(); in.resetReaderIndex();
throw new CorruptedFrameException( throw new CorruptedFrameException(
"Invalid magic number: " + magicNumber); "Invalid magic number: " + magicNumber);
} }
// Wait until the whole data is available. // Wait until the whole data is available.
int dataLength = buffer.readInt(); int dataLength = in.readInt();
if (buffer.readableBytes() < dataLength) { if (in.readableBytes() < dataLength) {
buffer.resetReaderIndex(); in.resetReaderIndex();
return null; return null;
} }
// Convert the received data into a new BigInteger. // Convert the received data into a new BigInteger.
byte[] decoded = new byte[dataLength]; byte[] decoded = new byte[dataLength];
buffer.readBytes(decoded); in.readBytes(decoded);
return new BigInteger(decoded); return new BigInteger(decoded);
} }

View File

@ -15,13 +15,10 @@
*/ */
package io.netty.example.factorial; package io.netty.example.factorial;
import java.net.InetSocketAddress; import io.netty.channel.ChannelBootstrap;
import java.util.concurrent.Executors;
import io.netty.bootstrap.ClientBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.socket.nio.NioClientSocketChannelFactory; import io.netty.channel.socket.nio.NioEventLoop;
import io.netty.channel.socket.nio.NioSocketChannel;
/** /**
* Sends a sequence of integers to a {@link FactorialServer} to calculate * Sends a sequence of integers to a {@link FactorialServer} to calculate
@ -39,32 +36,28 @@ public class FactorialClient {
this.count = count; this.count = count;
} }
public void run() { public void run() throws Exception {
// Configure the client. ChannelBootstrap b = new ChannelBootstrap();
ClientBootstrap bootstrap = new ClientBootstrap( try {
new NioClientSocketChannelFactory( b.eventLoop(new NioEventLoop())
Executors.newCachedThreadPool())); .channel(new NioSocketChannel())
.remoteAddress(host, port)
.initializer(new FactorialClientInitializer(count));
// Set up the event pipeline factory. // Make a new connection.
bootstrap.setPipelineFactory(new FactorialClientPipelineFactory(count)); ChannelFuture f = b.connect().sync();
// Make a new connection. // Get the handler instance to retrieve the answer.
ChannelFuture connectFuture = FactorialClientHandler handler =
bootstrap.connect(new InetSocketAddress(host, port)); (FactorialClientHandler) f.channel().pipeline().last();
// Wait until the connection is made successfully. // Print out the answer.
Channel channel = connectFuture.awaitUninterruptibly().channel(); System.err.format(
"Factorial of %,d is: %,d", count, handler.getFactorial());
// Get the handler instance to retrieve the answer. } finally {
FactorialClientHandler handler = b.shutdown();
(FactorialClientHandler) channel.pipeline().last(); }
// Print out the answer.
System.err.format(
"Factorial of %,d is: %,d", count, handler.getFactorial());
// Shut down all thread pools to exit.
bootstrap.releaseExternalResources();
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@ -15,22 +15,18 @@
*/ */
package io.netty.example.factorial; package io.netty.example.factorial;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInboundHandlerContext;
import io.netty.channel.ChannelInboundMessageHandlerAdapter;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Queue;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import io.netty.channel.Channel;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.ExceptionEvent;
import io.netty.channel.MessageEvent;
import io.netty.channel.SimpleChannelUpstreamHandler;
/** /**
* Handler for a client-side channel. This handler maintains stateful * Handler for a client-side channel. This handler maintains stateful
* information which is specific to a certain channel using member variables. * information which is specific to a certain channel using member variables.
@ -38,12 +34,13 @@ import io.netty.channel.SimpleChannelUpstreamHandler;
* to create a new handler instance whenever you create a new channel and insert * to create a new handler instance whenever you create a new channel and insert
* this handler to avoid a race condition. * this handler to avoid a race condition.
*/ */
public class FactorialClientHandler extends SimpleChannelUpstreamHandler { public class FactorialClientHandler extends ChannelInboundMessageHandlerAdapter<BigInteger> {
private static final Logger logger = Logger.getLogger( private static final Logger logger = Logger.getLogger(
FactorialClientHandler.class.getName()); FactorialClientHandler.class.getName());
// Stateful properties private ChannelInboundHandlerContext<BigInteger> ctx;
private Queue<Object> out;
private int i = 1; private int i = 1;
private int receivedMessages; private int receivedMessages;
private final int count; private final int count;
@ -69,34 +66,23 @@ public class FactorialClientHandler extends SimpleChannelUpstreamHandler {
} }
@Override @Override
public void handleUpstream( public void channelActive(ChannelInboundHandlerContext<BigInteger> ctx) {
ChannelHandlerContext ctx, ChannelEvent e) throws Exception { this.ctx = ctx;
if (e instanceof ChannelStateEvent) { out = ctx.out().messageBuffer();
logger.info(e.toString()); sendNumbers();
}
super.handleUpstream(ctx, e);
} }
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
sendNumbers(e);
}
@Override
public void channelInterestChanged(ChannelHandlerContext ctx, ChannelStateEvent e) {
sendNumbers(e);
}
@Override @Override
public void messageReceived( public void messageReceived(
ChannelHandlerContext ctx, final MessageEvent e) { ChannelInboundHandlerContext<BigInteger> ctx, final BigInteger msg) {
receivedMessages ++; receivedMessages ++;
if (receivedMessages == count) { if (receivedMessages == count) {
// Offer the answer after closing the connection. // Offer the answer after closing the connection.
e.channel().close().addListener(new ChannelFutureListener() { ctx.channel().close().addListener(new ChannelFutureListener() {
@Override @Override
public void operationComplete(ChannelFuture future) { public void operationComplete(ChannelFuture future) {
boolean offered = answer.offer((BigInteger) e.getMessage()); boolean offered = answer.offer(msg);
assert offered; assert offered;
} }
}); });
@ -105,23 +91,38 @@ public class FactorialClientHandler extends SimpleChannelUpstreamHandler {
@Override @Override
public void exceptionCaught( public void exceptionCaught(
ChannelHandlerContext ctx, ExceptionEvent e) { ChannelInboundHandlerContext<BigInteger> ctx, Throwable cause) throws Exception {
logger.log( logger.log(
Level.WARNING, Level.WARNING,
"Unexpected exception from downstream.", "Unexpected exception from downstream.", cause);
e.cause()); ctx.close();
e.channel().close();
} }
private void sendNumbers(ChannelStateEvent e) { private void sendNumbers() {
Channel channel = e.channel(); // Do not send more than 4096 numbers.
while (channel.isWritable()) { boolean finished = false;
while (out.size() < 4096) {
if (i <= count) { if (i <= count) {
channel.write(Integer.valueOf(i)); out.add(Integer.valueOf(i));
i ++; i ++;
} else { } else {
finished = true;
break; break;
} }
} }
ChannelFuture f = ctx.flush();
if (!finished) {
f.addListener(SEND_NUMBERS);
}
} }
private final ChannelFutureListener SEND_NUMBERS = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
sendNumbers();
}
}
};
} }

View File

@ -15,10 +15,9 @@
*/ */
package io.netty.example.factorial; package io.netty.example.factorial;
import static io.netty.channel.Channels.*; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPipelineFactory; import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.compression.ZlibDecoder; import io.netty.handler.codec.compression.ZlibDecoder;
import io.netty.handler.codec.compression.ZlibEncoder; import io.netty.handler.codec.compression.ZlibEncoder;
import io.netty.handler.codec.compression.ZlibWrapper; import io.netty.handler.codec.compression.ZlibWrapper;
@ -26,18 +25,17 @@ import io.netty.handler.codec.compression.ZlibWrapper;
/** /**
* Creates a newly configured {@link ChannelPipeline} for a client-side channel. * Creates a newly configured {@link ChannelPipeline} for a client-side channel.
*/ */
public class FactorialClientPipelineFactory implements public class FactorialClientInitializer extends ChannelInitializer<SocketChannel> {
ChannelPipelineFactory {
private final int count; private final int count;
public FactorialClientPipelineFactory(int count) { public FactorialClientInitializer(int count) {
this.count = count; this.count = count;
} }
@Override @Override
public ChannelPipeline getPipeline() throws Exception { public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = pipeline(); ChannelPipeline pipeline = ch.pipeline();
// Enable stream compression (you can remove these two if unnecessary) // Enable stream compression (you can remove these two if unnecessary)
pipeline.addLast("deflater", new ZlibEncoder(ZlibWrapper.GZIP)); pipeline.addLast("deflater", new ZlibEncoder(ZlibWrapper.GZIP));
@ -49,7 +47,5 @@ public class FactorialClientPipelineFactory implements
// and then business logic. // and then business logic.
pipeline.addLast("handler", new FactorialClientHandler(count)); pipeline.addLast("handler", new FactorialClientHandler(count));
return pipeline;
} }
} }

View File

@ -15,11 +15,9 @@
*/ */
package io.netty.example.factorial; package io.netty.example.factorial;
import java.net.InetSocketAddress; import io.netty.channel.ServerChannelBootstrap;
import java.util.concurrent.Executors; import io.netty.channel.socket.nio.NioEventLoop;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
/** /**
* Receives a sequence of integers from a {@link FactorialClient} to calculate * Receives a sequence of integers from a {@link FactorialClient} to calculate
@ -33,17 +31,18 @@ public class FactorialServer {
this.port = port; this.port = port;
} }
public void run() { public void run() throws Exception {
// Configure the server. ServerChannelBootstrap b = new ServerChannelBootstrap();
ServerBootstrap bootstrap = new ServerBootstrap( try {
new NioServerSocketChannelFactory( b.eventLoop(new NioEventLoop(), new NioEventLoop())
Executors.newCachedThreadPool())); .channel(new NioServerSocketChannel())
.localAddress(port)
.childInitializer(new FactorialServerInitializer());
// Set up the event pipeline factory. b.bind().sync().channel().closeFuture().sync();
bootstrap.setPipelineFactory(new FactorialServerPipelineFactory()); } finally {
b.shutdown();
// Bind and start to accept incoming connections. }
bootstrap.bind(new InetSocketAddress(port));
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@ -15,18 +15,14 @@
*/ */
package io.netty.example.factorial; package io.netty.example.factorial;
import io.netty.channel.ChannelInboundHandlerContext;
import io.netty.channel.ChannelInboundMessageHandlerAdapter;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Formatter; import java.util.Formatter;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.ExceptionEvent;
import io.netty.channel.MessageEvent;
import io.netty.channel.SimpleChannelUpstreamHandler;
/** /**
* Handler for a server-side channel. This handler maintains stateful * Handler for a server-side channel. This handler maintains stateful
* information which is specific to a certain channel using member variables. * information which is specific to a certain channel using member variables.
@ -34,54 +30,36 @@ import io.netty.channel.SimpleChannelUpstreamHandler;
* to create a new handler instance whenever you create a new channel and insert * to create a new handler instance whenever you create a new channel and insert
* this handler to avoid a race condition. * this handler to avoid a race condition.
*/ */
public class FactorialServerHandler extends SimpleChannelUpstreamHandler { public class FactorialServerHandler extends ChannelInboundMessageHandlerAdapter<BigInteger> {
private static final Logger logger = Logger.getLogger( private static final Logger logger = Logger.getLogger(
FactorialServerHandler.class.getName()); FactorialServerHandler.class.getName());
// Stateful properties. private BigInteger lastMultiplier = new BigInteger("1");
private int lastMultiplier = 1; private BigInteger factorial = new BigInteger("1");
private BigInteger factorial = new BigInteger(new byte[] { 1 });
@Override
public void handleUpstream(
ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
if (e instanceof ChannelStateEvent) {
logger.info(e.toString());
}
super.handleUpstream(ctx, e);
}
@Override @Override
public void messageReceived( public void messageReceived(
ChannelHandlerContext ctx, MessageEvent e) { ChannelInboundHandlerContext<BigInteger> ctx, BigInteger msg) throws Exception {
// Calculate the cumulative factorial and send it to the client. // Calculate the cumulative factorial and send it to the client.
BigInteger number; lastMultiplier = msg;
if (e.getMessage() instanceof BigInteger) { factorial = factorial.multiply(msg);
number = (BigInteger) e.getMessage(); ctx.write(factorial);
} else {
number = new BigInteger(e.getMessage().toString());
}
lastMultiplier = number.intValue();
factorial = factorial.multiply(number);
e.channel().write(factorial);
} }
@Override @Override
public void channelDisconnected(ChannelHandlerContext ctx, public void channelInactive(
ChannelStateEvent e) throws Exception { ChannelInboundHandlerContext<BigInteger> ctx) throws Exception {
logger.info(new Formatter().format( logger.info(new Formatter().format(
"Factorial of %,d is: %,d", lastMultiplier, factorial).toString()); "Factorial of %,d is: %,d", lastMultiplier, factorial).toString());
} }
@Override @Override
public void exceptionCaught( public void exceptionCaught(
ChannelHandlerContext ctx, ExceptionEvent e) { ChannelInboundHandlerContext<BigInteger> ctx, Throwable cause) throws Exception {
logger.log( logger.log(
Level.WARNING, Level.WARNING,
"Unexpected exception from downstream.", "Unexpected exception from downstream.", cause);
e.cause()); ctx.close();
e.channel().close();
} }
} }

View File

@ -15,10 +15,9 @@
*/ */
package io.netty.example.factorial; package io.netty.example.factorial;
import static io.netty.channel.Channels.*; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPipelineFactory; import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.compression.ZlibDecoder; import io.netty.handler.codec.compression.ZlibDecoder;
import io.netty.handler.codec.compression.ZlibEncoder; import io.netty.handler.codec.compression.ZlibEncoder;
import io.netty.handler.codec.compression.ZlibWrapper; import io.netty.handler.codec.compression.ZlibWrapper;
@ -26,12 +25,10 @@ import io.netty.handler.codec.compression.ZlibWrapper;
/** /**
* Creates a newly configured {@link ChannelPipeline} for a server-side channel. * Creates a newly configured {@link ChannelPipeline} for a server-side channel.
*/ */
public class FactorialServerPipelineFactory implements public class FactorialServerInitializer extends ChannelInitializer<SocketChannel> {
ChannelPipelineFactory {
@Override @Override
public ChannelPipeline getPipeline() throws Exception { public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = pipeline(); ChannelPipeline pipeline = ch.pipeline();
// Enable stream compression (you can remove these two if unnecessary) // Enable stream compression (you can remove these two if unnecessary)
pipeline.addLast("deflater", new ZlibEncoder(ZlibWrapper.GZIP)); pipeline.addLast("deflater", new ZlibEncoder(ZlibWrapper.GZIP));
@ -45,7 +42,5 @@ public class FactorialServerPipelineFactory implements
// Please note we create a handler for every new channel // Please note we create a handler for every new channel
// because it has stateful properties. // because it has stateful properties.
pipeline.addLast("handler", new FactorialServerHandler()); pipeline.addLast("handler", new FactorialServerHandler());
return pipeline;
} }
} }

View File

@ -15,29 +15,22 @@
*/ */
package io.netty.example.factorial; package io.netty.example.factorial;
import java.math.BigInteger;
import io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers; import io.netty.channel.ChannelOutboundHandlerContext;
import io.netty.channel.Channel; import io.netty.handler.codec.MessageToStreamEncoder;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.oneone.OneToOneEncoder; import java.math.BigInteger;
/** /**
* Encodes a {@link Number} into the binary representation prepended with * Encodes a {@link Number} into the binary representation prepended with
* a magic number ('F' or 0x46) and a 32-bit length prefix. For example, 42 * a magic number ('F' or 0x46) and a 32-bit length prefix. For example, 42
* will be encoded to { 'F', 0, 0, 0, 1, 42 }. * will be encoded to { 'F', 0, 0, 0, 1, 42 }.
*/ */
public class NumberEncoder extends OneToOneEncoder { public class NumberEncoder extends MessageToStreamEncoder<Number> {
@Override @Override
protected Object encode( public void encode(
ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { ChannelOutboundHandlerContext<Number> ctx, Number msg, ChannelBuffer out) throws Exception {
if (!(msg instanceof Number)) {
// Ignore what this encoder can't encode.
return msg;
}
// Convert to a BigInteger first for easier implementation. // Convert to a BigInteger first for easier implementation.
BigInteger v; BigInteger v;
if (msg instanceof BigInteger) { if (msg instanceof BigInteger) {
@ -50,13 +43,9 @@ public class NumberEncoder extends OneToOneEncoder {
byte[] data = v.toByteArray(); byte[] data = v.toByteArray();
int dataLength = data.length; int dataLength = data.length;
// Construct a message. // Write a message.
ChannelBuffer buf = ChannelBuffers.dynamicBuffer(); out.writeByte((byte) 'F'); // magic number
buf.writeByte((byte) 'F'); // magic number out.writeInt(dataLength); // data length
buf.writeInt(dataLength); // data length out.writeBytes(data); // data
buf.writeBytes(data); // data
// Return the constructed message.
return buf;
} }
} }