* Updated PDF XSL

* proven approach for -> proven approach to
* Keep writing the guide...
This commit is contained in:
Trustin Lee 2008-09-22 05:47:10 +00:00
parent d3f24589a4
commit 8d8c7c0227
4 changed files with 286 additions and 15 deletions

View File

@ -13,16 +13,25 @@
<!ENTITY ClientBootstrap "<ulink url='&API;bootstrap/ClientBootstrap.html'><classname>ClientBootstrap</classname></ulink>"> <!ENTITY ClientBootstrap "<ulink url='&API;bootstrap/ClientBootstrap.html'><classname>ClientBootstrap</classname></ulink>">
<!ENTITY ServerBootstrap "<ulink url='&API;bootstrap/ServerBootstrap.html'><classname>ServerBootstrap</classname></ulink>"> <!ENTITY ServerBootstrap "<ulink url='&API;bootstrap/ServerBootstrap.html'><classname>ServerBootstrap</classname></ulink>">
<!-- Types in the buffer package -->
<!ENTITY ChannelBuffer "<ulink url='&API;buffer/ChannelBuffer.html'><interfacename>ChannelBuffer</interfacename></ulink>">
<!ENTITY ChannelBuffers "<ulink url='&API;buffer/ChannelBuffers.html'><classname>ChannelBuffers</classname></ulink>">
<!-- Types in the channel package --> <!-- Types in the channel package -->
<!ENTITY Channel "<ulink url='&API;channel/Channel.html'><interfacename>Channel</interfacename></ulink>"> <!ENTITY Channel "<ulink url='&API;channel/Channel.html'><interfacename>Channel</interfacename></ulink>">
<!ENTITY ChannelDownstreamHandler "<ulink url='&API;channel/ChannelDownstreamHandler.html'><interfacename>ChannelDownstreamHandler</interfacename></ulink>"> <!ENTITY ChannelDownstreamHandler "<ulink url='&API;channel/ChannelDownstreamHandler.html'><interfacename>ChannelDownstreamHandler</interfacename></ulink>">
<!ENTITY ChannelEvent "<ulink url='&API;channel/ChannelEvent.html'><interfacename>ChannelEvent</interfacename></ulink>"> <!ENTITY ChannelEvent "<ulink url='&API;channel/ChannelEvent.html'><interfacename>ChannelEvent</interfacename></ulink>">
<!ENTITY ChannelFactory "<ulink url='&API;channel/ChannelFactory.html'><interfacename>ChannelFactory</interfacename></ulink>"> <!ENTITY ChannelFactory "<ulink url='&API;channel/ChannelFactory.html'><interfacename>ChannelFactory</interfacename></ulink>">
<!ENTITY ChannelFuture "<ulink url='&API;channel/ChannelFuture.html'><interfacename>ChannelFuture</interfacename></ulink>">
<!ENTITY ChannelFutureListener "<ulink url='&API;channel/ChannelFutureListener.html'><interfacename>ChannelFutureListener</interfacename></ulink>">
<!ENTITY ChannelHandler "<ulink url='&API;channel/ChannelHandler.html'><interfacename>ChannelHandler</interfacename></ulink>"> <!ENTITY ChannelHandler "<ulink url='&API;channel/ChannelHandler.html'><interfacename>ChannelHandler</interfacename></ulink>">
<!ENTITY ChannelHandlerContext "<ulink url='&API;channel/ChannelHandlerContext.html'><interfacename>ChannelHandlerContext</interfacename></ulink>"> <!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 ChannelPipeline "<ulink url='&API;channel/ChannelPipeline.html'><interfacename>ChannelPipeline</interfacename></ulink>">
<!ENTITY ChannelPipelineCoverage "<ulink url='&API;channel/ChannelPipelineCoverage.html'><interfacename>ChannelPipelineCoverage</interfacename></ulink>"> <!ENTITY ChannelPipelineCoverage "<ulink url='&API;channel/ChannelPipelineCoverage.html'><interfacename>ChannelPipelineCoverage</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>"> <!ENTITY ChannelUpstreamHandler "<ulink url='&API;channel/ChannelUpstreamHandler.html'><interfacename>ChannelUpstreamHandler</interfacename></ulink>">
<!ENTITY ExceptionEvent "<ulink url='&API;channel/ExceptionEvent.html'><interfacename>ExceptionEvent</interfacename></ulink>"> <!ENTITY ExceptionEvent "<ulink url='&API;channel/ExceptionEvent.html'><interfacename>ExceptionEvent</interfacename></ulink>">
<!ENTITY MessageEvent "<ulink url='&API;channel/MessageEvent.html'><interfacename>MessageEvent</interfacename></ulink>"> <!ENTITY MessageEvent "<ulink url='&API;channel/MessageEvent.html'><interfacename>MessageEvent</interfacename></ulink>">

