Finished the first chapter

This commit is contained in:
Trustin Lee 2008-09-22 11:00:18 +00:00
parent 8d8c7c0227
commit f206c5359b
2 changed files with 638 additions and 38 deletions

View File

@ -30,6 +30,7 @@
<!ENTITY ChannelHandlerContext "<ulink url='&API;channel/ChannelHandlerContext.html'><interfacename>ChannelHandlerContext</interfacename></ulink>">
<!ENTITY ChannelPipeline "<ulink url='&API;channel/ChannelPipeline.html'><interfacename>ChannelPipeline</interfacename></ulink>">
<!ENTITY ChannelPipelineCoverage "<ulink url='&API;channel/ChannelPipelineCoverage.html'><interfacename>ChannelPipelineCoverage</interfacename></ulink>">
<!ENTITY ChannelPipelineFactory "<ulink url='&API;channel/ChannelPipelineFactory.html'><interfacename>ChannelPipelineFactory</interfacename></ulink>">
<!ENTITY Channels "<ulink url='&API;channel/Channels.html'><classname>Channels</classname></ulink>">
<!ENTITY ChannelStateEvent "<ulink url='&API;channel/ChannelStateEvent.html'><interfacename>ChannelStateEvent</interfacename></ulink>">
<!ENTITY ChannelUpstreamHandler "<ulink url='&API;channel/ChannelUpstreamHandler.html'><interfacename>ChannelUpstreamHandler</interfacename></ulink>">
@ -39,8 +40,21 @@
<!-- Types in the channel.socket package -->
<!ENTITY ServerSocketChannel "<ulink url='&API;channel/socket/ServerSocketChannel.html'><classname>ServerSocketChannel</classname></ulink>">
<!ENTITY ServerSocketChannel "<ulink url='&API;channel/socket/ServerSocketChannel.html'><interfacename>ServerSocketChannel</interfacename></ulink>">
<!ENTITY SocketChannel "<ulink url='&API;channel/socket/SocketChannel.html'><interfacename>SocketChannel</interfacename></ulink>">
<!-- Types in the channel.socket.nio package -->
<!ENTITY NioClientSocketChannelFactory "<ulink url='&API;channel/socket/nio/NioClientSocketChannelFactory.html'><classname>NioClientSocketChannelFactory</classname></ulink>">
<!ENTITY NioServerSocketChannelFactory "<ulink url='&API;channel/socket/nio/NioServerSocketChannelFactory.html'><classname>NioServerSocketChannelFactory</classname></ulink>">
<!-- Types in the handler.codec.frame package -->
<!ENTITY FrameDecoder "<ulink url='&API;handler/codec/frame/FrameDecoder.html'><classname>FrameDecoder</classname></ulink>">
<!-- Types in the handler.codec.replay package -->
<!ENTITY ReplayingDecoder "<ulink url='&API;handler/codec/replay/ReplayingDecoder.html'><classname>ReplayingDecoder</classname></ulink>">
<!ENTITY VoidEnum "<ulink url='&API;handler/codec/replay/VoidEnum.html'><classname>VoidEnum</classname></ulink>">

View File

