Finished the first chapter
This commit is contained in:
parent
8d8c7c0227
commit
f206c5359b
@ -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>">
|
||||
|
||||
|
||||
|
@ -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() >= 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() < 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;<&VoidEnum;> {
|
||||
|
||||
@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() < 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() >= 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>
|
||||
|
Loading…
Reference in New Issue
Block a user