View File

@ -6,7 +6,7 @@
<book lang="en"> <book lang="en">
<bookinfo> <bookinfo>
<title>The Netty Project 3.0 User Guide</title> <title>The Netty Project 3.0 User Guide</title>
<subtitle>The Proven Approach for Rapid Network Application Development</subtitle> <subtitle>The Proven Approach to Rapid Network Application Development</subtitle>
<releaseinfo> <releaseinfo>
<!-- The version.txt file is generated by maven-antrun-plugin. --> <!-- The version.txt file is generated by maven-antrun-plugin. -->
<xi:include href="../../target/version.txt" parse="text" <xi:include href="../../target/version.txt" parse="text"

View File

@ -17,7 +17,7 @@
</para> </para>
<section> <section>
<title>Minimum Requirement</title> <title>Minimum Requirements</title>
<para> <para>
The minimum requirements to run the examples which are introduced in The minimum requirements to run the examples which are introduced in
this chapter are just two; the latest version of Netty release and JDK this chapter are just two; the latest version of Netty release and JDK
@ -191,18 +191,260 @@ public class DiscardServer {
<literal>keepAlive</literal>. Please note that the <literal>keepAlive</literal>. Please note that the
<literal>"child."</literal> prefix was added to all options. It <literal>"child."</literal> prefix was added to all options. It
means the options will be applied to the accepted &Channel;s instead means the options will be applied to the accepted &Channel;s instead
of the options of the &ServerSocketChannel;. To set the options of of the options of the &ServerSocketChannel;. You could do like the following:
the &ServerSocketChannel;, you could do the following:
<programlisting>bootstrap.setOption("reuseAddress", true);</programlisting> <programlisting>bootstrap.setOption("reuseAddress", true);</programlisting>
to set the options of the &ServerSocketChannel;.
</para> </para>
</callout> </callout>
<callout arearefs="example.discard2.co5"> <callout arearefs="example.discard2.co5">
<para> <para>
OK, we are all set. What's left is bind to the port to start the We are ready to go now. What's left is to bind to the port and to
server. Here, we bind to the port <literal>8080</literal> of all start the server. Here, we bind to the port <literal>8080</literal>
NICs in the machine. You are allowed to call the of all NICs (network interface cards) in the machine. You are fine
<methodname>bind</methodname> method as many times as you want with to call the <methodname>bind</methodname> method as many times as
different bind addresses. you want, with different bind addresses.
</para>
</callout>
</calloutlist>
<para>
Congratulations! You've just finished your first server on top of Netty.
</para>
</section>
<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>
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.
</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
<methodname>messageReceived</methodname> method of the
<classname>DiscardServerHandler</classname>:
</para>
<programlisting>@Override
public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
&ChannelBuffer;<co id="example.discard3.co1"/> buf = (ChannelBuffer) e.getMessage();
while(buf.readable()) {
System.out.println((char) buf.readByte(i));
}
}</programlisting>
<calloutlist>
<callout arearefs="example.discard3.co1">
<para>
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
flexible. For example, Netty allows you to create a composite
&ChannelBuffer; which combines multiple &ChannelBuffer;s reducing
the number of unnecessary memory copy.
</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.
</para>
</callout>
</calloutlist>
<para>
If you run the <command>telnet</command> command again, you will see the
server prints what has received.
</para>
<para>
The full source code of the discard server is located in the
<literal>org.jboss.netty.example.discard</literal> package of the
distribution.
</para>
</section>
<section>
<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
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.
</para>
<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:
</para>
<programlisting>@Override
public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; e) {
&Channel;<co id="example.echo.co1"/> ch = e.getChannel();
ch.write(e.getMessage());
}</programlisting>
<calloutlist>
<callout arearefs="example.echo.co1">
<para>
A &ChannelEvent; object has a reference to its associated &Channel;.
Here, the returned &Channel; represents the connection which received
the &MessageEvent;. We can get the &Channel; and call the
<methodname>write</methodname> method to write something back to
the remote peer.
</para>
</callout>
</calloutlist>
<para>
If you run the <command>telnet</command> command again, you will see the
server sends back whatever you have sent to it.
</para>
<para>
The full source code of the echo server is located in the
<literal>org.jboss.netty.example.echo</literal> package of the
distribution.
</para>
</section>
<section>
<title>Writing a Time Server</title>
<para>
The protocol to implement time time 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
will learn how to construct and send a message, and to close the
connection on completion.
</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
<methodname>messageReceived</methodname> method this time. Instead,
we should override the <methodname>channelConnected</methodname> method.
Here's the implementation:
</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; {
@Override
public void channelConnected(&ChannelHandlerContext; ctx, &ChannelStateEvent; e) {<co id="example.time.co1"/>
&Channel; ch = e.getChannel();
&ChannelBuffer; time = &ChannelBuffers;.buffer(4);<co id="example.time.co2"/>
time.writeInt(System.currentTimeMillis() / 1000);
&ChannelFuture; f = ch.write(time);<co id="example.time.co3"/>
f.addListener(new &ChannelFutureListener;() {<co id="example.time.co4"/>
public void operationComplete(&ChannelFuture; f) {
&Channel; ch = future.getChannel();
ch.close();
}
});
}
@Override
public void exceptionCaught(&ChannelHandlerContext; ctx, &ExceptionEvent; e) {
e.getCause().printStackTrace();
&Channel; ch = e.getChannel();
ch.close();
}
}</programlisting>
<calloutlist>
<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
integer that represents the current time in seconds here.
</para>
</callout>
<callout arearefs="example.time.co2">
<para>
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 &ChannelBuffer; whose capacity is
<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.
</para>
<para>
On the other hand, it's a good idea to use static imports for
&ChannelBuffers;:
<programlisting>import static org.jboss.netty.buffer.ChannelBuffers.*;
...
&ChannelBuffer; buf = dynamicBuffer(1024);</programlisting>
</para>
</callout>
<callout arearefs="example.time.co3">
<para>
As usual, we write the constructed message.
</para>
<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
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
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
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
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!
</para>
<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
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:
</para>
<programlisting>&Channel; ch = ...;
ch.write(message);
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.
</para>
</callout>
<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
&ChannelFuture;. Here, we created a new anonymous &ChannelFutureListener;
which closes the &Channel; when the operation is done.
</para>
<para>
Alternatively, you could simplify the code like this:
<programlisting>f.addListener(&ChannelFutureListener;.CLOSE);</programlisting>
</para> </para>
</callout> </callout>
</calloutlist> </calloutlist>

