Documentation cleanup

This commit is contained in:
Trustin Lee 2008-11-14 06:55:02 +00:00
parent 3d83768039
commit 927aaf8d12

View File

@ -22,10 +22,10 @@
<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 and JDK 1.5 or
this chapter are only two; the latest version of Netty and JDK 1.5 or
above. The latest version of Netty is available in
<ulink url="&Downloads;">the project download page</ulink>. To get the
right version of JDK, please refer to your preferred JDK vendor's web
<ulink url="&Downloads;">the project download page</ulink>. To download
the right version of JDK, please refer to your preferred JDK vendor's web
site.
</para>
<para>
@ -53,9 +53,9 @@
a protocol which discards any received data without any response.
</para>
<para>
What you are supposed to do to implement the DISCARD protocol is to log
the received data, and that's all. Let's start straight from the handler
implementation, which handles I/O events generated by Netty.
To implement the DISCARD protocol, you only need to log the received data.
Let us start straight from the handler implementation, which handles I/O
events generated by Netty.
</para>
<programlisting>package org.jboss.netty.example.discard;
@ -80,8 +80,8 @@ public class DiscardServerHandler extends &SimpleChannelHandler; {<co id="exampl
&ChannelPipelineCoverage; annotates a handler type to tell if the
handler instance of the annotated type can be shared by more than
one &Channel; (and its associated &ChannelPipeline;).
<classname>DiscardServerHandler</classname> doesn't manage any
stateful information, and therefore it's annotated with the value
<classname>DiscardServerHandler</classname> does not manage any
stateful information, and therefore it is annotated with the value
<literal>"all"</literal>.
</para>
</callout>
@ -90,7 +90,7 @@ public class DiscardServerHandler extends &SimpleChannelHandler; {<co id="exampl
<classname>DiscardServerHandler</classname> extends
&SimpleChannelHandler;, which is an implementation of
&ChannelHandler;. &SimpleChannelHandler; provides various event
handler methods that you can override. For now, it's just enough
handler methods that you can override. For now, it is just enough
to extend &SimpleChannelHandler; rather than to implement
the handler interfaces by yourself.
</para>
@ -100,8 +100,8 @@ public class DiscardServerHandler extends &SimpleChannelHandler; {<co id="exampl
We override the <methodname>messageReceived</methodname> event
handler method here. This method is called with a &MessageEvent;,
which contains the received data, whenever new data is received
from a client. In this example, we just ignore the received data
by doing nothing to implement the DISCARD protocol.
from a client. In this example, we ignore the received data by doing
nothing to implement the DISCARD protocol.
</para>
</callout>
<callout arearefs="example.discard.co4">
@ -119,9 +119,9 @@ public class DiscardServerHandler extends &SimpleChannelHandler; {<co id="exampl
</callout>
</calloutlist>
<para>
So far so good. We have implemented the half of the DISCARD server.
So far so good. We have implemented the first half of the DISCARD server.
What's left now is to write the <methodname>main</methodname> method
which starts the server up with the <classname>DiscardServerHandler</classname>.
which starts the server with the <classname>DiscardServerHandler</classname>.
</para>
<programlisting>package org.jboss.netty.example.discard;
@ -157,7 +157,7 @@ public class DiscardServer {
&ChannelFactory; implementations. We are implementing a server-side
application in this example, and therefore
&NioServerSocketChannelFactory; was used. Another thing to note is
that it doesn't create I/O threads by itself. It is supposed to
that it does not create I/O threads by itself. It is supposed to
acquire threads from the thread pool you specified in the
constructor, and it gives you more control over how threads should
be managed in the environment where your application runs, such as
@ -167,8 +167,9 @@ public class DiscardServer {
<callout arearefs="example.discard2.co2">
<para>
&ServerBootstrap; is a helper class that sets up a server. You can
set up the server by yourself using a &Channel; directly, but it's a
tedious process and you won't need to do that in most cases.
set up the server using a &Channel; directly. However, please note
that this is a tedious process and you do not need to do that in most
cases.
</para>
</callout>
<callout arearefs="example.discard2.co3">
@ -192,18 +193,18 @@ public class DiscardServer {
<literal>keepAlive</literal>. Please note that the
<literal>"child."</literal> prefix was added to all options. It
means the options will be applied to the accepted &Channel;s instead
of the options of the &ServerSocketChannel;. You could do like the following:
of the options of the &ServerSocketChannel;. You could do the
following to set the options of the &ServerSocketChannel;:
<programlisting>bootstrap.setOption("reuseAddress", true);</programlisting>
to set the options of the &ServerSocketChannel;.
</para>
</callout>
<callout arearefs="example.discard2.co5">
<para>
We are ready to go now. What's left is to bind to the port and to
start the server. Here, we bind to the port <literal>8080</literal>
of all NICs (network interface cards) in the machine. You are fine
to call the <methodname>bind</methodname> method as many times as
you want, with different bind addresses.
of all NICs (network interface cards) in the machine. You can now
call the <methodname>bind</methodname> method as many times as
you want (with different bind addresses.)
</para>
</callout>
</calloutlist>
@ -215,21 +216,21 @@ 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.
The easiest way to test it is to use the <command>telnet</command>
Now that we have written 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.
</para>
<para>
However, can we say that the server is working fine? We can't really
know that because it's a discard server. You will not going to get any
response at all. To prove it's really working, let's modify the server
to print what it has received.
However, can we say that the server is working fine? We cannot really
know that because it is a discard server. You will not get any response
at all. To prove it is really working, let us modify the server to print
what it has received.
</para>
<para>
We already know that &MessageEvent; is generated whenever data is
received and the <methodname>messageReceived</methodname> handler method
will be invoked. Let's put some code into the
will be invoked. Let us put some code into the
<methodname>messageReceived</methodname> method of the
<classname>DiscardServerHandler</classname>:
</para>
@ -246,7 +247,7 @@ public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
It is safe to assume the message type in socket transports is always
&ChannelBuffer;. &ChannelBuffer; is a fundamental data structure
which stores a sequence of bytes in Netty. It's similar to NIO
<classname>ByteBuffer</classname>, but it's easier to use and more
<classname>ByteBuffer</classname>, but it is easier to use and more
flexible. For example, Netty allows you to create a composite
&ChannelBuffer; which combines multiple &ChannelBuffer;s reducing
the number of unnecessary memory copy.
@ -273,7 +274,7 @@ public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
<title>Writing an Echo Server</title>
<para>
So far, we have been consuming data without responding at all. A server,
however, is usually supposed to respond to a request. Let's learn how to
however, is usually supposed to respond to a request. Let us learn how to
write a response message to a client by implementing the
<ulink url="http://tools.ietf.org/html/rfc862">ECHO</ulink> protocol,
where any received data is sent back.
@ -281,9 +282,8 @@ public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
<para>
The only difference from the discard server we have implemented in the
previous sections is that it sends the received data back instead of
printing the received data out to the console. Therefore, it's just
enough again to modify the <methodname>messageReceived</methodname>
method:
printing the received data out to the console. Therefore, it is enough
again to modify the <methodname>messageReceived</methodname> method:
</para>
<programlisting>@Override
public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
@ -324,10 +324,10 @@ public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
</para>
<para>
Because we are going to ignore any received data but to send a message
as soon as a connection is established, we can't use the
as soon as a connection is established, we cannot use the
<methodname>messageReceived</methodname> method this time. Instead,
we should override the <methodname>channelConnected</methodname> method.
Here's the implementation:
The following is the implementation:
</para>
<programlisting>package org.jboss.netty.example.time;
@ -361,7 +361,7 @@ public class TimeServerHandler extends &SimpleChannelHandler; {
<callout arearefs="example.time.co1">
<para>
As explained, <methodname>channelConnected</methodname> method will
be invoked when a connection is established. Let's write the 32-bit
be invoked when a connection is established. Let us write the 32-bit
integer that represents the current time in seconds here.
</para>
</callout>
@ -373,11 +373,11 @@ public class TimeServerHandler extends &SimpleChannelHandler; {
<literal>4</literal> bytes. The &ChannelBuffers; helper class is
used to allocate a new buffer. Besides the
<methodname>buffer</methodname> method, &ChannelBuffers; provides a
lot of useful methods related with &ChannelBuffer;. Please refer to
the API reference.
lot of useful methods related to the &ChannelBuffer;. For more
information, please refer to the API reference.
</para>
<para>
On the other hand, it's a good idea to use static imports for
On the other hand, it is a good idea to use static imports for
&ChannelBuffers;:
<programlisting>import static org.jboss.netty.buffer.&ChannelBuffers;.*;
...
@ -392,19 +392,19 @@ public class TimeServerHandler extends &SimpleChannelHandler; {
<para>
But wait, where's the <methodname>flip</methodname>? Didn't we used
to call <methodname>ByteBuffer.flip()</methodname> before sending a
message in NIO? &ChannelBuffer; doesn't have such a method because
message in NIO? &ChannelBuffer; 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
a &ChannelBuffer; while the reader index doesn't change. The reader
a &ChannelBuffer; while the reader index does not change. The reader
index and the writer index represents where the message starts and
ends respectively.
</para>
<para>
In contrast, NIO buffer doesn't provide a clean way to figure out
In contrast, NIO buffer does not provide a clean way to figure out
where the message content starts and ends without calling the
<methodname>flip</methodname> method. You will be in trouble when
you forget to flip the buffer because nothing or incorrect data will
be sent. Such an error doesn't happen in Netty because we have
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!
@ -412,7 +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, any requested
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:
@ -424,7 +424,7 @@ ch.close();</programlisting>
Therefore, you need to call the <methodname>close</methodname>
method after the &ChannelFuture;, which was returned by the
<methodname>write</methodname> method, notifies you when the write
operation has been done. Also, <methodname>close</methodname>
operation has been done. Please note that, <methodname>close</methodname>
might not close the connection immediately, and it returns a
&ChannelFuture;.
</para>
@ -432,7 +432,7 @@ ch.close();</programlisting>
<callout arearefs="example.time.co4">
<para>
How do we get notified when the write request is finished then?
It's as simple as adding a &ChannelFutureListener; to the returned
This is as simple as adding a &ChannelFutureListener; to the returned
&ChannelFuture;. Here, we created a new anonymous &ChannelFutureListener;
which closes the &Channel; when the operation is done.
</para>
@ -449,9 +449,9 @@ ch.close();</programlisting>
<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.
because a human cannot translate a 32-bit binary data into a date on a
calendar. In this section, we discuss how to 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
@ -500,7 +500,7 @@ public class TimeClient {
<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.
A client-side &SocketChannel; does not have a parent.
</para>
</callout>
<callout arearefs="example.time2.co4">
@ -511,10 +511,10 @@ public class TimeClient {
</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:
As you can see, it is 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;
@ -536,10 +536,10 @@ public class TimeClientHandler extends &SimpleChannelHandler; {
}
}</programlisting>
<para>
It looks very simple and doesn't look any different from the server side
It looks very simple and does not 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.
<exceptionname>IndexOutOfBoundsException</exceptionname>. We discuss why
this happens in the next section.
</para>
</section>
@ -554,20 +554,20 @@ public class TimeClientHandler extends &SimpleChannelHandler; {
<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:
let us 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:
because of the packet fragmentation, a server can receive them as
follows:
</para>
<programlisting>+----+-------+---+---+
| AB | CDEFG | H | I |
+----+-------+---+---+</programlisting>
<para>
Therefore, a receiving part, regardless it's server-side or client-side,
Therefore, a receiving part, regardless it is 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
@ -582,17 +582,17 @@ public class TimeClientHandler extends &SimpleChannelHandler; {
The First Solution
</title>
<para>
Now let's get back to the TIME client example. We have the same problem
Now let us 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.
fragmentation will increase as the traffic increases.
</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.
wait until all 4 bytes are received into the internal buffer. The
following is the modified <classname>TimeClientHandler</classname>
implementation that fixes the problem:
</para>
<programlisting>package org.jboss.netty.example.time;
@ -630,7 +630,7 @@ public class TimeClientHandler extends &SimpleChannelHandler; {
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.
the content of the <varname>buf</varname> will be corrupted.
</para>
</callout>
<callout arearefs="example.time3.co2">
@ -657,11 +657,11 @@ public class TimeClientHandler extends &SimpleChannelHandler; {
</callout>
</calloutlist>
<para>
There's another place that needs a fix. Do you remember that we have
There's another place that needs a fix. Do you remember that we
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 data will be messed up. To
multiple &Channel;s and consequently the data will be corrupted. To
create a new <classname>TimeClientHandler</classname> instance per
&Channel;, we should implement a &ChannelPipelineFactory;:
</para>
@ -676,7 +676,7 @@ public class TimeClientPipelineFactory implements &ChannelPipelineFactory; {
}
}</programlisting>
<para>
Now let's replace the following lines of <classname>TimeClient</classname>:
Now let us replace the following lines of <classname>TimeClient</classname>:
</para>
<programlisting>TimeClientHandler handler = new TimeClientHandler();
bootstrap.getPipeline().addLast("handler", handler);</programlisting>
@ -702,17 +702,17 @@ bootstrap.getPipeline().addLast("handler", handler);</programlisting>
</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
client, the modified handler does not 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:
As you may 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>
@ -764,8 +764,8 @@ public class TimeDecoder extends &FrameDecoder; {
<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.
enough data yet. &FrameDecoder; will call again when there is a
sufficient amount of data.
</para>
</callout>
<callout arearefs="example.time4.co3">
@ -781,7 +781,7 @@ public class TimeDecoder extends &FrameDecoder; {
</callout>
</calloutlist>
<para>
If you are a adventurous person, you might want to try the
If you are an 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>
@ -798,10 +798,9 @@ public class TimeDecoder extends &ReplayingDecoder;&lt;&VoidEnum;&gt; {
}</programlisting>
<para>
Additionally, Netty provides out-of-the-box decoders which enables
you to implement most protocol very easily and helps you avoid from
you to implement most protocols 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:
Please refer to the following packages for more detailed examples:
<itemizedlist>
<listitem>
<para>
@ -825,9 +824,9 @@ public class TimeDecoder extends &ReplayingDecoder;&lt;&VoidEnum;&gt; {
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
All the examples we have reviewed 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>
@ -836,12 +835,12 @@ public class TimeDecoder extends &ReplayingDecoder;&lt;&VoidEnum;&gt; {
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.
32-bit integer and it is not a major issue 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>.
First, let us define a new type called <classname>UnixTime</classname>.
</para>
<programlisting>package org.jboss.netty.example.time;
@ -887,7 +886,7 @@ protected Object decode(
</calloutlist>
<para>
With the updated decoder, the <classname>TimeClientHandler</classname>
doesn't use &ChannelBuffer; anymore:
does not use &ChannelBuffer; anymore:
</para>
<programlisting>@Override
public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
@ -897,7 +896,7 @@ public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
}</programlisting>
<para>
Much simpler and elegant, right? The same technique can be applied on
the server side. Let's update the
the server side. Let us update the
<classname>TimeServerHandler</classname> first this time:
</para>
<programlisting>@Override
@ -963,7 +962,7 @@ public class TimeEncoder implements &SimpleChannelHandler; {
in the &ChannelPipeline;.
</para>
<para>
On the other hand, it's a good idea to use static imports for
On the other hand, it is a good idea to use static imports for
&Channels;:
<programlisting>import static org.jboss.netty.channel.&Channels;.*;
...
@ -975,7 +974,7 @@ fireChannelDisconnected(ctx, e.getChannel());</programlisting>
</calloutlist>
<para>
The last task left is to insert a <classname>TimeEncoder</classname>
into the &ChannelPipeline; on the server side, and it's left as a
into the &ChannelPipeline; on the server side, and it is left as a
trivial exercise.
</para>
</section>
@ -985,12 +984,11 @@ fireChannelDisconnected(ctx, e.getChannel());</programlisting>
Summary
</title>
<para>
In this chapter, 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.
In this chapter, we had a quick tour of Netty with a demonstration on how
to write a network application on top of Netty. More questions you may
have will be covered in the upcoming chapters and the revised version of
this chapter. Please also note that <ulink url="&Community;">the Netty
project community</ulink> is always here to help you.
</para>
</section>
</chapter>