原文地址:http://netty.io/wiki/user-guide-for-4.x.html
Writing a Time Server
The protocol to implement in this section is the TIME
protocol. It is different from the previous examples in that it sends a message, which contains a 32-bit integer, without receiving any requests and closes the connection once the message is sent. In this example, you will learn how to construct and send a message, and to close the connection on completion.
Because we are going to ignore any received data but to send a message as soon as a connection is established, we cannot use the channelRead()
method this time. Instead, we should override the channelActive()
method. The following is the implementation:
package io.netty.example.time;
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(final ChannelHandlerContext ctx) { // (1)
final ByteBuf time = ctx.alloc().buffer(4); // (2)
time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));
final ChannelFuture f = ctx.writeAndFlush(time); // (3)
f.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
assert f == future;
ctx.close();
}
}); // (4)
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
- As explained, the
channelActive()
method will be invoked when a connection is established and ready to generate traffic. Let's write a 32-bit integer that represents the current time in this method. - To send a new message, we need to allocate a new buffer which will contain the message. We are going to write a 32-bit integer, and therefore we need a
ByteBuf
whose capacity is at least 4 bytes. Get the currentByteBufAllocator
viaChannelHandlerContext.alloc()
and allocate a new buffer. -
As usual, we write the constructed message.
But wait, where's the flip? Didn't we used to call
java.nio.ByteBuffer.flip()
before sending a message in NIO?ByteBuf
does not have such a method because it has two pointers; one for read operations and the other for write operations. The writer index increases when you write something to aByteBuf
while the reader index does not change. The reader index and the writer index represents where the message starts and ends respectively.In contrast, NIO buffer does not provide a clean way to figure out where the message content starts and ends without calling the flip method. You will be in trouble when you forget to flip the buffer because nothing or incorrect data will be sent. Such an error does not happen in Netty because we have different pointer for different operation types. You will find it makes your life much easier as you get used to it -- a life without flipping out!
Another point to note is that the
ChannelHandlerContext.write()
(andwriteAndFlush()
) method returns aChannelFuture
. AChannelFuture
represents an I/O operation which has not yet occurred. It means, any requested operation might not have been performed yet because all operations are asynchronous in Netty. For example, the following code might close the connection even before a message is sent:Channel ch = ...; ch.writeAndFlush(message); ch.close();
Therefore, you need to call the
close()
method after theChannelFuture
is complete, which was returned by thewrite()
method, and it notifies its listeners when the write operation has been done. Please note that,close()
also might not close the connection immediately, and it returns aChannelFuture
. -
How do we get notified when a write request is finished then? This is as simple as adding a
ChannelFutureListener
to the returnedChannelFuture
. Here, we created a new anonymousChannelFutureListener
which closes theChannel
when the operation is done.Alternatively, you could simplify the code using a pre-defined listener:
f.addListener(ChannelFutureListener.CLOSE);
To test if our time server works as expected, you can use the UNIX rdate
command:
$ rdate -o <port> -p <host>
where <port>
is the port number you specified in the main()
method and <host>
is usually localhost
.