@ -7,17 +7,19 @@
<title>Getting Started</title>
<para>
This chapter tours around the core constructs of Netty with simple
examples to let you get started with Netty easily. You should be able to
examples to let you get started with Netty easily. You will be able to
write a network application on top of Netty right away when you are at the
end of this chapter.
</para>
<para>
<!-- The chapter 2 is not written yet. -->
<!--para>
If you prefer the top-down approach in learning something, you might want
to start from <xref linkend="architecture"/> and get back here.
</para>
</para-->
<section>
<title>Minimum Requirements</title>
<title>Before Getting Started</title>
<para>
The minimum requirements to run the examples which are introduced in
this chapter are just two; the latest version of Netty release and JDK
@ -30,7 +32,16 @@
Is that all? To tell the truth, you should find these two are just
enough to implement almost any type of protocols. Otherwise, please
feel free to <ulink url="&Community;">contact the Netty
project team</ulink> and let us know what's missing.
project community</ulink> and let us know what's missing.
</para>
<para>
At last but least, please refer to the API reference whenever you want
to know more about the classes introduced here. All class names in
this document are linked to the online API reference for your
convenience. Also, please feel free to <ulink url="&Community;">contact
the Netty project community</ulink> and let us know if there's any
incorrect information, errors in grammar and typo, and if you have a
good idea to improve the documentation.
</para>
</section>
@ -48,12 +59,6 @@
</para>
<programlisting>package org.jboss.netty.example.discard;
import org.jboss.netty.channel.&ChannelHandlerContext;;
import org.jboss.netty.channel.&ChannelPipelineCoverage;;
import org.jboss.netty.channel.&ExceptionEvent;;
import org.jboss.netty.channel.&MessageEvent;;
import org.jboss.netty.channel.&SimpleChannelHandler;;
@&ChannelPipelineCoverage;("all")<co id="example.discard.co1"/>
public class DiscardServerHandler extends &SimpleChannelHandler; {<co id="example.discard.co2"/>
@ -123,10 +128,6 @@ public class DiscardServerHandler extends &SimpleChannelHandler; {<co id="exampl
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import org.jboss.netty.bootstrap.&ServerBootstrap;;
import org.jboss.netty.channel.&ChannelFactory;;
import org.jboss.netty.channel.socket.nio.&NioServerSocketChannelFactory;;
public class DiscardServer {
public static void main(String[] args) throws Exception {
@ -214,7 +215,7 @@ public class DiscardServer {
<section>
<title>Looking into the Received Data</title>
<para>
Now that we wrote our first server. We need to test if it really works.
Now that we wrote our first server, we need to test if it really works.
The easiest way to test it is to use the <command>telnet</command>
command. For example, you could enter "<command>telnet localhost
8080</command>" in the command line and type something.
@ -252,9 +253,9 @@ public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
</para>
<para>
Although it resembles to NIO <classname>ByteBuffer</classname> a lot,
it is highly recommended to refer to the API reference documentation.
Learning how to use &ChannelBuffer; correctly is a critical step in
using Netty without difficulty.
it is highly recommended to refer to the API reference. Learning how
to use &ChannelBuffer; correctly is a critical step in using Netty
without difficulty.
</para>
</callout>
</calloutlist>
@ -314,7 +315,7 @@ public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
<section>
<title>Writing a Time Server</title>
<para>
The protocol to implement time time is the <ulink url="">TIME</ulink>
The protocol to implement in this section is the <ulink url="">TIME</ulink>
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
@ -330,14 +331,6 @@ public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
</para>
<programlisting>package org.jboss.netty.example.time;
import org.jboss.netty.channel.&ChannelBuffers;;
import org.jboss.netty.channel.&ChannelHandlerContext;;
import org.jboss.netty.channel.&ChannelPipelineCoverage;;
import org.jboss.netty.channel.&ExceptionEvent;;
import org.jboss.netty.channel.&MessageEvent;;
import org.jboss.netty.channel.&SimpleChannelHandler;;
@&ChannelPipelineCoverage;("all")
public class TimeServerHandler extends &SimpleChannelHandler; {
@ -361,8 +354,7 @@ public class TimeServerHandler extends &SimpleChannelHandler; {
@Override
public void exceptionCaught(&ChannelHandlerContext; ctx, &ExceptionEvent; e) {
e.getCause().printStackTrace();
&Channel; ch = e.getChannel();
ch.close();
e.getChannel().close();
}
}</programlisting>
<calloutlist>
@ -387,9 +379,10 @@ public class TimeServerHandler extends &SimpleChannelHandler; {
<para>
On the other hand, it's a good idea to use static imports for
&ChannelBuffers;:
<programlisting>import static org.jboss.netty.buffer.ChannelBuffers.*;
<programlisting>import static org.jboss.netty.buffer.&ChannelBuffers;.*;
...
&ChannelBuffer; buf = dynamicBuffer(1024);</programlisting>
&ChannelBuffer; dynamicBuf = dynamicBuffer(256);
&ChannelBuffer; ordinaryBuf = buffer(1024);</programlisting>
</para>
</callout>
<callout arearefs="example.time.co3">
@ -419,8 +412,7 @@ public class TimeServerHandler extends &SimpleChannelHandler; {
<para>
Another point to note is that the <methodname>write</methodname>
method returns a &ChannelFuture;. A &ChannelFuture; represents an
I/O operation which was not occurred yet. It means, even if you have
called the <methodname>write</methodname> method, the requested
I/O operation which was not occurred yet. 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:
@ -431,8 +423,10 @@ ch.close();</programlisting>
<para>
Therefore, you need to call the <methodname>close</methodname>
method after the &ChannelFuture;, which was returned by the
<methodname>write</methodname> method, tells you that the write
operation has been done.
<methodname>write</methodname> method, notifies you when the write
operation has been done. Also, <methodname>close</methodname>
might not close the connection immediately, and it returns a
&ChannelFuture;.
</para>
</callout>
<callout arearefs="example.time.co4">
@ -443,10 +437,602 @@ ch.close();</programlisting>
which closes the &Channel; when the operation is done.
</para>
<para>
Alternatively, you could simplify the code like this:
Alternatively, you could simplify the code using a pre-defined
listener:
<programlisting>f.addListener(&ChannelFutureListener;.CLOSE);</programlisting>
</para>
</callout>
</calloutlist>
</section>
<section>
<title>Writing a Time Client</title>
<para>
Unlike DISCARD and ECHO servers, we need a client for the TIME protocol
because a human can't translate a 32-bit binary data into a date on a
calendar. Let's make sure the server works correctly and learn how to
write a client with Netty.
</para>
<para>
The biggest and only difference between a server and a client in Netty
is that different &Bootstrap; and &ChannelFactory; are required. Please
take a look at the following code:
</para>
<programlisting>package org.jboss.netty.example.time;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
public class TimeClient {
public static void main(String[] args) throws Exception {
String host = args[0];
int port = Integer.parseInt(args[1]);
&ChannelFactory; factory =
new &NioClientSocketChannelFactory;<co id="example.time2.co1"/>(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
&ClientBootstrap; bootstrap = new &ClientBootstrap;<co id="example.time2.co2"/>(factory);
TimeClientHandler handler = new TimeClientHandler();
bootstrap.getPipeline().addLast("handler", handler);
bootstrap.setOption("tcpNoDelay"<co id="example.time2.co3"/>, true);
bootstrap.setOption("keepAlive", true);
bootstrap.connect<co id="example.time2.co4"/>(new InetSocketAddress(host, port));
}
}</programlisting>
<calloutlist>
<callout arearefs="example.time2.co1">
<para>
&NioClientSocketChannelFactory;, instead of &NioServerSocketChannelFactory;
was used to create a client-side &Channel;.
</para>
</callout>
<callout arearefs="example.time2.co2">
<para>
&ClientBootstrap; is a client-side counterpart of &ServerBootstrap;.
</para>
</callout>
<callout arearefs="example.time2.co3">
<para>
Please note that there's no <literal>"child."</literal> prefix.
A client-side &SocketChannel; doesn't have a parent.
</para>
</callout>
<callout arearefs="example.time2.co4">
<para>
We should call the <methodname>connect</methodname> method instead of
the <methodname>bind</methodname> method.
</para>
</callout>
</calloutlist>
<para>
As you saw, it's not really different from the server side startup. What
about the &ChannelHandler; implementation? It should receive a 32-bit
integer from the server, translate it into a human readable format, print
the translated time, and close the connection:
</para>
<programlisting>package org.jboss.netty.example.time;
@&ChannelPipelineCoverage;("all")
public class TimeClientHandler extends &SimpleChannelHandler; {
@Override
public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
&ChannelBuffer; buf = (&ChannelBuffer;) e.getMessage();
long currentTimeMillis = buf.readInt() * 1000L;
System.out.println(new Date(currentTimeMillis()));
e.getChannel().close();
}
@Override
public void exceptionCaught(&ChannelHandlerContext; ctx, &ExceptionEvent; e) {
e.getCause().printStackTrace();
e.getChannel().close();
}
}</programlisting>
<para>
It looks very simple and doesn't look any different from the server side
example. However, this handler sometimes will refuse to work raising an
<exceptionname>IndexOutOfBoundsException</exceptionname>. Let's figure
out why in the next section.
</para>
</section>
<section>
<title>
Dealing with Packet Fragmentation and Assembly
</title>
<section>
<title>
What is Packet Fragmentation and Assembly?
</title>
<para>
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's assume you have received three packets:
</para>
<programlisting>+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+</programlisting>
<para>
because of the packet fragmentation, a server can receive them like the
following:
</para>
<programlisting>+----+-------+---+---+
| AB | CDEFG | H | I |
+----+-------+---+---+</programlisting>
<para>
Therefore, a receiving part, regardless it's server-side or client-side,
should defrag the received packets into one or more meaningful
<firstterm>frames</firstterm> that could be easily understood by the
application logic. In case of the example above, the received packets
should be defragmented back like the following:
</para>
<programlisting>+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+</programlisting>
</section>
<section>
<title>
The First Solution
</title>
<para>
Now let's get back to the TIME client example. We have the same problem
here. A 32-bit integer is a very small amount of data, and it is not
likely to be fragmented often. However, the problem is that it
<emphasis>can</emphasis> be fragmented, and the possibility of
fragmentation will increase as the traffic goes higher.
</para>
<para>
The simplistic solution is to create an internal cumulative buffer and
wait until all 4 bytes are received into the internal buffer. Here's
the modified <classname>TimeClientHandler</classname> implementation
that fixes the problem.
</para>
<programlisting>package org.jboss.netty.example.time;
import static org.jboss.netty.buffer.&ChannelBuffers;.*;
@&ChannelPipelineCoverage;("one")<co id="example.time3.co1"/>
public class TimeClientHandler extends &SimpleChannelHandler; {
private final &ChannelBuffer; buf = dynamicBuffer();<co id="example.time3.co2"/>
@Override
public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
&ChannelBuffer; m = (&ChannelBuffer;) e.getMessage();
buf.writeBytes(m);<co id="example.time3.co3"/>
if (buf.readableBytes() &gt;= 4) {<co id="example.time3.co4"/>
long currentTimeMillis = buf.readInt() * 1000L;
System.out.println(new Date(currentTimeMillis()));
e.getChannel().close();
}
}
@Override
public void exceptionCaught(&ChannelHandlerContext; ctx, &ExceptionEvent; e) {
e.getCause().printStackTrace();
e.getChannel().close();
}
}</programlisting>
<calloutlist>
<callout arearefs="example.time3.co1">
<para>
This time, <literal>"one"</literal> was used as the value of the
&ChannelPipelineCoverage; annotation. It's because the new
<classname>TimeClientHandler</classname> has to maintain the internal
buffer and therefore cannot serve multiple &Channel;s. If an
instance of <classname>TimeClientHandler</classname> is shared by
multiple &Channel;s (and consequently multiple &ChannelPipeline;s),
the content of the <varname>buf</varname> will be messed up.
</para>
</callout>
<callout arearefs="example.time3.co2">
<para>
A <firstterm>dynamic buffer</firstterm> is a &ChannelBuffer; which
increases its capacity on demand. It's very useful when you don't
know the length of the message.
</para>
</callout>
<callout arearefs="example.time3.co3">
<para>
First, all received data should be cumulated into
<varname>buf</varname>.
</para>
</callout>
<callout arearefs="example.time3.co4">
<para>
And then, the handler must check if <varname>buf</varname> has enough
data, 4 bytes in this example, and proceed to the actual business
logic. Otherwise, Netty will call the
<methodname>messageReceived</methodname> method again when more
data arrives, and eventually all 4 bytes will be cumulated.
</para>
</callout>
</calloutlist>
<para>
There's another place that needs a fix. Do you remember that we have
added a <classname>TimeClientHandler</classname> instance to the
<emphasis>default</emphasis> &ChannelPipeline; of the &ClientBootstrap;?
It means one <classname>TimeClientHandler</classname> is going to handle
multiple &Channel;s and consequently the date will be messed up. To
create a new <classname>TimeClientHandler</classname> instance per
&Channel;, we should implement a &ChannelPipelineFactory;:
</para>
<programlisting>package org.jboss.netty.example.time;
public class TimeClientPipelineFactory implements &ChannelPipelineFactory; {
public &ChannelPipeline; getPipeline() {
&ChannelPipeline; pipeline = &Channels;.pipeline();
pipeline.addLast("handler", new TimeClientHandler());
return pipeline;
}
}</programlisting>
<para>
Now let's replace the following lines of <classname>TimeClient</classname>:
</para>
<programlisting>TimeClientHandler handler = new TimeClientHandler();
bootstrap.getPipeline().addLast("handler", handler);</programlisting>
<para>
with the following:
</para>
<programlisting>bootstrap.getPipelineFactory(new TimeClientPipelineFactory());</programlisting>
<para>
It might look somewhat complicated at the first glance, and it is true
that we don't need to introduce <classname>TimeClientPipelineFactory</classname>
in this particular case because <classname>TimeClient</classname> creates
only one connection.
</para>
<para>
However, as your application gets more and more complex, you will end up
with an implementation of &ChannelPipelineFactory;, which yields much
more flexibility.
</para>
</section>
<section>
<title>
The Second Solution
</title>
<para>
Although the first solution has resolved the problem with the TIME
client, the modified handler doesn't look that clean. Imagine a more
complicated protocol which is composed of multiple fields such as a
variable length field. Your &ChannelHandler; implementation will
become unmaintainable very quickly.
</para>
<para>
As you already might have noticed, you can add more than one
&ChannelHandler; to a &ChannelPipeline;, and therefore, you can
split one monolithic &ChannelHandler; into multiple modular ones to
reduce the complexity of your application. For example, you could
split <classname>TimeClientHandler</classname> into two handlers:
<itemizedlist>
<listitem>
<para>
<classname>TimeDecoder</classname> which deals with the packet
fragmentation and assembly issue, and
</para>
</listitem>
<listitem>
<para>
the initial simple version of <classname>TimeClientHandler</classname>.
</para>
</listitem>
</itemizedlist>
</para>
<para>
Fortunately, Netty provides an extensible class which enables you to
write the first one out of the box.
</para>
<programlisting>package org.jboss.netty.example.time;
<co id="example.time4.co1"/>
public class TimeDecoder extends &FrameDecoder; {
@Override
protected Object decode(
&ChannelHandlerContext; ctx, &Channel; channel, &ChannelBuffer; buffer)<co id="example.time4.co2"/> {
if (buffer.readableBytes() &lt; 4) {
return null; <co id="example.time4.co3"/>
}
return buffer.readBytes(4);<co id="example.time4.co4"/>
}
}</programlisting>
<calloutlist>
<callout arearefs="example.time4.co1">
<para>
There's no &ChannelPipelineCoverage; annotation this time because
&FrameDecoder; is already annotated with <literal>"one"</literal>.
</para>
</callout>
<callout arearefs="example.time4.co2">
<para>
&FrameDecoder; calls <methodname>decode</methodname> method with
an internally maintained cumulative buffer whenever new data is
received.
</para>
</callout>
<callout arearefs="example.time4.co3">
<para>
If <literal>null</literal> is returned, it means there's not
enough data yet. &FrameDecoder; will call again when more data is
in.
</para>
</callout>
<callout arearefs="example.time4.co3">
<para>
If non-<literal>null</literal> is returned, it means the
<methodname>decode</methodname> method has decoded a message
successfully. &FrameDecoder; will discard the read part of its
internal cumulative buffer. Please remember that you don't need
to decode multiple messages. &FrameDecoder; will keep calling
the <methodname>decoder</methodname> method until it returns
<literal>null</literal>.
</para>
</callout>
</calloutlist>
<para>
If you are a adventurous person, you might want to try the
&ReplayingDecoder; which simplifies the decoder even more. You will
need to consult the API reference for more information though.
</para>
<programlisting>package org.jboss.netty.example.time;
public class TimeDecoder extends &ReplayingDecoder;&lt;&VoidEnum;&gt; {
@Override
protected Object decode(
&ChannelHandlerContext; ctx, &Channel; channel, &ChannelBuffer; buffer, &VoidEnum; state) {
return buffer.readBytes(4);
}
}</programlisting>
<para>
Additionally, Netty provides out-of-the-box decoders which enables
you to implement most protocol very easily and helps you avoid from
ending up with a monolithic unmaintainable handler implementation.
You might want to take a look into the following packages for more
detailed examples:
<itemizedlist>
<listitem>
<para>
<literal>org.jboss.netty.example.factorial</literal> for
a binary protocol, and
</para>
</listitem>
<listitem>
<para>
<literal>org.jboss.netty.example.telnet</literal> for
a text line-based protocol.
</para>
</listitem>
</itemizedlist>
</para>
</section>
</section>
<section>
<title>
Speaking in POJO instead of ChannelBuffer
</title>
<para>
All the examples we visited so far used a &ChannelBuffer; as a primary
data structure of a protocol message. In this section, we will improve
the TIME protocol client and server example to use a
<ulink url="http://en.wikipedia.org/wiki/POJO">POJO</ulink> instead of a
&ChannelBuffer;.
</para>
<para>
The advantage of using a POJO in your &ChannelHandler; is obvious;
your handler becomes more maintainable and reusable by separating the
code which extracts information from &ChannelBuffer; out from the
handler. In the TIME client and server examples, we read only one
32-bit integer and it's not a big deal to use &ChannelBuffer; directly.
However, you will find it is necessary to make the separation as you
implement a real world protocol.
</para>
<para>
First, let's define a new type called <classname>UnixTime</classname>.
</para>
<programlisting>package org.jboss.netty.example.time;
public class UnixTime {
private final int value;
public UnixTime(int value) {
this.value = value;
}
public int getValue() {
return value;
}
@Override
public String toString() {
return new Date(value * 1000L).toString();
}
}</programlisting>
<para>
We can now revise the <classname>TimeDecoder</classname> to return
a <classname>UnixTime</classname> instead of a &ChannelBuffer;.
</para>
<programlisting>@Override
protected Object decode(
&ChannelHandlerContext; ctx, &Channel; channel, &ChannelBuffer; buffer) {
if (buffer.readableBytes() &lt; 4) {
return null;
}
return new UnixTime(buffer.readInt());<co id="example.time5.co1"/>
}</programlisting>
<calloutlist>
<callout arearefs="example.time5.co1">
<para>
&FrameDecoder; and &ReplayingDecoder; allow you to return an object
of any type. If they were restricted to return only a
&ChannelBuffer;, we would have to insert another &ChannelHandler;
which transforms a &ChannelBuffer; into a
<classname>UnixTime</classname>.
</para>
</callout>
</calloutlist>
<para>
With the updated decoder, the <classname>TimeClientHandler</classname>
doesn't use &ChannelBuffer; anymore:
</para>
<programlisting>@Override
public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
UnixTime m = (UnixTime) e.getMessage();
System.out.println(m);
e.getChannel().close();
}</programlisting>
<para>
Much simpler and elegant, right? The same technique can be applied on
the server side. Let's update the
<classname>TimeServerHandler</classname> first this time:
</para>
<programlisting>@Override
public void channelConnected(&ChannelHandlerContext; ctx, &ChannelStateEvent; e) {
UnixTime time = new UnixTime(System.currentTimeMillis() / 1000);
&ChannelFuture; f = e.getChannel().write(time);
f.addListener(&ChannelFutureListener;.CLOSE);
}</programlisting>
<para>
Now, the only missing piece is the &ChannelHandler; which translates a
<classname>UnixTime</classname> back into a &ChannelBuffer;. It's much
simpler than writing a decoder because there's no need to deal with
packet fragmentation and assembly when encoding a message.
</para>
<programlisting>package org.jboss.netty.example.time;
import static org.jboss.netty.buffer.&ChannelBuffers;.*;
@&ChannelPipelineCoverage;("all")<co id="example.time6.co1"/>
public class TimeEncoder implements &ChannelDownstreamHandler; {<co id="example.time6.co2"/>
public void handleDownstream(&ChannelHandlerContext; ctx, &ChannelEvent; e) {
if (!(e instanceof &MessageEvent;)) {
ctx.sendDownstream(e);<co id="example.time6.co3"/>
return;
}
UnixTime time = (UnixTime) ((&MessageEvent;) e).getMessage();
&ChannelBuffer; buf = buffer(4);
buf.writeInt(time.getValue());
&Channels;.write(ctx, e.getChannel(), e.getFuture(), buf);<co id="example.time6.co4"/>
}
}</programlisting>
<calloutlist>
<callout arearefs="example.time6.co1">
<para>
The &ChannelPipelineCoverage; value of an encoder is usually
<literal>"all"</literal> because an encoder is stateless in most
cases.
</para>
</callout>
<callout arearefs="example.time6.co2">
<para>
An encoder implements a &ChannelDownstreamHandler; to intercept a
write request. &ChannelDownstreamHandler; is a sub-type of
&ChannelHandler; that is used to intercept the downstream events,
which flows in the opposite direction of the &ChannelEvent;s we have
been processing so far. Please refer to the API reference to learn
more about the difference between a upstream event and a downstream
event.
</para>
</callout>
<callout arearefs="example.time6.co3">
<para>
&MessageEvent; is not the only event that a &ChannelDownstreamHandler;
intercepts. You need to forward the events of a unknown type so that
the other &ChannelDownstreamHandler;s or I/O thread processes them.
</para>
</callout>
<callout arearefs="example.time6.co4">
<para>
Once done with transforming a POJO into a &ChannelBuffer;, you should
forward the new buffer to the previous &ChannelDownstreamHandler; in
the &ChannelPipeline;. &Channels; provides various helper methods
which generates and fires a &ChannelEvent;. In this example,
&Channels;<literal>.write(...)</literal> method creates a new
&MessageEvent; and fires it to the previous &ChannelDownstreamHandler;
in the &ChannelPipeline;.
</para>
<para>
On the other hand, it's a good idea to use static imports for
&Channels;:
<programlisting>import static org.jboss.netty.channel.&Channels;.*;
...
&ChannelPipeline; pipeline = pipeline();
write(ctx, e.getChannel(), e.getFuture(), buf);
fireChannelDisconnected(ctx, e.getChannel());</programlisting>
</para>
</callout>
</calloutlist>
<para>
At this point, you might wonder why you didn't have to forward a
&ChannelEvent; to the next &ChannelUpstreamHandler; in the
&ChannelPipeline; when we write the decoders. Actually, it had been
done behind the scene by &FrameDecoder; and &ReplayingDecoder;.
The <classname>TimeDecoder</classname> would look like the following if
it didn't extend neither &FrameDecoder; nor &SimpleChannelHandler;.
</para>
<programlisting>package org.jboss.netty.example.time;
import static org.jboss.netty.buffer.&ChannelBuffers;.*;
import static org.jboss.netty.channel.&Channels;.*;
@&ChannelPipelineCoverage;("one")
public class TimeDecoder implements &ChannelUpstreamHandler; {
private final &ChannelBuffer; buf = dynamicBuffer();
public void handleUpstream(&ChannelHandlerContext; ctx, &ChannelEvent; e) {
if (!(e instanceof &MessageEvent;)) {
ctx.sendUpstream(e);
return;
}
&ChannelBuffer; m = (&ChannelBuffer;) ((&MessageEvent;) e).getMessage();
buf.writeBytes(m);
while (buf.readableBytes() &gt;= 4) {
UnixTime time = new UnixTime(buf.readInt());
fireMessageReceived(ctx, time);
}
buf.discardReadBytes();
}
}</programlisting>
<para>
The last task left is to add the <classname>TimeEncoder</classname> to
the &ChannelPipeline; of the &ServerBootstrap;, and it's left as a
trivial exercise.
</para>
</section>
<section>
<title>
Summary
</title>
<para>
In this section, we had a quick tour of Netty so that you can start to
write a network application on top of Netty right away. I believe you
still have a lot of questions about various topics, and they will be
covered in the upcoming chapters and the revised version of this chapter.
Also, please don't forget that <ulink url="&Community;">the Netty project
community</ulink> is always here to help you.
</para>
</section>
</chapter>