View File

@ -30,13 +30,33 @@
</xsl:attribute-set> </xsl:attribute-set>
<!-- Decrease the link font size in the program listing --> <!-- Decrease the link font size in the program listing -->
<xsl:template match="//programlisting/*/text()|//programlisting/*/*/text()"> <xsl:attribute-set name="monospace.properties">
<fo:inline font-size="80%" margin="0em" padding="0em"> <xsl:attribute name="font-size">1em</xsl:attribute>
<xsl:value-of select="." /> <xsl:attribute name="font-family">
</fo:inline> <xsl:value-of select="$monospace.font.family"/>
</xsl:attribute>
</xsl:attribute-set>
<!-- Add some spacing between callout listing items -->
<xsl:template match="callout">
<xsl:variable name="id"><xsl:call-template name="object.id"/></xsl:variable>
<fo:list-item id="{$id}" space-before="1em">
<fo:list-item-label end-indent="label-end()">
<fo:block>
<xsl:call-template name="callout.arearefs">
<xsl:with-param name="arearefs" select="@arearefs"/>
</xsl:call-template>
</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent="body-start()">
<fo:block padding-top="0.2em">
<xsl:apply-templates/>
</fo:block>
</fo:list-item-body>
</fo:list-item>
</xsl:template> </xsl:template>
<!-- Slight baseline-shift --> <!-- Slight baseline-shift for callouts in the program listing -->
<xsl:template name="callout-bug"> <xsl:template name="callout-bug">
<xsl:param name="conum" select='1'/> <xsl:param name="conum" select='1'/>
<xsl:choose> <xsl:choose>