<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Java &#38; Game &#187; netty</title>
	<atom:link href="http://www.javagg.com/archives/tag/netty/feed" rel="self" type="application/rss+xml" />
	<link>http://www.javagg.com</link>
	<description>java&#38;j2me</description>
	<lastBuildDate>Sat, 02 Jul 2011 08:59:18 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>使用netty 作为 http服务器</title>
		<link>http://www.javagg.com/archives/818</link>
		<comments>http://www.javagg.com/archives/818#comments</comments>
		<pubDate>Mon, 11 Oct 2010 15:45:04 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[java]]></category>
		<category><![CDATA[netty]]></category>

		<guid isPermaLink="false">http://www.javagg.com/?p=818</guid>
		<description><![CDATA[netty是继mina之后一个非常受欢迎的nio网络框架（其实netty的主程就是mina的主程）.
其实netty的介绍就不说了，去看项目介绍吧，直接上代码
httpserver启动和配置类
import static org.jboss.netty.channel.Channels.pipeline;   
  
import java.net.InetSocketAddress;   
import java.util.concurrent.Executors;   
  
import org.jboss.netty.bootstrap.ServerBootstrap;   
import org.jboss.netty.channel.ChannelPipeline;   
import org.jboss.netty.channel.ChannelPipelineFactory;   
import org.jboss.netty.channel.Channels;   
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;   
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;   
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;   
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;   
import org.jboss.netty.handler.stream.ChunkedWriteHandler;   
  
/**  
 * 后台管理服务  
 *   
 * @author javagg  
 *   
 */  
public class AdminServer {   
    public static void main(String[] args) {   
        start(8080);   
    }   
  
    public static void start(int port) {   
        // 配置服务器-使用java线程池作为解释线程   
        ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));   
        // 设置 pipeline factory.   
        bootstrap.setPipelineFactory(new ServerPipelineFactory());   
        // 绑定端口   
        bootstrap.bind(new InetSocketAddress(port));   
        System.out.println(&#8220;admin start on &#8221;+port);   
    }   
  
    private static class ServerPipelineFactory implements  
            ChannelPipelineFactory {   
        public ChannelPipeline getPipeline() throws Exception {   
            // Create a default pipeline implementation.   
            ChannelPipeline pipeline = Channels.pipeline();   
            pipeline.addLast(&#8220;decoder&#8221;, new HttpRequestDecoder());   
            pipeline.addLast(&#8220;encoder&#8221;, new HttpResponseEncoder());   
            //http处理handler   
            pipeline.addLast(&#8220;handler&#8221;, new AdminServerHandler());   
            return pipeline;   
        }   
    }   
}  
 
handler类
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;   
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;   
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;   
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK;   
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;   
  
import java.util.HashMap;   
  
import mmo.test.UITestHandle;   
  
import org.jboss.netty.buffer.ChannelBuffers;   
import org.jboss.netty.buffer.DynamicChannelBuffer;   
import org.jboss.netty.channel.Channel;   
import org.jboss.netty.channel.ChannelFutureListener;   
import org.jboss.netty.channel.ChannelHandlerContext;   
import org.jboss.netty.channel.ExceptionEvent;   
import org.jboss.netty.channel.MessageEvent; [...]]]></description>
			<content:encoded><![CDATA[<p>netty是继mina之后一个非常受欢迎的nio网络框架（其实netty的主程就是mina的主程）.</p>
<p>其实netty的介绍就不说了，去看项目介绍吧，直接上代码</p>
<p>httpserver启动和配置类</p>
<li>import static org.jboss.netty.channel.Channels.pipeline;   </li>
<li>  </li>
<li>import java.net.InetSocketAddress;   </li>
<li>import java.util.concurrent.Executors;   </li>
<li>  </li>
<li>import org.jboss.netty.bootstrap.ServerBootstrap;   </li>
<li>import org.jboss.netty.channel.ChannelPipeline;   </li>
<li>import org.jboss.netty.channel.ChannelPipelineFactory;   </li>
<li>import org.jboss.netty.channel.Channels;   </li>
<li>import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;   </li>
<li>import org.jboss.netty.handler.codec.http.HttpChunkAggregator;   </li>
<li>import org.jboss.netty.handler.codec.http.HttpRequestDecoder;   </li>
<li>import org.jboss.netty.handler.codec.http.HttpResponseEncoder;   </li>
<li>import org.jboss.netty.handler.stream.ChunkedWriteHandler;   </li>
<li>  </li>
<li>/**  </li>
<li> * 后台管理服务  </li>
<li> *   </li>
<li> * @author javagg  </li>
<li> *   </li>
<li> */  </li>
<li>public class AdminServer {   </li>
<li>    public static void main(String[] args) {   </li>
<li>        start(8080);   </li>
<li>    }   </li>
<li>  </li>
<li>    public static void start(int port) {   </li>
<li>        // 配置服务器-使用java线程池作为解释线程   </li>
<li>        ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));   </li>
<li>        // 设置 pipeline factory.   </li>
<li>        bootstrap.setPipelineFactory(new ServerPipelineFactory());   </li>
<li>        // 绑定端口   </li>
<li>        bootstrap.bind(new InetSocketAddress(port));   </li>
<li>        System.out.println(&#8220;admin start on &#8221;+port);   </li>
<li>    }   </li>
<li>  </li>
<li>    private static class ServerPipelineFactory implements  </li>
<li>            ChannelPipelineFactory {   </li>
<li>        public ChannelPipeline getPipeline() throws Exception {   </li>
<li>            // Create a default pipeline implementation.   </li>
<li>            ChannelPipeline pipeline = Channels.pipeline();   </li>
<li>            pipeline.addLast(&#8220;decoder&#8221;, new HttpRequestDecoder());   </li>
<li>            pipeline.addLast(&#8220;encoder&#8221;, new HttpResponseEncoder());   </li>
<li>            //http处理handler   </li>
<li>            pipeline.addLast(&#8220;handler&#8221;, new AdminServerHandler());   </li>
<li>            return pipeline;   </li>
<li>        }   </li>
<li>    }   </li>
<li>}  </li>
<p> <span id="more-818"></span></p>
<p>handler类</p>
<li>import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;   </li>
<li>import static org.jboss.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;   </li>
<li>import static org.jboss.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;   </li>
<li>import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK;   </li>
<li>import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;   </li>
<li>  </li>
<li>import java.util.HashMap;   </li>
<li>  </li>
<li>import mmo.test.UITestHandle;   </li>
<li>  </li>
<li>import org.jboss.netty.buffer.ChannelBuffers;   </li>
<li>import org.jboss.netty.buffer.DynamicChannelBuffer;   </li>
<li>import org.jboss.netty.channel.Channel;   </li>
<li>import org.jboss.netty.channel.ChannelFutureListener;   </li>
<li>import org.jboss.netty.channel.ChannelHandlerContext;   </li>
<li>import org.jboss.netty.channel.ExceptionEvent;   </li>
<li>import org.jboss.netty.channel.MessageEvent;   </li>
<li>import org.jboss.netty.channel.SimpleChannelUpstreamHandler;   </li>
<li>import org.jboss.netty.handler.codec.frame.TooLongFrameException;   </li>
<li>import org.jboss.netty.handler.codec.http.DefaultHttpResponse;   </li>
<li>import org.jboss.netty.handler.codec.http.HttpRequest;   </li>
<li>import org.jboss.netty.handler.codec.http.HttpResponse;   </li>
<li>import org.jboss.netty.handler.codec.http.HttpResponseStatus;   </li>
<li>import org.jboss.netty.util.CharsetUtil;   </li>
<li>  </li>
<li>/**  </li>
<li> * @author javagg  </li>
<li> */  </li>
<li>public class AdminServerHandler extends SimpleChannelUpstreamHandler {   </li>
<li>  </li>
<li>    @Override  </li>
<li>    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)   </li>
<li>            throws Exception {   </li>
<li>        HttpRequest request = (HttpRequest) e.getMessage();   </li>
<li>        String uri = request.getUri();   </li>
<li>                          System.out.println(&#8220;uri:&#8221;+uri);   </li>
<li>        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);   </li>
<li>                          ChannelBuffer buffer=new DynamicChannelBuffer(2048);   </li>
<li>        buffer.writeBytes(&#8220;hello!! 你好&#8221;.getBytes(&#8220;UTF-8&#8243;));   </li>
<li>        response.setContent(buffer);   </li>
<li>        response.setHeader(&#8220;Content-Type&#8221;, &#8221;text/html; charset=UTF-8&#8243;);   </li>
<li>        response.setHeader(&#8220;Content-Length&#8221;, response.getContent().writerIndex());   </li>
<li>        Channel ch = e.getChannel();   </li>
<li>        // Write the initial line and the header.   </li>
<li>        ch.write(response);   </li>
<li>        ch.disconnect();   </li>
<li>        ch.close();   </li>
<li>           </li>
<li>    }   </li>
<li>  </li>
<li>    @Override  </li>
<li>    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)   </li>
<li>            throws Exception {   </li>
<li>        Channel ch = e.getChannel();   </li>
<li>        Throwable cause = e.getCause();   </li>
<li>        if (cause instanceof TooLongFrameException) {   </li>
<li>            sendError(ctx, BAD_REQUEST);   </li>
<li>            return;   </li>
<li>        }   </li>
<li>  </li>
<li>        cause.printStackTrace();   </li>
<li>        if (ch.isConnected()) {   </li>
<li>            sendError(ctx, INTERNAL_SERVER_ERROR);   </li>
<li>        }   </li>
<li>    }   </li>
<li>  </li>
<li>    private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {   </li>
<li>        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, status);   </li>
<li>        response.setHeader(CONTENT_TYPE, &#8221;text/plain; charset=UTF-8&#8243;);   </li>
<li>        response.setContent(ChannelBuffers.copiedBuffer(&#8220;Failure: &#8221; + status.toString() + &#8221;\r\n&#8221;, CharsetUtil.UTF_8));   </li>
<li>  </li>
<li>        // Close the connection as soon as the error message is sent.   </li>
<li>        ctx.getChannel().write(response).addListener(ChannelFutureListener.CLOSE);   </li>
<li>    }   </li>
<li>}  </li>
<pre></pre>
<p> </p>
<p>搞定，启动服务器后在ie上输入<a href="http://localhost:8080/">http://localhost:8080/</a> 就看到   hello!! 你好</p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/818/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Netty实现原理浅析</title>
		<link>http://www.javagg.com/archives/813</link>
		<comments>http://www.javagg.com/archives/813#comments</comments>
		<pubDate>Wed, 28 Jul 2010 08:25:48 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[java]]></category>
		<category><![CDATA[netty]]></category>
		<category><![CDATA[nio]]></category>

		<guid isPermaLink="false">http://www.javagg.com/?p=813</guid>
		<description><![CDATA[Netty是JBoss出品的高效的Java NIO开发框架，关于其使用，可参考我的另一篇文章 netty使用初步。本文将主要分析Netty实现方面的东西，由于精力有限，本人并没有对其源码做了极细致的研 究。如果下面的内容有错误或不严谨的地方，也请指正和谅解。对于Netty使用者来说，Netty提供了几个典型的example，并有详尽的API doc和guide doc，本文的一些内容及图示也来自于Netty的文档，特此致谢。
1、总体结构

先放上一张漂亮的Netty总体结构图，下面的内容也主要围绕该图上的一些核心功能做分析，但对如Container Integration及Security Support等高级可选功能，本文不予分析。
2、网络模型
Netty是典型的Reactor模型结构，关于Reactor的详尽阐释，可参考POSA2,这里不做概念性的解释。而应用Java NIO构建Reactor模式，Doug Lea（就是那位让人无限景仰的大爷）在“Scalable IO in Java”中给了很好的阐述。这里截取其PPT中经典的图例说明 Reactor模式的典型实现：
1、这是最简单的单Reactor单线程模型。Reactor线程是个多面手，负责多路分离套接字，Accept新连接，并分派请求到处理器链中。该模型 适用于处理器链中业务处理组件能快速完成的场景。不过，这种单线程模型不能充分利用多核资源，所以实际使用的不多。

2、相比上一种模型，该模型在处理器链部分采用了多线程（线程池），也是后端程序常用的模型。

3、 第三种模型比起第二种模型，是将Reactor分成两部分，mainReactor负责监听server socket，accept新连接，并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket，读写网 络数据，对业务处理功能，其扔给worker线程池完成。通常，subReactor个数上可与CPU个数等同。

说完Reacotr模型的三种形式，那么Netty是哪种呢？其实，我还有一种Reactor模型的变种没说，那就是去掉线程池的第三种形式的变种，这也 是Netty NIO的默认模式。在实现上，Netty中的Boss类充当mainReactor，NioWorker类充当subReactor（默认 NioWorker的个数是Runtime.getRuntime().availableProcessors()）。在处理新来的请求 时，NioWorker读完已收到的数据到ChannelBuffer中，之后触发ChannelPipeline中的ChannelHandler流。
Netty是事件驱动的，可以通过ChannelHandler链来控制执行流向。因为ChannelHandler链的执行过程是在 subReactor中同步的，所以如果业务处理handler耗时长，将严重影响可支持的并发数。这种模型适合于像Memcache这样的应用场景，但 对需要操作数据库或者和其他模块阻塞交互的系统就不是很合适。Netty的可扩展性非常好，而像ChannelHandler线程池化的需要，可以通过在 ChannelPipeline中添加Netty内置的ChannelHandler实现类–ExecutionHandler实现，对使用者来说只是 添加一行代码而已。对于ExecutionHandler需要的线程池模型，Netty提供了两种可 选：1） MemoryAwareThreadPoolExecutor 可控制Executor中待处理任务的上限（超过上限时，后续进来的任务将被阻 塞），并可控制单个Channel待处理任务的上限；2） OrderedMemoryAwareThreadPoolExecutor 是  MemoryAwareThreadPoolExecutor 的子类，它还可以保证同一Channel中处理的事件流的顺序性，这主要是控制事件在异步处 理模式下可能出现的错误的事件顺序，但它并不保证同一Channel中的事件都在一个线程中执行（通常也没必要）。一般来 说，OrderedMemoryAwareThreadPoolExecutor 是个很不错的选择，当然，如果有需要，也可以DIY一个。
3、 buffer
org.jboss.netty.buffer包的接口及类的结构图如下：

该包核心的接口是ChannelBuffer和ChannelBufferFactory,下面予以简要的介绍。
Netty使用ChannelBuffer来存储并操作读写的网络数据。ChannelBuffer除了提供和ByteBuffer类似的方法，还提供了 一些实用方法，具体可参考其API文档。ChannelBuffer的实现类有多个，这里列举其中主要的几个：
1）HeapChannelBuffer：这是Netty读网络数据时默认使用的ChannelBuffer，这里的Heap就是Java堆的意思，因为 读SocketChannel的数据是要经过ByteBuffer的，而ByteBuffer实际操作的就是个byte数组，所以 ChannelBuffer的内部就包含了一个byte数组，使得ByteBuffer和ChannelBuffer之间的转换是零拷贝方式。根据网络字 节续的不同，HeapChannelBuffer又分为BigEndianHeapChannelBuffer和 LittleEndianHeapChannelBuffer，默认使用的是BigEndianHeapChannelBuffer。Netty在读网络 数据时使用的就是HeapChannelBuffer，HeapChannelBuffer是个大小固定的buffer，为了不至于分配的Buffer的 大小不太合适，Netty在分配Buffer时会参考上次请求需要的大小。
2）DynamicChannelBuffer：相比于HeapChannelBuffer，DynamicChannelBuffer可动态自适应大 小。对于在DecodeHandler中的写数据操作，在数据大小未知的情况下，通常使用DynamicChannelBuffer。
3）ByteBufferBackedChannelBuffer：这是directBuffer，直接封装了ByteBuffer的 directBuffer。
对于读写网络数据的buffer，分配策略有两种：1）通常出于简单考虑，直接分配固定大小的buffer，缺点是，对一些应用来说这个大小限制有时是不 合理的，并且如果buffer的上限很大也会有内存上的浪费。2）针对固定大小的buffer缺点，就引入动态buffer，动态buffer之于固定 buffer相当于List之于Array。
buffer的寄存策略常见的也有两种（其实是我知道的就限于此）：1）在多线程（线程池） 模型下，每个线程维护自己的读写buffer，每次处理新的请求前清空buffer（或者在处理结束后清空），该请求的读写操作都需要在该线程中完成。 2）buffer和socket绑定而与线程无关。两种方法的目的都是为了重用buffer。
Netty对buffer的处理策略是：读 请求数据时，Netty首先读数据到新创建的固定大小的HeapChannelBuffer中，当HeapChannelBuffer满或者没有数据可读 时，调用handler来处理数据，这通常首先触发的是用户自定义的DecodeHandler，因为handler对象是和ChannelSocket 绑定的，所以在DecodeHandler里可以设置ChannelBuffer成员，当解析数据包发现数据不完整时就终止此次处理流程，等下次读事件触 发时接着上次的数据继续解析。就这个过程来说，和ChannelSocket绑定的DecodeHandler中的Buffer通常是动态的可重用 [...]]]></description>
			<content:encoded><![CDATA[<p>Netty是JBoss出品的高效的Java NIO开发框架，关于其使用，可参考我的另一篇文章 <a href="http://www.kafka0102.com/2010/06/netty%E4%BD%BF%E7%94%A8%E5%88%9D%E6%AD%A5/" target="_blank">netty使用初步</a>。本文将主要分析Netty实现方面的东西，由于精力有限，本人并没有对其源码做了极细致的研 究。如果下面的内容有错误或不严谨的地方，也请指正和谅解。对于Netty使用者来说，Netty提供了几个典型的example，并有详尽的API doc和guide doc，本文的一些内容及图示也来自于Netty的文档，特此致谢。<span id="more-813"></span></p>
<h2>1、总体结构</h2>
<p><a href="http://www.kafka0102.com/wp-content/uploads/2010/06/architecture.png"></a><a href="http://www.oschina.net/uploads/img/201007/19165036_4Wta.png" target="_blank"><img title="architecture" src="http://www.oschina.net/uploads/img/201007/19165036_4Wta.png" alt="" width="605" height="287" /></a></p>
<p>先放上一张漂亮的Netty总体结构图，下面的内容也主要围绕该图上的一些核心功能做分析，但对如Container Integration及Security Support等高级可选功能，本文不予分析。</p>
<h2>2、网络模型</h2>
<p>Netty是典型的Reactor模型结构，关于Reactor的详尽阐释，可参考POSA2,这里不做概念性的解释。而应用Java NIO构建Reactor模式，Doug Lea（就是那位让人无限景仰的大爷）在“<a href="http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf" target="_blank">Scalable IO in Java</a>”中给了很好的阐述。这里截取其PPT中经典的图例说明 Reactor模式的典型实现：</p>
<p>1、这是最简单的单Reactor单线程模型。Reactor线程是个多面手，负责多路分离套接字，Accept新连接，并分派请求到处理器链中。该模型 适用于处理器链中业务处理组件能快速完成的场景。不过，这种单线程模型不能充分利用多核资源，所以实际使用的不多。</p>
<p><a href="http://www.kafka0102.com/wp-content/uploads/2010/06/reactor1.png"></a><a href="http://www.oschina.net/uploads/img/201007/19165037_MPbe.png" target="_blank"><img title="reactor1" src="http://www.oschina.net/uploads/img/201007/19165037_MPbe.png" alt="" width="470" height="256" /></a></p>
<p>2、相比上一种模型，该模型在处理器链部分采用了多线程（线程池），也是后端程序常用的模型。</p>
<p><a href="http://www.kafka0102.com/wp-content/uploads/2010/06/reactor2.png"></a><a href="http://www.oschina.net/uploads/img/201007/19165038_MKoN.png" target="_blank"><img title="reactor2" src="http://www.oschina.net/uploads/img/201007/19165038_MKoN.png" alt="" width="582" height="395" /></a></p>
<p>3、 第三种模型比起第二种模型，是将Reactor分成两部分，mainReactor负责监听server socket，accept新连接，并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket，读写网 络数据，对业务处理功能，其扔给worker线程池完成。通常，subReactor个数上可与CPU个数等同。<br />
<a href="http://www.kafka0102.com/wp-content/uploads/2010/06/reactor3.png"></a><a href="http://www.oschina.net/uploads/img/201007/19165039_rx75.png" target="_blank"><img title="reactor3" src="http://www.oschina.net/uploads/img/201007/19165039_rx75.png" alt="" width="573" height="389" /></a></p>
<p>说完Reacotr模型的三种形式，那么Netty是哪种呢？其实，我还有一种Reactor模型的变种没说，那就是去掉线程池的第三种形式的变种，这也 是Netty NIO的默认模式。在实现上，Netty中的Boss类充当mainReactor，NioWorker类充当subReactor（默认 NioWorker的个数是Runtime.getRuntime().availableProcessors()）。在处理新来的请求 时，NioWorker读完已收到的数据到ChannelBuffer中，之后触发ChannelPipeline中的ChannelHandler流。</p>
<p>Netty是事件驱动的，可以通过ChannelHandler链来控制执行流向。因为ChannelHandler链的执行过程是在 subReactor中同步的，所以如果业务处理handler耗时长，将严重影响可支持的并发数。这种模型适合于像Memcache这样的应用场景，但 对需要操作数据库或者和其他模块阻塞交互的系统就不是很合适。Netty的可扩展性非常好，而像ChannelHandler线程池化的需要，可以通过在 ChannelPipeline中添加Netty内置的ChannelHandler实现类–ExecutionHandler实现，对使用者来说只是 添加一行代码而已。对于ExecutionHandler需要的线程池模型，Netty提供了两种可 选：1） MemoryAwareThreadPoolExecutor 可控制Executor中待处理任务的上限（超过上限时，后续进来的任务将被阻 塞），并可控制单个Channel待处理任务的上限；2） OrderedMemoryAwareThreadPoolExecutor 是  MemoryAwareThreadPoolExecutor 的子类，它还可以保证同一Channel中处理的事件流的顺序性，这主要是控制事件在异步处 理模式下可能出现的错误的事件顺序，但它并不保证同一Channel中的事件都在一个线程中执行（通常也没必要）。一般来 说，OrderedMemoryAwareThreadPoolExecutor 是个很不错的选择，当然，如果有需要，也可以DIY一个。</p>
<h2>3、 buffer</h2>
<p>org.jboss.netty.buffer包的接口及类的结构图如下：</p>
<p><a href="http://www.kafka0102.com/wp-content/uploads/2010/06/channelbuffer.png"></a><a href="http://www.oschina.net/uploads/img/201007/19165039_S4ZB.png" target="_blank"><img title="channelbuffer" src="http://www.oschina.net/uploads/img/201007/19165039_S4ZB.png" alt="" width="801" height="384" /></a></p>
<p>该包核心的接口是ChannelBuffer和ChannelBufferFactory,下面予以简要的介绍。</p>
<p>Netty使用ChannelBuffer来存储并操作读写的网络数据。ChannelBuffer除了提供和ByteBuffer类似的方法，还提供了 一些实用方法，具体可参考其API文档。ChannelBuffer的实现类有多个，这里列举其中主要的几个：</p>
<p>1）HeapChannelBuffer：这是Netty读网络数据时默认使用的ChannelBuffer，这里的Heap就是Java堆的意思，因为 读SocketChannel的数据是要经过ByteBuffer的，而ByteBuffer实际操作的就是个byte数组，所以 ChannelBuffer的内部就包含了一个byte数组，使得ByteBuffer和ChannelBuffer之间的转换是零拷贝方式。根据网络字 节续的不同，HeapChannelBuffer又分为BigEndianHeapChannelBuffer和 LittleEndianHeapChannelBuffer，默认使用的是BigEndianHeapChannelBuffer。Netty在读网络 数据时使用的就是HeapChannelBuffer，HeapChannelBuffer是个大小固定的buffer，为了不至于分配的Buffer的 大小不太合适，Netty在分配Buffer时会参考上次请求需要的大小。</p>
<p>2）DynamicChannelBuffer：相比于HeapChannelBuffer，DynamicChannelBuffer可动态自适应大 小。对于在DecodeHandler中的写数据操作，在数据大小未知的情况下，通常使用DynamicChannelBuffer。</p>
<p>3）ByteBufferBackedChannelBuffer：这是directBuffer，直接封装了ByteBuffer的 directBuffer。</p>
<p>对于读写网络数据的buffer，分配策略有两种：1）通常出于简单考虑，直接分配固定大小的buffer，缺点是，对一些应用来说这个大小限制有时是不 合理的，并且如果buffer的上限很大也会有内存上的浪费。2）针对固定大小的buffer缺点，就引入动态buffer，动态buffer之于固定 buffer相当于List之于Array。</p>
<p>buffer的寄存策略常见的也有两种（其实是我知道的就限于此）：1）在多线程（线程池） 模型下，每个线程维护自己的读写buffer，每次处理新的请求前清空buffer（或者在处理结束后清空），该请求的读写操作都需要在该线程中完成。 2）buffer和socket绑定而与线程无关。两种方法的目的都是为了重用buffer。</p>
<p>Netty对buffer的处理策略是：读 请求数据时，Netty首先读数据到新创建的固定大小的HeapChannelBuffer中，当HeapChannelBuffer满或者没有数据可读 时，调用handler来处理数据，这通常首先触发的是用户自定义的DecodeHandler，因为handler对象是和ChannelSocket 绑定的，所以在DecodeHandler里可以设置ChannelBuffer成员，当解析数据包发现数据不完整时就终止此次处理流程，等下次读事件触 发时接着上次的数据继续解析。就这个过程来说，和ChannelSocket绑定的DecodeHandler中的Buffer通常是动态的可重用 Buffer（DynamicChannelBuffer），而在NioWorker中读ChannelSocket中的数据的buffer是临时分配的 固定大小的HeapChannelBuffer，这个转换过程是有个字节拷贝行为的。</p>
<p>对ChannelBuffer的创建，Netty内部使用的是ChannelBufferFactory接口，具体的实现有 DirectChannelBufferFactory和HeapChannelBufferFactory。对于开发者创建 ChannelBuffer，可使用实用类ChannelBuffers中的工厂方法。</p>
<h2>4、Channel</h2>
<p>和Channel相关的接口及类结构图如下：</p>
<p><a href="http://www.kafka0102.com/wp-content/uploads/2010/06/Channel.png"></a><a href="http://www.oschina.net/uploads/img/201007/19165039_mvJ8.png" target="_blank"><img title="Channel" src="http://www.oschina.net/uploads/img/201007/19165039_mvJ8.png" alt="" width="539" height="253" /></a></p>
<p>从该结构图也可以看到，Channel主要提供的功能如下：</p>
<p>1）当前Channel的状态信息，比如是打开还是关闭等。<br />
2）通过ChannelConfig可以得到的Channel配置信息。<br />
3）Channel所支持的如read、write、bind、connect等IO操作。<br />
4）得到处理该Channel的ChannelPipeline，既而可以调用其做和请求相关的IO操作。</p>
<p>在Channel实现方面，以通常使用的nio socket来说，Netty中的NioServerSocketChannel和NioSocketChannel分别封装了java.nio中包含的 ServerSocketChannel和SocketChannel的功能。</p>
<h2>5、ChannelEvent</h2>
<p>如前所述，Netty是事件驱动的，其通过ChannelEvent来确定事件流的方向。一个ChannelEvent是依附于Channel的 ChannelPipeline来处理，并由ChannelPipeline调用ChannelHandler来做具体的处理。下面是和 ChannelEvent相关的接口及类图：</p>
<p><a href="http://www.kafka0102.com/wp-content/uploads/2010/06/ChannelEvent.png"></a><a href="http://www.oschina.net/uploads/img/201007/19165040_WGXd.png" target="_blank"><img title="ChannelEvent" src="http://www.oschina.net/uploads/img/201007/19165040_WGXd.png" alt="" width="416" height="437" /></a></p>
<p>对于使用者来说，在ChannelHandler实现类中会使用继承于ChannelEvent的MessageEvent，调用其 getMessage()方法来获得读到的ChannelBuffer或被转化的对象。</p>
<h2>6、ChannelPipeline</h2>
<p>Netty 在事件处理上，是通过ChannelPipeline来控制事件流，通过调用注册其上的一系列ChannelHandler来处理事件，这也是典型的拦截 器模式。下面是和ChannelPipeline相关的接口及类图：</p>
<p><a href="http://www.kafka0102.com/wp-content/uploads/2010/06/ChannelPipeline.png"></a><a href="http://www.oschina.net/uploads/img/201007/19165040_5jsb.png" target="_blank"><img title="ChannelPipeline" src="http://www.oschina.net/uploads/img/201007/19165040_5jsb.png" alt="" width="559" height="237" /></a></p>
<p>事件流有两种，upstream事件和downstream事件。在ChannelPipeline中，其可被注册的ChannelHandler既可以 是 ChannelUpstreamHandler 也可以是ChannelDownstreamHandler ，但事件在ChannelPipeline传递过程中只会调用匹配流的ChannelHandler。在事件流的过滤器链 中，ChannelUpstreamHandler或ChannelDownstreamHandler既可以终止流程，也可以通过调用 ChannelHandlerContext.sendUpstream(ChannelEvent)或 ChannelHandlerContext.sendDownstream(ChannelEvent)将事件传递下去。下面是事件流处理的图示：</p>
<p><a href="http://www.kafka0102.com/wp-content/uploads/2010/06/ChannelPipeline.jpg"></a><a href="http://www.oschina.net/uploads/img/201007/19165041_z42g.jpg" target="_blank"><img title="ChannelPipeline" src="http://www.oschina.net/uploads/img/201007/19165041_z42g.jpg" alt="" width="522" height="622" /></a></p>
<p>从上图可见，upstream event是被Upstream Handler们自底向上逐个处理，downstream event是被Downstream Handler们自顶向下逐个处理，这里的上下关系就是向ChannelPipeline里添加Handler的先后顺序关系。简单的理 解，upstream event是处理来自外部的请求的过程，而downstream event是处理向外发送请求的过程。</p>
<p>服务端处 理请求的过程通常就是解码请求、业务逻辑处理、编码响应，构建的ChannelPipeline也就类似下面的代码片断：</p>
<p>ChannelPipeline pipeline = Channels.pipeline();<br />
pipeline.addLast(&#8220;decoder&#8221;, new MyProtocolDecoder());<br />
pipeline.addLast(&#8220;encoder&#8221;, new MyProtocolEncoder());</p>
<p>pipeline.addLast(&#8220;handler&#8221;, new MyBusinessLogicHandler());</p>
<p>其中，MyProtocolDecoder是ChannelUpstreamHandler类型，MyProtocolEncoder是 ChannelDownstreamHandler类型，MyBusinessLogicHandler既可以是 ChannelUpstreamHandler类型，也可兼ChannelDownstreamHandler类型，视其是服务端程序还是客户端程序以及 应用需要而定。</p>
<p>补充一点，Netty对抽象和实现做了很好的解耦。像org.jboss.netty.channel.socket包， 定义了一些和socket处理相关的接口，而org.jboss.netty.channel.socket.nio、 org.jboss.netty.channel.socket.oio等包，则是和协议相关的实现。</p>
<h2>7、codec framework</h2>
<p>对于请求协议的编码解码，当然是可以按照协议格式自己操作ChannelBuffer中的字节数据。另一方面，Netty也做了几个很实用的codec helper，这里给出简单的介绍。</p>
<p>1）FrameDecoder：FrameDecoder内部维护了一个 DynamicChannelBuffer成员来存储接收到的数据，它就像个抽象模板，把整个解码过程模板写好了，其子类只需实现decode函数即可。 FrameDecoder的直接实现类有两个：（1）DelimiterBasedFrameDecoder是基于分割符 （比如\r\n）的解码器，可在构造函数中指定分割符。（2）LengthFieldBasedFrameDecoder是基于长度字段的解码器。如果协 议 格式类似“内容长度”+内容、“固定头”+“内容长度”+动态内容这样的格式，就可以使用该解码器，其使用方法在API DOC上详尽的解释。<br />
2）ReplayingDecoder： 它是FrameDecoder的一个变种子类，它相对于FrameDecoder是非阻塞解码。也就是说，使用 FrameDecoder时需要考虑到读到的数据有可能是不完整的，而使用ReplayingDecoder就可以假定读到了全部的数据。<br />
3）ObjectEncoder 和ObjectDecoder：编码解码序列化的Java对象。<br />
4）HttpRequestEncoder和 HttpRequestDecoder：http协议处理。</p>
<p>下面来看使用FrameDecoder和ReplayingDecoder的两个例子：</p>
<p>public class IntegerHeaderFrameDecoder extends FrameDecoder {<br />
    protected Object decode(ChannelHandlerContext ctx, Channel channel,<br />
            ChannelBuffer buf) throws Exception {<br />
        if (buf.readableBytes() &amp;lt; 4) {<br />
            return null;<br />
        }<br />
        buf.markReaderIndex();<br />
        int length = buf.readInt();<br />
        if (buf.readableBytes() &amp;lt; length) {<br />
            buf.resetReaderIndex();<br />
            return null;<br />
        }<br />
        return buf.readBytes(length);<br />
    }<br />
}</p>
<p>而使用ReplayingDecoder的解码片断类似下面的，相对来说会简化很多。</p>
<p>public class IntegerHeaderFrameDecoder2 extends ReplayingDecoder {<br />
    protected Object decode(ChannelHandlerContext ctx, Channel channel,<br />
            ChannelBuffer buf, VoidEnum state) throws Exception {<br />
        return buf.readBytes(buf.readInt());<br />
    }<br />
}</p>
<p>就实现来说，当在ReplayingDecoder子类的decode函数中调用ChannelBuffer读数据时，如果读失败，那么 ReplayingDecoder就会catch住其抛出的Error，然后ReplayingDecoder接手控制权，等待下一次读到后续的数据后继 续decode。</p>
<h2>8、小结</h2>
<p>尽管该文行至此处将止，但该文显然没有将Netty实现原理深入浅出的说全说透。当我打算写这篇文章时，也是一边看Netty的代码，一边总结些可写的东 西，但前后断断续续，到最后都没了多少兴致。我还是爱做一些源码分析的事情，但精力终究有限，并且倘不能把源码分析的结果有条理的托出来，不能产生有意义 的心得，这分析也没什么价值和趣味。而就分析Netty代码的感受来说，Netty的代码很漂亮，结构上层次上很清晰，不过这种面向接口及抽象层次对代码 跟踪很是个问题，因为跟踪代码经常遇到接口和抽象类，只能借助于工厂类和API DOC，反复对照接口和实现类的对应关系。就像几乎任何优秀的Java开源项目都会用上一系列优秀的设计模式，也完全可以从模式这一点单独拿出一篇分析文 章来，尽管我目前没有这样的想法。而在此文完成之后，我也没什么兴趣再看Netty的代码了。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/813/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Netty 3.1 中文用户手册</title>
		<link>http://www.javagg.com/archives/811</link>
		<comments>http://www.javagg.com/archives/811#comments</comments>
		<pubDate>Wed, 14 Jul 2010 10:55:22 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[java]]></category>
		<category><![CDATA[netty]]></category>
		<category><![CDATA[nio]]></category>

		<guid isPermaLink="false">http://www.javagg.com/?p=811</guid>
		<description><![CDATA[本指南对Netty 进行了介绍并指出其意义所在。
1. 问题
现在，我们使用适合一般用途的应用或组件来和彼此通信。例如，我们常常使用一个HTTP客户端从远程服务器获取信息或者通过web services进行远程方法的调用。
然而，一个适合普通目的的协议或其实现并不具备其规模上的扩展性。例如，我们无法使用一个普通的HTTP服务器进行大型文件，电邮信息的交互，或者处理金融信息和多人游戏数据那种要求准实时消息传递的应用场景。因此，这些都要求使用一个适用于特殊目的并经过高度优化的协议实现。例如，你可能想要实现一个对基于AJAX的聊天应用，媒体流或大文件传输进行过特殊优化的HTTP服务器。你甚至可能想去设计和实现一个全新的，特定于你的需求的通信协议。
另一种无法避免的场景是你可能不得不使用一种专有的协议和原有系统交互。在这种情况下，你需要考虑的是如何能够快速的开发出这个协议的实现并且同时还没有牺牲最终应用的性能和稳定性。
2. 方案
Netty 是一个异步的，事件驱动的网络编程框架和工具，使用Netty 可以快速开发出可维护的，高性能、高扩展能力的协议服务及其客户端应用。
也就是说，Netty 是一个基于NIO的客户，服务器端编程框架，使用Netty 可以确保你快速和简单的开发出一个网络应用，例如实现了某种协议的客户，服务端应用。Netty相当简化和流线化了网络应用的编程开发过程，例如，TCP和UDP的socket服务开发。
“快速”和“简单”并不意味着会让你的最终应用产生维护性或性能上的问题。Netty 是一个吸收了多种协议的实现经验，这些协议包括FTP,SMPT,HTTP，各种二进制，文本协议，并经过相当精心设计的项目，最终，Netty 成功的找到了一种方式，在保证易于开发的同时还保证了其应用的性能，稳定性和伸缩性。
一些用户可能找到了某些同样声称具有这些特性的编程框架，因此你们可能想问Netty 又有什么不一样的地方。这个问题的答案是Netty 项目的设计哲学。从创立之初，无论是在API还是在其实现上Netty 都致力于为你提供最为舒适的使用体验。虽然这并不是显而易见的，但你终将会认识到这种设计哲学将令你在阅读本指南和使用Netty 时变得更加得轻松和容易。
第一章. 开始
这一章节将围绕Netty的核心结构展开，同时通过一些简单的例子可以让你更快的了解Netty的使用。当你读完本章，你将有能力使用Netty完成客户端和服务端的开发。
如果你更喜欢自上而下式的学习方式，你可以首先完成 第二章：架构总览 的学习，然后再回到这里。
1.1. 开始之前
运行本章示例程序的两个最低要求是：最新版本的Netty程序以及JDK 1.5或更高版本。最新版本的Netty程序可在项目下载页 下载。下载正确版本的JDK，请到你偏好的JDK站点下载。
这就已经足够了吗？实际上你会发现，这两个条件已经足够你完成任何协议的开发了。如果不是这样，请联系Netty项目社区 ，让我们知道还缺少了什么。
最终但不是至少，当你想了解本章所介绍的类的更多信息时请参考API手册。为方便你的使用，这篇文档中所有的类名均连接至在线API手册。此外，如果本篇文档中有任何错误信息，无论是语法错误，还是打印排版错误或者你有更好的建议，请不要顾虑，立即联系Netty项目社区 。
1.2. 抛弃协议服务
在这个世界上最简化的协议不是“Hello,world!”而是抛弃协议 。这是一种丢弃接收到的任何数据并不做任何回应的协议。
实现抛弃协议（DISCARD protocol），你仅需要忽略接受到的任何数据即可。让我们直接从处理器（handler）实现开始，这个处理器处理Netty的所有I/O事件。
package org.jboss.netty.example.discard;  
@ChannelPipelineCoverage(&#8220;all&#8221;)1 
public class DiscardServerHandler extends SimpleChannelHandler {2 
    @Override 
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {3 
    [...]]]></description>
			<content:encoded><![CDATA[<p>本指南对Netty 进行了介绍并指出其意义所在。</p>
<p>1. 问题</p>
<p>现在，我们使用适合一般用途的应用或组件来和彼此通信。例如，我们常常使用一个HTTP客户端从远程服务器获取信息或者通过web services进行远程方法的调用。</p>
<p>然而，一个适合普通目的的协议或其实现并不具备其规模上的扩展性。例如，我们无法使用一个普通的HTTP服务器进行大型文件，电邮信息的交互，或者处理金融信息和多人游戏数据那种要求准实时消息传递的应用场景。因此，这些都要求使用一个适用于特殊目的并经过高度优化的协议实现。例如，你可能想要实现一个对基于AJAX的聊天应用，媒体流或大文件传输进行过特殊优化的HTTP服务器。你甚至可能想去设计和实现一个全新的，特定于你的需求的通信协议。<span id="more-811"></span></p>
<p>另一种无法避免的场景是你可能不得不使用一种专有的协议和原有系统交互。在这种情况下，你需要考虑的是如何能够快速的开发出这个协议的实现并且同时还没有牺牲最终应用的性能和稳定性。</p>
<p>2. 方案</p>
<p>Netty 是一个异步的，事件驱动的网络编程框架和工具，使用Netty 可以快速开发出可维护的，高性能、高扩展能力的协议服务及其客户端应用。</p>
<p>也就是说，Netty 是一个基于NIO的客户，服务器端编程框架，使用Netty 可以确保你快速和简单的开发出一个网络应用，例如实现了某种协议的客户，服务端应用。Netty相当简化和流线化了网络应用的编程开发过程，例如，TCP和UDP的socket服务开发。</p>
<p>“快速”和“简单”并不意味着会让你的最终应用产生维护性或性能上的问题。Netty 是一个吸收了多种协议的实现经验，这些协议包括FTP,SMPT,HTTP，各种二进制，文本协议，并经过相当精心设计的项目，最终，Netty 成功的找到了一种方式，在保证易于开发的同时还保证了其应用的性能，稳定性和伸缩性。</p>
<p>一些用户可能找到了某些同样声称具有这些特性的编程框架，因此你们可能想问Netty 又有什么不一样的地方。这个问题的答案是Netty 项目的设计哲学。从创立之初，无论是在API还是在其实现上Netty 都致力于为你提供最为舒适的使用体验。虽然这并不是显而易见的，但你终将会认识到这种设计哲学将令你在阅读本指南和使用Netty 时变得更加得轻松和容易。</p>
<p>第一章. 开始</p>
<p>这一章节将围绕Netty的核心结构展开，同时通过一些简单的例子可以让你更快的了解Netty的使用。当你读完本章，你将有能力使用Netty完成客户端和服务端的开发。</p>
<p>如果你更喜欢自上而下式的学习方式，你可以首先完成 第二章：架构总览 的学习，然后再回到这里。</p>
<p>1.1. 开始之前</p>
<p>运行本章示例程序的两个最低要求是：最新版本的Netty程序以及JDK 1.5或更高版本。最新版本的Netty程序可在项目下载页 下载。下载正确版本的JDK，请到你偏好的JDK站点下载。</p>
<p>这就已经足够了吗？实际上你会发现，这两个条件已经足够你完成任何协议的开发了。如果不是这样，请联系Netty项目社区 ，让我们知道还缺少了什么。</p>
<p>最终但不是至少，当你想了解本章所介绍的类的更多信息时请参考API手册。为方便你的使用，这篇文档中所有的类名均连接至在线API手册。此外，如果本篇文档中有任何错误信息，无论是语法错误，还是打印排版错误或者你有更好的建议，请不要顾虑，立即联系Netty项目社区 。</p>
<p>1.2. 抛弃协议服务</p>
<p>在这个世界上最简化的协议不是“Hello,world!”而是抛弃协议 。这是一种丢弃接收到的任何数据并不做任何回应的协议。</p>
<p>实现抛弃协议（DISCARD protocol），你仅需要忽略接受到的任何数据即可。让我们直接从处理器（handler）实现开始，这个处理器处理Netty的所有I/O事件。</p>
<p>package org.jboss.netty.example.discard;  </p>
<p>@ChannelPipelineCoverage(&#8220;all&#8221;)1 </p>
<p>public class DiscardServerHandler extends SimpleChannelHandler {2 </p>
<p>    @Override </p>
<p>    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {3 </p>
<p>    }  </p>
<p> @Override </p>
<p> public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {4 </p>
<p>     e.getCause().printStackTrace();  </p>
<p>     Channel ch = e.getChannel();  </p>
<p>     ch.close();  </p>
<p> }  </p>
<p>    @Override </p>
<p>    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {4 </p>
<p>        e.getCause().printStackTrace();  </p>
<p>        Channel ch = e.getChannel();  </p>
<p>        ch.close();  </p>
<p>    }  </p>
<p>} </p>
<p>     代码说明</p>
<p>1)ChannelPipelineCoverage注解了一种处理器类型，这个注解标示了一个处理器是否可被多个Channel通道共享（同时关联着ChannelPipeline）。DiscardServerHandler没有处理任何有状态的信息，因此这里的注解是“all”。</p>
<p>2)DiscardServerHandler继承了SimpleChannelHandler，这也是一个ChannelHandler 的实现。SimpleChannelHandler提供了多种你可以重写的事件处理方法。目前直接继承SimpleChannelHandler已经足够了，并不需要你完成一个自己的处理器接口。</p>
<p>3)我们这里重写了messageReceived事件处理方法。这个方法由一个接收了客户端传送数据的MessageEvent事件调用。在这个例子中，我们忽略接收到的任何数据，并以此来实现一个抛弃协议（DISCARD protocol）。</p>
<p>4)exceptionCaught 事件处理方法由一个ExceptionEvent异常事件调用，这个异常事件起因于Netty的I/O异常或一个处理器实现的内部异常。多数情况下，捕捉到的异常应当被记录下来，并在这个方法中关闭这个channel通道。当然处理这种异常情况的方法实现可能因你的实际需求而有所不同，例如，在关闭这个连接之前你可能会发送一个包含了错误码的响应消息。</p>
<p>目前进展不错，我们已经完成了抛弃协议服务器的一半开发工作。下面要做的是完成一个可以启动这个包含DiscardServerHandler处理器服务的主方法。</p>
<p>package org.jboss.netty.example.discard;  </p>
<p>import java.net.InetSocketAddress;  </p>
<p>import java.util.concurrent.Executors;  </p>
<p>public class DiscardServer {  </p>
<p>    public static void main(String[] args) throws Exception {  </p>
<p>        ChannelFactory factory =  </p>
<p>            new NioServerSocketChannelFactory (  </p>
<p>                    Executors.newCachedThreadPool(),  </p>
<p>                    Executors.newCachedThreadPool());  </p>
<p>        ServerBootstrap bootstrap = new ServerBootstrap (factory);  </p>
<p>        DiscardServerHandler handler = new DiscardServerHandler();  </p>
<p>        ChannelPipeline pipeline = bootstrap.getPipeline();  </p>
<p>        pipeline.addLast(&#8220;handler&#8221;, handler);  </p>
<p>        bootstrap.setOption(&#8220;child.tcpNoDelay&#8221;, true);  </p>
<p>        bootstrap.setOption(&#8220;child.keepAlive&#8221;, true);  </p>
<p>        bootstrap.bind(new InetSocketAddress(8080));  </p>
<p>    }  </p>
<p>} </p>
<p>     代码说明</p>
<p>1)ChannelFactory 是一个创建和管理Channel通道及其相关资源的工厂接口，它处理所有的I/O请求并产生相应的I/O ChannelEvent通道事件。Netty 提供了多种 ChannelFactory 实现。这里我们需要实现一个服务端的例子，因此我们使用NioServerSocketChannelFactory实现。另一件需要注意的事情是这个工厂并自己不负责创建I/O线程。你应当在其构造器中指定该工厂使用的线程池，这样做的好处是你获得了更高的控制力来管理你的应用环境中使用的线程，例如一个包含了安全管理的应用服务。</p>
<p>2)ServerBootstrap 是一个设置服务的帮助类。你甚至可以在这个服务中直接设置一个Channel通道。然而请注意，这是一个繁琐的过程，大多数情况下并不需要这样做。</p>
<p>3)这里，我们将DiscardServerHandler处理器添加至默认的ChannelPipeline通道。任何时候当服务器接收到一个新的连接，一个新的ChannelPipeline管道对象将被创建，并且所有在这里添加的ChannelHandler对象将被添加至这个新的ChannelPipeline管道对象。这很像是一种浅拷贝操作（a shallow-copy operation）；所有的Channel通道以及其对应的ChannelPipeline实例将分享相同的DiscardServerHandler实例。</p>
<p>4)你也可以设置我们在这里指定的这个通道实现的配置参数。我们正在写的是一个TCP/IP服务，因此我们运行设定一些socket选项，例如tcpNoDelay和keepAlive。请注意我们在配置选项里添加的&#8221;child.&#8221;前缀。这意味着这个配置项仅适用于我们接收到的通道实例，而不是ServerSocketChannel实例。因此，你可以这样给一个ServerSocketChannel设定参数：<br />
bootstrap.setOption(&#8220;reuseAddress&#8221;, true);</p>
<p>5)我们继续。剩下要做的是绑定这个服务使用的端口并且启动这个服务。这里，我们绑定本机所有网卡（NICs,network interface cards）上的8080端口。当然，你现在也可以对应不同的绑定地址多次调用绑定操作。</p>
<p>大功告成！现在你已经完成你的第一个基于Netty的服务端程序。</p>
<p>1.3. 查看接收到的数据</p>
<p>现在你已经完成了你的第一个服务端程序，我们需要测试它是否可以真正的工作。最简单的方法是使用telnet 命令。例如，你可以在命令行中输入“telnet localhost 8080 ”或其他类型参数。</p>
<p>然而，我们可以认为服务器在正常工作吗？由于这是一个丢球协议服务，所以实际上我们无法真正的知道。你最终将收不到任何回应。为了证明它在真正的工作，让我们修改代码打印其接收到的数据。<br />
我们已经知道当完成数据的接收后将产生MessageEvent消息事件，并且也会触发messageReceived处理方法。所以让我在DiscardServerHandler处理器的messageReceived方法内增加一些代码。</p>
<p>@Override </p>
<p>public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {  </p>
<p>    ChannelBuffer  buf = (ChannelBuffer) e.getMessage();  </p>
<p>    while(buf.readable()) {  </p>
<p>        System.out.println((char) buf.readByte());  </p>
<p>    }  </p>
<p>} </p>
<p>     代码说明</p>
<p>1) 基本上我们可以假定在socket的传输中消息类型总是ChannelBuffer。ChannelBuffer是Netty的一个基本数据结构，这个数据结构存储了一个字节序列。ChannelBuffer类似于NIO的ByteBuffer，但是前者却更加的灵活和易于使用。例如，Netty允许你创建一个由多个ChannelBuffer构建的复合ChannelBuffer类型，这样就可以减少不必要的内存拷贝次数。</p>
<p>2) 虽然ChannelBuffer有些类似于NIO的ByteBuffer，但强烈建议你参考Netty的API手册。学会如何正确的使用ChannelBuffer是无障碍使用Netty的关键一步。</p>
<p>如果你再次运行telnet命令，你将会看到你所接收到的数据。<br />
抛弃协议服务的所有源代码均存放在在分发版的org.jboss.netty.example.discard包下。</p>
<p>1.4. 响应协议服务</p>
<p>目前，我们虽然使用了数据，但最终却未作任何回应。然而一般情况下，一个服务都需要回应一个请求。让我们实现ECHO协议 来学习如何完成一个客户请求的回应消息，ECHO协议规定要返回任何接收到的数据。</p>
<p>与我们上一节实现的抛弃协议服务唯一不同的地方是，这里需要返回所有的接收数据而不是仅仅打印在控制台之上。因此我们再次修改messageReceived方法就足够了。</p>
<p>@Override </p>
<p>public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {  </p>
<p>    Channel  ch = e.getChannel();  </p>
<p>    ch.write(e.getMessage());  </p>
<p>} </p>
<p>代码说明</p>
<p>1) 一个ChannelEvent通道事件对象自身存有一个和其关联的Channel对象引用。这个返回的Channel通道对象代表了这个接收 MessageEvent消息事件的连接（connection）。因此，我们可以通过调用这个Channel通道对象的write方法向远程节点写入返回数据。</p>
<p>现在如果你再次运行telnet命令，你将会看到服务器返回的你所发送的任何数据。</p>
<p>相应服务的所有源代码存放在分发版的org.jboss.netty.example.echo包下。</p>
<p>1.5. 时间协议服务</p>
<p>这一节需要实现的协议是TIME协议 。这是一个与先前所介绍的不同的例子。这个例子里，服务端返回一个32位的整数消息，我们不接受请求中包含的任何数据并且当消息返回完毕后立即关闭连接。通过这个例子你将学会如何构建和发送消息，以及当完成处理后如何主动关闭连接。</p>
<p>因为我们会忽略接收到的任何数据而只是返回消息，这应当在建立连接后就立即开始。因此这次我们不再使用messageReceived方法，取而代之的是使用channelConnected方法。下面是具体的实现：</p>
<p>package org.jboss.netty.example.time;  </p>
<p>@ChannelPipelineCoverage(&#8220;all&#8221;)  </p>
<p>public class TimeServerHandler extends SimpleChannelHandler {  </p>
<p>    @Override </p>
<p>    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {  </p>
<p>        Channel ch = e.getChannel();  </p>
<p>        ChannelBuffer time = ChannelBuffers.buffer(4);  </p>
<p>        time.writeInt(System.currentTimeMillis() / 1000);  </p>
<p>        ChannelFuture f = ch.write(time);  </p>
<p>        f.addListener(new ChannelFutureListener() {  </p>
<p>            public void operationComplete(ChannelFuture future) {  </p>
<p>                Channel ch = future.getChannel();  </p>
<p>                ch.close();  </p>
<p>            }  </p>
<p>        });  </p>
<p>    }  </p>
<p>    @Override </p>
<p>    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {  </p>
<p>        e.getCause().printStackTrace();  </p>
<p>        e.getChannel().close();  </p>
<p>    }  </p>
<p>} </p>
<p>代码说明</p>
<p>1) 正如我们解释过的，channelConnected方法将在一个连接建立后立即触发。因此让我们在这个方法里完成一个代表当前时间（秒）的32位整数消息的构建工作。</p>
<p>2) 为了发送一个消息，我们需要分配一个包含了这个消息的buffer缓冲。因为我们将要写入一个32位的整数，因此我们需要一个4字节的 ChannelBuffer。ChannelBuffers是一个可以创建buffer缓冲的帮助类。除了这个buffer方法，ChannelBuffers还提供了很多和ChannelBuffer相关的实用方法。更多信息请参考API手册。</p>
<p>另外，一个很不错的方法是使用静态的导入方式：<br />
import static org.jboss.netty.buffer.ChannelBuffers.*;<br />
&#8230;<br />
ChannelBuffer dynamicBuf = dynamicBuffer(256);<br />
ChannelBuffer ordinaryBuf = buffer(1024);</p>
<p>3) 像通常一样，我们需要自己构造消息。</p>
<p>但是打住，flip在哪？过去我们在使用NIO发送消息时不是常常需要调用 ByteBuffer.flip()方法吗？实际上ChannelBuffer之所以不需要这个方法是因为 ChannelBuffer有两个指针；一个对应读操作，一个对应写操作。当你向一个 ChannelBuffer写入数据的时候写指针的索引值便会增加，但与此同时读指针的索引值不会有任何变化。读写指针的索引值分别代表了这个消息的开始、结束位置。</p>
<p>与之相应的是，NIO的buffer缓冲没有为我们提供如此简洁的一种方法，除非你调用它的flip方法。因此，当你忘记调用flip方法而引起发送错误时，你便会陷入困境。这样的错误不会再Netty中发生，因为我们对应不同的操作类型有不同的指针。你会发现就像你已习惯的这样过程变得更加容易—一种没有flippling的体验！</p>
<p>另一点需要注意的是这个写方法返回了一个ChannelFuture对象。一个ChannelFuture 对象代表了一个尚未发生的I/O操作。这意味着，任何已请求的操作都可能是没有被立即执行的，因为在Netty内部所有的操作都是异步的。例如，下面的代码可能会关闭一 个连接，这个操作甚至会发生在消息发送之前：</p>
<p>Channel ch = &#8230;;<br />
ch.write(message);<br />
ch.close();</p>
<p>因此，你需要这个write方法返回的ChannelFuture对象，close方法需要等待写操作异步完成之后的ChannelFuture通知/监听触发。需要注意的是，关闭方法仍旧不是立即关闭一个连接，它同样也是返回了一个ChannelFuture对象。</p>
<p>4) 在写操作完成之后我们又如何得到通知？这个只需要简单的为这个返回的ChannelFuture对象增加一个ChannelFutureListener 即可。在这里我们创建了一个匿名ChannelFutureListener对象，在这个ChannelFutureListener对象内部我们处理了异步操作完成之后的关闭操作。</p>
<p>另外，你也可以通过使用一个预定义的监听类来简化代码。<br />
f.addListener(ChannelFutureListener.CLOSE);</p>
<p>1.6. 时间协议服务客户端</p>
<p>不同于DISCARD和ECHO协议服务，我们需要一个时间协议服务的客户端，因为人们无法直接将一个32位的二进制数据转换一个日历时间。在这一节我们将学习如何确保服务器端工作正常，以及如何使用Netty完成客户端的开发。</p>
<p>使用Netty开发服务器端和客户端代码最大的不同是要求使用不同的Bootstrap及ChannelFactory。请参照以下的代码：</p>
<p>package org.jboss.netty.example.time;  </p>
<p>import java.net.InetSocketAddress;  </p>
<p>import java.util.concurrent.Executors;  </p>
<p>public class TimeClient {  </p>
<p>    public static void main(String[] args) throws Exception {  </p>
<p>        String host = args[0];  </p>
<p>        int port = Integer.parseInt(args[1]);  </p>
<p>        ChannelFactory factory =  </p>
<p>            new NioClientSocketChannelFactory (  </p>
<p>                    Executors.newCachedThreadPool(),  </p>
<p>                    Executors.newCachedThreadPool());  </p>
<p>        ClientBootstrap bootstrap = new ClientBootstrap (factory);  </p>
<p>        TimeClientHandler handler = new TimeClientHandler();  </p>
<p>        bootstrap.getPipeline().addLast(&#8220;handler&#8221;, handler);  </p>
<p>        bootstrap.setOption(&#8220;tcpNoDelay&#8221; , true);  </p>
<p>        bootstrap.setOption(&#8220;keepAlive&#8221;, true);  </p>
<p>        bootstrap.connect (new InetSocketAddress(host, port));  </p>
<p>    }  </p>
<p>} </p>
<p>代码说明</p>
<p>1) 使用NioClientSocketChannelFactory而不是NioServerSocketChannelFactory来创建客户端的Channel通道对象。</p>
<p>2) 客户端的ClientBootstrap对应ServerBootstrap。</p>
<p>3) 请注意，这里不存在使用“child.”前缀的配置项，客户端的SocketChannel实例不存在父级Channel对象。</p>
<p>4) 我们应当调用connect连接方法，而不是之前的bind绑定方法。</p>
<p>正如你所看到的，这与服务端的启动过程是完全不一样的。ChannelHandler又该如何实现呢？它应当负责接收一个32位的整数，将其转换为可读的格式后，打印输出时间，并关闭这个连接。</p>
<p> package org.jboss.netty.example.time;   </p>
<p> import java.util.Date;   </p>
<p> @ChannelPipelineCoverage(&#8220;all&#8221;)   </p>
<p> public class TimeClientHandler extends SimpleChannelHandler {   </p>
<p>     @Override  </p>
<p>     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {   </p>
<p>         ChannelBuffer buf = (ChannelBuffer) e.getMessage();   </p>
<p>         long currentTimeMillis = buf.readInt() * 1000L;   </p>
<p>         System.out.println(new Date(currentTimeMillis));   </p>
<p>         e.getChannel().close();   </p>
<p>     }   </p>
<p>     @Override  </p>
<p>     public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {   </p>
<p>         e.getCause().printStackTrace();   </p>
<p>         e.getChannel().close();   </p>
<p>     }   </p>
<p>这看起来很是简单，与服务端的实现也并未有什么不同。然而，这个处理器却时常会因为抛出IndexOutOfBoundsException异常而拒绝工作。我们将在下一节讨论这个问题产生的原因。</p>
<p>1.7. 流数据的传输处理</p>
<p>1.7.1. Socket Buffer的缺陷</p>
<p>对于例如TCP/IP这种基于流的传输协议实现，接收到的数据会被存储在socket的接受缓冲区内。不幸的是，这种基于流的传输缓冲区并不是一个包队列，而是一个字节队列。这意味着，即使你以两个数据包的形式发送了两条消息，操作系统却不会把它们看成是两条消息，而仅仅是一个批次的字节序列。因此，在这种情况下我们就无法保证收到的数据恰好就是远程节点所发送的数据。例如，让我们假设一个操作系统的TCP/IP堆栈收到了三个数据包：</p>
<p>+&#8212;&#8211;+&#8212;&#8211;+&#8212;&#8211;+<br />
| ABC | DEF | GHI |<br />
+&#8212;&#8211;+&#8212;&#8211;+&#8212;&#8211;+</p>
<p>由于这种流传输协议的普遍性质，在你的应用中有较高的可能会把这些数据读取为另外一种形式：</p>
<p>+&#8212;-+&#8212;&#8212;-+&#8212;+&#8212;+<br />
| AB | CDEFG | H | I |<br />
+&#8212;-+&#8212;&#8212;-+&#8212;+&#8212;+</p>
<p>因此对于数据的接收方，不管是服务端还是客户端，应当重构这些接收到的数据，让其变成一种可让你的应用逻辑易于理解的更有意义的数据结构。在上面所述的这个例子中，接收到的数据应当重构为下面的形式：</p>
<p>+&#8212;&#8211;+&#8212;&#8211;+&#8212;&#8211;+<br />
| ABC | DEF | GHI |<br />
+&#8212;&#8211;+&#8212;&#8211;+&#8212;&#8211;+</p>
<p>1.7.2. 第一种方案</p>
<p>现在让我们回到时间协议服务客户端的例子中。我们在这里遇到了同样的问题。一个32位的整数是一个非常小的数据量，因此它常常不会被切分在不同的数据段内。然而，问题是它确实可以被切分在不同的数据段内，并且这种可能性随着流量的增加而提高。</p>
<p>最简单的方案是在程序内部创建一个可准确接收4字节数据的累积性缓冲。下面的代码是修复了这个问题后的TimeClientHandler实现。</p>
<p>package org.jboss.netty.example.time;  </p>
<p>import static org.jboss.netty.buffer.ChannelBuffers.*;  </p>
<p>import java.util.Date;  </p>
<p>@ChannelPipelineCoverage(&#8220;one&#8221;)  </p>
<p>public class TimeClientHandler extends SimpleChannelHandler {  </p>
<p>    private final ChannelBuffer buf = dynamicBuffer();  </p>
<p>    @Override </p>
<p>    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {  </p>
<p>        ChannelBuffer m = (ChannelBuffer) e.getMessage();  </p>
<p>        buf.writeBytes(m);  </p>
<p>        if (buf.readableBytes() >= 4) {  </p>
<p>            long currentTimeMillis = buf.readInt() * 1000L;  </p>
<p>            System.out.println(new Date(currentTimeMillis));  </p>
<p>            e.getChannel().close();  </p>
<p>        }  </p>
<p>    }  </p>
<p>    @Override </p>
<p>    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {  </p>
<p>        e.getCause().printStackTrace();  </p>
<p>        e.getChannel().close();  </p>
<p>    }  </p>
<p>} </p>
<p>     代码说明 </p>
<p>1) 这一次我们使用“one”做为ChannelPipelineCoverage的注解值。这是由于这个修改后的TimeClientHandler不在不在内部保持一个buffer缓冲，因此这个TimeClientHandler实例不可以再被多个Channel通道或ChannelPipeline共享。否则这个内部的buffer缓冲将无法缓冲正确的数据内容。</p>
<p>2) 动态的buffer缓冲也是ChannelBuffer的一种实现，其拥有动态增加缓冲容量的能力。当你无法预估消息的数据长度时，动态的buffer缓冲是一种很有用的缓冲结构。</p>
<p>3) 首先，所有的数据将会被累积的缓冲至buf容器。</p>
<p>4) 之后，这个处理器将会检查是否收到了足够的数据然后再进行真实的业务逻辑处理，在这个例子中需要接收4字节数据。否则，Netty将重复调用messageReceived方法，直至4字节数据接收完成。</p>
<p>这里还有另一个地方需要进行修改。你是否还记得我们把TimeClientHandler实例添加到了这个ClientBootstrap实例的默认ChannelPipeline管道里？这意味着同一个TimeClientHandler实例将被多个Channel通道共享，因此接受的数据也将受到破坏。为了给每一个Channel通道创建一个新的TimeClientHandler实例，我们需要实现一个ChannelPipelineFactory管道工厂：</p>
<p>package org.jboss.netty.example.time;  </p>
<p>public class TimeClientPipelineFactory implements ChannelPipelineFactory {  </p>
<p>    public ChannelPipeline getPipeline() {  </p>
<p>        ChannelPipeline pipeline = Channels.pipeline();  </p>
<p>        pipeline.addLast(&#8220;handler&#8221;, new TimeClientHandler());  </p>
<p>        return pipeline;  </p>
<p>    }  </p>
<p>}</p>
<p>现在，我们需要把TimeClient下面的代码片段：</p>
<p>TimeClientHandler handler = new TimeClientHandler();  </p>
<p>bootstrap.getPipeline().addLast(&#8220;handler&#8221;, handler); </p>
<p>替换为：</p>
<p>bootstrap.setPipelineFactory(new TimeClientPipelineFactory()); </p>
<p>虽然这看上去有些复杂，并且由于在TimeClient内部我们只创建了一个连接（connection），因此我们在这里确实没必要引入TimeClientPipelineFactory实例。</p>
<p>然而，当你的应用变得越来越复杂，你就总会需要实现自己的ChannelPipelineFactory，这个管道工厂将会令你的管道配置变得更加具有灵活性。</p>
<p>1.7.3. 第二种方案</p>
<p>虽然第二种方案解决了时间协议客户端遇到的问题，但是这个修改后的处理器实现看上去却不再那么简洁。设想一种更为复杂的，由多个可变长度字段组成的协议。你的ChannelHandler实现将变得越来越难以维护。</p>
<p>正如你已注意到的，你可以为一个ChannelPipeline添加多个ChannelHandler，因此，为了减小应用的复杂性，你可以把这个臃肿的ChannelHandler切分为多个独立的模块单元。例如，你可以把TimeClientHandler切分为两个独立的处理器：</p>
<p> TimeDecoder，解决数据分段的问题。</p>
<p> TimeClientHandler，原始版本的实现。</p>
<p>幸运的是，Netty提供了一个可扩展的类，这个类可以直接拿过来使用帮你完成TimeDecoder的开发：</p>
<p>package org.jboss.netty.example.time;  </p>
<p>public class TimeDecoder extends FrameDecoder {  </p>
<p>    @Override </p>
<p>    protected Object decode(  </p>
<p>            ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)  {  </p>
<p>        if (buffer.readableBytes() < 4) {  </p>
<p>            return null;   </p>
<p>        }  </p>
<p>        return buffer.readBytes(4);  </p>
<p>    }  </p>
<p>} </p>
<p>代码说明</p>
<p>1) 这里不再需要使用ChannelPipelineCoverage的注解，因为FrameDecoder总是被注解为“one”。</p>
<p>2) 当接收到新的数据后，FrameDecoder会调用decode方法，同时传入一个FrameDecoder内部持有的累积型buffer缓冲。</p>
<p>3) 如果decode返回null值，这意味着还没有接收到足够的数据。当有足够数量的数据后FrameDecoder会再次调用decode方法。</p>
<p>4) 如果decode方法返回一个非空值，这意味着decode方法已经成功完成一条信息的解码。FrameDecoder将丢弃这个内部的累计型缓冲。请注意你不需要对多条消息进行解码，FrameDecoder将保持对decode方法的调用，直到decode方法返回非空对象。</p>
<p>如果你是一个勇于尝试的人，你或许应当使用ReplayingDecoder，ReplayingDecoder更加简化了解码的过程。为此你需要查看API手册获得更多的帮助信息。</p>
<p>package org.jboss.netty.example.time;  </p>
<p>public class TimeDecoder extends ReplayingDecoder<VoidEnum> {  </p>
<p>    @Override </p>
<p>    protected Object decode(  </p>
<p>            ChannelHandlerContext ctx, Channel channel,  </p>
<p>            ChannelBuffer buffer, VoidEnum state) {  </p>
<p>        return buffer.readBytes(4);  </p>
<p>    }  </p>
<p>} </p>
<p>此外，Netty还为你提供了一些可以直接使用的decoder实现，这些decoder实现不仅可以让你非常容易的实现大多数协议，并且还会帮你避免某些臃肿、难以维护的处理器实现。请参考下面的代码包获得更加详细的实例：</p>
<p>org.jboss.netty.example.factorial for a binary protocol, and</p>
<p> org.jboss.netty.example.telnet for a text line-based protocol</p>
<p>1.8. 使用POJO代替ChannelBuffer</p>
<p>目前为止所有的实例程序都是使用ChannelBuffer做为协议消息的原始数据结构。在这一节，我们将改进时间协议服务的客户/服务端实现，使用POJO 而不是ChannelBuffer做为协议消息的原始数据结构。</p>
<p>在你的ChannelHandler实现中使用POJO的优势是很明显的；从你的ChannelHandler实现中分离从ChannelBuffer获取数据的代码，将有助于提高你的ChannelHandler实现的可维护性和可重用性。在时间协议服务的客户/服务端代码中，直接使用ChannelBuffer读取一个32位的整数并不是一个主要的问题。然而，你会发现，当你试图实现一个真实的协议的时候，这种代码上的分离是很有必要的。</p>
<p>首先，让我们定义一个称之为UnixTime的新类型。</p>
<p>package org.jboss.netty.example.time;  </p>
<p>import java.util.Date;  </p>
<p>public class UnixTime {  </p>
<p>    private final int value;  </p>
<p>    public UnixTime(int value) {  </p>
<p>        this.value = value;  </p>
<p>    }  </p>
<p>    public int getValue() {  </p>
<p>        return value;  </p>
<p>    }  </p>
<p>    @Override </p>
<p>    public String toString() {  </p>
<p>        return new Date(value * 1000L).toString();  </p>
<p>    }  </p>
<p>} </p>
<p>现在让我们重新修改TimeDecoder实现，让其返回一个UnixTime，而不是一个ChannelBuffer。</p>
<p>@Override </p>
<p>protected Object decode(  </p>
<p>        ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {  </p>
<p>    if (buffer.readableBytes() < 4) {  </p>
<p>        return null;  </p>
<p>    }  </p>
<p>    return new UnixTime(buffer.readInt());  </p>
<p>}</p>
<p>FrameDecoder和ReplayingDecoder允许你返回一个任何类型的对象。如果它们仅允许返回一个ChannelBuffer类型的对象，我们将不得不插入另一个可以从ChannelBuffer对象转换 为UnixTime对象的ChannelHandler实现。</p>
<p>有了这个修改后的decoder实现，这个TimeClientHandler便不会再依赖ChannelBuffer。</p>
<p>@Override </p>
<p>public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {  </p>
<p>    UnixTime m = (UnixTime) e.getMessage();  </p>
<p>    System.out.println(m);  </p>
<p>    e.getChannel().close();  </p>
<p>}</p>
<p>更加简单优雅了，不是吗？同样的技巧也可以应用在服务端，让我们现在更新TimeServerHandler的实现：</p>
<p>@Override </p>
<p>public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {  </p>
<p>    UnixTime time = new UnixTime(System.currentTimeMillis() / 1000);  </p>
<p>    ChannelFuture f = e.getChannel().write(time);  </p>
<p>    f.addListener(ChannelFutureListener.CLOSE);  </p>
<p>} </p>
<p>现在剩下的唯一需要修改的部分是这个ChannelHandler实现，这个ChannelHandler实现需要把一个UnixTime对象重新转换为一个ChannelBuffer。但这却已是相当简单了，因为当你对消息进行编码的时候你不再需要处理数据包的拆分及组装。</p>
<p>package org.jboss.netty.example.time;  </p>
<p>import static org.jboss.netty.buffer.ChannelBuffers.*;  </p>
<p>@ChannelPipelineCoverage(&#8220;all&#8221;)  </p>
<p>public class TimeEncoder extends SimpleChannelHandler {  </p>
<p>    public void writeRequested(ChannelHandlerContext ctx, MessageEvent  e) {  </p>
<p>        UnixTime time = (UnixTime) e.getMessage();  </p>
<p>        ChannelBuffer buf = buffer(4);  </p>
<p>        buf.writeInt(time.getValue());  </p>
<p>        Channels.write(ctx, e.getFuture(), buf);  </p>
<p>    }  </p>
<p>} </p>
<p>     代码说明</p>
<p>1) 因为这个encoder是无状态的，所以其使用的ChannelPipelineCoverage注解值是“all”。实际上，大多数encoder实现都是无状态的。</p>
<p>2) 一个encoder通过重写writeRequested方法来实现对写操作请求的拦截。不过请注意虽然这个writeRequested方法使用了和 messageReceived方法一样的MessageEvent参数，但是它们却分别对应了不同的解释。一个ChannelEvent事件可以既是一个上升流事件（upstream event）也可以是一个下降流事件（downstream event），这取决于事件流的方向。例如：一个MessageEvent消息事件可以作为一个上升流事件（upstream event）被messageReceived方法调用，也可以作为一个下降流事件（downstream event）被writeRequested方法调用。请参考API手册获得上升流事件（upstream event）和下降流事件（downstream event）的更多信息。</p>
<p>3) 一旦完成了POJO和ChannelBuffer转换，你应当确保把这个新的buffer缓冲转发至先前的 ChannelDownstreamHandler处理，这个下降通道的处理器由某个ChannelPipeline管理。Channels提供了多个可以创建和发送ChannelEvent事件的帮助方法。在这个例子中，Channels.write(&#8230;)方法创建了一个新的 MessageEvent事件，并把这个事件发送给了先前的处于某个ChannelPipeline内的 ChannelDownstreamHandler处理器。</p>
<p>另外，一个很不错的方法是使用静态的方式导入Channels类：</p>
<p>import static org.jboss.netty.channel.Channels.*;<br />
&#8230;<br />
ChannelPipeline pipeline = pipeline();<br />
write(ctx, e.getFuture(), buf);<br />
fireChannelDisconnected(ctx);</p>
<p>最后的任务是把这个TimeEncoder插入服务端的ChannelPipeline，这是一个很简单的步骤。</p>
<p>1.9. 关闭你的应用</p>
<p>如果你运行了TimeClient，你肯定可以注意到，这个应用并没有自动退出而只是在那里保持着无意义的运行。跟踪堆栈记录你可以发现，这里有一些运行状态的I/O线程。为了关闭这些I/O线程并让应用优雅的退出，你需要释放这些由ChannelFactory分配的资源。</p>
<p>一个典型的网络应用的关闭过程由以下三步组成：</p>
<p>关闭负责接收所有请求的server socket。</p>
<p>关闭所有客户端socket或服务端为响应某个请求而创建的socket。</p>
<p>释放ChannelFactory使用的所有资源。</p>
<p>为了让TimeClient执行这三步，你需要在TimeClient.main()方法内关闭唯一的客户连接以及ChannelFactory使用的所有资源，这样做便可以优雅的关闭这个应用。</p>
<p>package org.jboss.netty.example.time;  </p>
<p>public class TimeClient {  </p>
<p>    public static void main(String[] args) throws Exception {  </p>
<p>        &#8230;  </p>
<p>        ChannelFactory factory = &#8230;;  </p>
<p>        ClientBootstrap bootstrap = &#8230;;  </p>
<p>        &#8230;  </p>
<p>        ChannelFuture future  = bootstrap.connect(&#8230;);  </p>
<p>        future.awaitUninterruptible();  </p>
<p>        if (!future.isSuccess()) {  </p>
<p>            future.getCause().printStackTrace();  </p>
<p>        }  </p>
<p>        future.getChannel().getCloseFuture().awaitUninterruptibly();  </p>
<p>        factory.releaseExternalResources();  </p>
<p>    }  </p>
<p>} </p>
<p>代码说明</p>
<p>1) ClientBootstrap对象的connect方法返回一个ChannelFuture对象，这个ChannelFuture对象将告知这个连接操作的成功或失败状态。同时这个ChannelFuture对象也保存了一个代表这个连接操作的Channel对象引用。</p>
<p>2) 阻塞式的等待，直到ChannelFuture对象返回这个连接操作的成功或失败状态。</p>
<p>3) 如果连接失败，我们将打印连接失败的原因。如果连接操作没有成功或者被取消，ChannelFuture对象的getCause()方法将返回连接失败的原因。</p>
<p>4) 现在，连接操作结束，我们需要等待并且一直到这个Channel通道返回的closeFuture关闭这个连接。每一个Channel都可获得自己的closeFuture对象，因此我们可以收到通知并在这个关闭时间点执行某种操作。</p>
<p>并且即使这个连接操作失败，这个closeFuture仍旧会收到通知，因为这个代表连接的 Channel对象将会在连接操作失败后自动关闭。</p>
<p>5) 在这个时间点，所有的连接已被关闭。剩下的唯一工作是释放ChannelFactory通道工厂使用的资源。这一步仅需要调用 releaseExternalResources()方法即可。包括NIO Secector和线程池在内的所有资源将被自动的关闭和终止。</p>
<p>关闭一个客户端应用是很简单的，但又该如何关闭一个服务端应用呢？你需要释放其绑定的端口并关闭所有接受和打开的连接。为了做到这一点，你需要使用一种数据结构记录所有的活动连接，但这却并不是一件容易的事。幸运的是，这里有一种解决方案，ChannelGroup。</p>
<p>ChannelGroup是Java 集合 API的一个特有扩展，ChannelGroup内部持有所有打开状态的Channel通道。如果一个Channel通道对象被加入到ChannelGroup，如果这个Channel通道被关闭，ChannelGroup将自动移除这个关闭的Channel通道对象。此外，你还可以对一个ChannelGroup对象内部的所有Channel通道对象执行相同的操作。例如，当你关闭服务端应用时你可以关闭一个ChannelGroup内部的所有Channel通道对象。</p>
<p>为了记录所有打开的socket，你需要修改你的TimeServerHandler实现，将一个打开的Channel通道加入全局的ChannelGroup对象，TimeServer.allChannels:</p>
<p>@Override </p>
<p>public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {  </p>
<p>    TimeServer.allChannels.add(e.getChannel());  </p>
<p>} </p>
<p>代码说明</p>
<p>是的，ChannelGroup是线程安全的。</p>
<p>现在，所有活动的Channel通道将被自动的维护，关闭一个服务端应用有如关闭一个客户端应用一样简单。</p>
<p>package org.jboss.netty.example.time;  </p>
<p>public class TimeServer {  </p>
<p>    static final ChannelGroup allChannels = new DefaultChannelGroup(&#8220;time-server&#8221; );  </p>
<p>    public static void main(String[] args) throws Exception {  </p>
<p>        &#8230;  </p>
<p>        ChannelFactory factory = &#8230;;  </p>
<p>        ServerBootstrap bootstrap = &#8230;;  </p>
<p>        &#8230;  </p>
<p>        Channel channel  = bootstrap.bind(&#8230;);  </p>
<p>        allChannels.add(channel);  </p>
<p>        waitForShutdownCommand();  </p>
<p>        ChannelGroupFuture future = allChannels.close();  </p>
<p>        future.awaitUninterruptibly();  </p>
<p>        factory.releaseExternalResources();  </p>
<p>    }  </p>
<p>} </p>
<p>代码说明</p>
<p>1) DefaultChannelGroup需要一个组名作为其构造器参数。这个组名仅是区分每个ChannelGroup的一个标示。</p>
<p>2) ServerBootstrap对象的bind方法返回了一个绑定了本地地址的服务端Channel通道对象。调用这个Channel通道的close()方法将释放这个Channel通道绑定的本地地址。</p>
<p>3) 不管这个Channel对象属于服务端，客户端，还是为响应某一个请求创建，任何一种类型的Channel对象都会被加入ChannelGroup。因此，你尽可在关闭服务时关闭所有的Channel对象。</p>
<p>4) waitForShutdownCommand()是一个想象中等待关闭信号的方法。你可以在这里等待某个客户端的关闭信号或者JVM的关闭回调命令。</p>
<p>5) 你可以对ChannelGroup管理的所有Channel对象执行相同的操作。在这个例子里，我们将关闭所有的通道，这意味着绑定在服务端特定地址的 Channel通道将解除绑定，所有已建立的连接也将异步关闭。为了获得成功关闭所有连接的通知，close()方法将返回一个 ChannelGroupFuture对象，这是一个类似ChannelFuture的对象。</p>
<p>1.10. 总述</p>
<p>在这一章节，我们快速浏览并示范了如何使用Netty开发网络应用。下一章节将涉及更多的问题。同时请记住，为了帮助你以及能够让Netty基于你的回馈得到持续的改进和提高，Netty社区 将永远欢迎你的问题及建议。</p>
<p>第二章. 架构总览</p>
<p>在这个章节，我们将阐述Netty提供的核心功能以及在此基础之上如何构建一个完备的网络应用。</p>
<p>2.1. 丰富的缓冲实现</p>
<p>Netty使用自建的buffer API，而不是使用NIO的ByteBuffer来代表一个连续的字节序列。与ByteBuffer相比这种方式拥有明显的优势。Netty使用新的buffer类型ChannelBuffer，ChannelBuffer被设计为一个可从底层解决ByteBuffer问题，并可满足日常网络应用开发需要的缓冲类型。这些很酷的特性包括：</p>
<p>如果需要，允许使用自定义的缓冲类型。</p>
<p>复合缓冲类型中内置的透明的零拷贝实现。</p>
<p>开箱即用的动态缓冲类型，具有像StringBuffer一样的动态缓冲能力。</p>
<p>不再需要调用的flip()方法。</p>
<p>正常情况下具有比ByteBuffer更快的响应速度。</p>
<p>更多信息请参考：org.jboss.netty.buffer package description</p>
<p>2.2. 统一的异步 I/O API</p>
<p>传统的Java I/O API在应对不同的传输协议时需要使用不同的类型和方法。例如：java.net.Socket 和 java.net.DatagramSocket它们并不具有相同的超类型，因此，这就需要使用不同的调用方式执行socket操作。</p>
<p>这种模式上的不匹配使得在更换一个网络应用的传输协议时变得繁杂和困难。由于（Java I/O API）缺乏协议间的移植性，当你试图在不修改网络传输层的前提下增加多种协议的支持，这时便会产生问题。并且理论上讲，多种应用层协议可运行在多种传输层协议之上例如TCP/IP,UDP/IP,SCTP和串口通信。</p>
<p>让这种情况变得更糟的是，Java新的I/O（NIO）API与原有的阻塞式的I/O（OIO）API并不兼容，NIO.2(AIO)也是如此。由于所有的API无论是在其设计上还是性能上的特性都与彼此不同，在进入开发阶段，你常常会被迫的选择一种你需要的API。</p>
<p>例如，在用户数较小的时候你可能会选择使用传统的OIO(Old I/O) API，毕竟与NIO相比使用OIO将更加容易一些。然而，当你的业务呈指数增长并且服务器需要同时处理成千上万的客户连接时你便会遇到问题。这种情况下你可能会尝试使用NIO，但是复杂的NIO Selector编程接口又会耗费你大量时间并最终会阻碍你的快速开发。</p>
<p>Netty有一个叫做Channel的统一的异步I/O编程接口，这个编程接口抽象了所有点对点的通信操作。也就是说，如果你的应用是基于Netty的某一种传输实现，那么同样的，你的应用也可以运行在Netty的另一种传输实现上。Netty提供了几种拥有相同编程接口的基本传输实现：</p>
<p>NIO-based TCP/IP transport (See org.jboss.netty.channel.socket.nio),</p>
<p>OIO-based TCP/IP transport (See org.jboss.netty.channel.socket.oio),</p>
<p>OIO-based UDP/IP transport, and</p>
<p>Local transport (See org.jboss.netty.channel.local).</p>
<p>切换不同的传输实现通常只需对代码进行几行的修改调整，例如选择一个不同的ChannelFactory实现。</p>
<p>此外，你甚至可以利用新的传输实现没有写入的优势，只需替换一些构造器的调用方法即可，例如串口通信。而且由于核心API具有高度的可扩展性，你还可以完成自己的传输实现。</p>
<p>2.3. 基于拦截链模式的事件模型</p>
<p>一个定义良好并具有扩展能力的事件模型是事件驱动开发的必要条件。Netty具有定义良好的I/O事件模型。由于严格的层次结构区分了不同的事件类型，因此Netty也允许你在不破坏现有代码的情况下实现自己的事件类型。这是与其他框架相比另一个不同的地方。很多NIO框架没有或者仅有有限的事件模型概念；在你试图添加一个新的事件类型的时候常常需要修改已有的代码，或者根本就不允许你进行这种扩展。</p>
<p>在一个ChannelPipeline内部一个ChannelEvent被一组ChannelHandler处理。这个管道是拦截过滤器 模式的一种高级形式的实现，因此对于一个事件如何被处理以及管道内部处理器间的交互过程，你都将拥有绝对的控制力。例如，你可以定义一个从socket读取到数据后的操作：</p>
<p>public class MyReadHandler implements SimpleChannelHandler {  </p>
<p>    public void messageReceived(ChannelHandlerContext ctx, MessageEvent evt) {  </p>
<p>        Object message = evt.getMessage();  </p>
<p>        // Do something with the received message. </p>
<p>        &#8230;  </p>
<p>        // And forward the event to the next handler. </p>
<p>        ctx.sendUpstream(evt);  </p>
<p>    }  </p>
<p>} </p>
<p>同时你也可以定义一种操作响应其他处理器的写操作请求：</p>
<p>public class MyWriteHandler implements SimpleChannelHandler {  </p>
<p>    public void writeRequested(ChannelHandlerContext ctx, MessageEvent evt) {  </p>
<p>        Object message = evt.getMessage();  </p>
<p>        // Do something with the message to be written. </p>
<p>        &#8230;  </p>
<p>        // And forward the event to the next handler. </p>
<p>        ctx.sendDownstream(evt);  </p>
<p>    }  </p>
<p>} </p>
<p>有关事件模型的更多信息，请参考API文档ChannelEvent和ChannelPipeline部分。</p>
<p>2.4. 适用快速开发的高级组件</p>
<p>上述所提及的核心组件已经足够实现各种类型的网络应用，除此之外，Netty也提供了一系列的高级组件来加速你的开发过程。</p>
<p>2.4.1. Codec框架</p>
<p>就像“1.8. 使用POJO代替ChannelBuffer”一节所展示的那样，从业务逻辑代码中分离协议处理部分总是一个很不错的想法。然而如果一切从零开始便会遭遇到实现上的复杂性。你不得不处理分段的消息。一些协议是多层的（例如构建在其他低层协议之上的协议）。一些协议过于复杂以致难以在一台主机（single state machine）上实现。</p>
<p>因此，一个好的网络应用框架应该提供一种可扩展，可重用，可单元测试并且是多层的codec框架，为用户提供易维护的codec代码。</p>
<p>Netty提供了一组构建在其核心模块之上的codec实现，这些简单的或者高级的codec实现帮你解决了大部分在你进行协议处理开发过程会遇到的问题，无论这些协议是简单的还是复杂的，二进制的或是简单文本的。</p>
<p>2.4.2. SSL / TLS 支持</p>
<p>不同于传统阻塞式的I/O实现，在NIO模式下支持SSL功能是一个艰难的工作。你不能只是简单的包装一下流数据并进行加密或解密工作，你不得不借助于javax.net.ssl.SSLEngine，SSLEngine是一个有状态的实现，其复杂性不亚于SSL自身。你必须管理所有可能的状态，例如密码套件，密钥协商（或重新协商），证书交换以及认证等。此外，与通常期望情况相反的是SSLEngine甚至不是一个绝对的线程安全实现。</p>
<p>在Netty内部，SslHandler封装了所有艰难的细节以及使用SSLEngine可能带来的陷阱。你所做的仅是配置并将该SslHandler插入到你的ChannelPipeline中。同样Netty也允许你实现像StartTlS 那样所拥有的高级特性，这很容易。</p>
<p>2.4.3. HTTP实现</p>
<p>HTTP无疑是互联网上最受欢迎的协议，并且已经有了一些例如Servlet容器这样的HTTP实现。因此，为什么Netty还要在其核心模块之上构建一套HTTP实现？</p>
<p>与现有的HTTP实现相比Netty的HTTP实现是相当与众不同的。在HTTP消息的低层交互过程中你将拥有绝对的控制力。这是因为Netty的HTTP实现只是一些HTTP codec和HTTP消息类的简单组合，这里不存在任何限制——例如那种被迫选择的线程模型。你可以随心所欲的编写那种可以完全按照你期望的工作方式工作的客户端或服务器端代码。这包括线程模型，连接生命期，快编码，以及所有HTTP协议允许你做的，所有的一切，你都将拥有绝对的控制力。</p>
<p>由于这种高度可定制化的特性，你可以开发一个非常高效的HTTP服务器，例如：</p>
<p>要求持久化链接以及服务器端推送技术的聊天服务（e.g. Comet ）</p>
<p>需要保持链接直至整个文件下载完成的媒体流服务（e.g. 2小时长的电影）</p>
<p>需要上传大文件并且没有内存压力的文件服务（e.g. 上传1GB文件的请求）</p>
<p>支持大规模mash-up应用以及数以万计连接的第三方web services异步处理平台</p>
<p>2.4.4. Google Protocol Buffer 整合</p>
<p>Google Protocol Buffers 是快速实现一个高效的二进制协议的理想方案。通过使用ProtobufEncoder和ProtobufDecoder，你可以把Google Protocol Buffers 编译器 (protoc)生成的消息类放入到Netty的codec实现中。请参考“LocalTime ”实例，这个例子也同时显示出开发一个由简单协议定义 的客户及服务端是多么的容易。</p>
<p>2.5. 总述<br />
在这一章节，我们从功能特性的角度回顾了Netty的整体架构。Netty有一个简单却不失强大的架构。这个架构由三部分组成——缓冲（buffer），通道（channel），事件模型（event model）——所有的高级特性都构建在这三个核心组件之上。一旦你理解了它们之间的工作原理，你便不难理解在本章简要提及的更多高级特性。</p>
<p>你可能对Netty的整体架构以及每一部分的工作原理仍旧存有疑问。如果是这样，最好的方式是告诉我们 应该如何改进这份指南。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/811/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Comet 10w + 连接打开测试</title>
		<link>http://www.javagg.com/archives/804</link>
		<comments>http://www.javagg.com/archives/804#comments</comments>
		<pubDate>Fri, 09 Jul 2010 04:27:18 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[java]]></category>
		<category><![CDATA[Comet]]></category>
		<category><![CDATA[netty]]></category>
		<category><![CDATA[nio]]></category>

		<guid isPermaLink="false">http://www.javagg.com/archives/804</guid>
		<description><![CDATA[Comet是一个时髦词语，象ajax一样，非常Cool。Comet的主要作用是，服务器可以把新数据主动传送给客户端。 
我认为，Comet将是下一波互联网应用更新潮的主流.象google wave使用Comet一样，实现实时更新. 
Plurkr 的增长速度非常快，我们正在成为最大的Python网站之一，我们的每天都有10w+的在线用户。
我们大约用了一个星期寻找不同的解决方案.
以下是我积累的一些技术资料：
Python Twisted: 无阻塞的Python服务器。遗憾的是吃了很多的CPU，不能规模化应用。
Jetty: 他们声称对Comet有良好的支持，我们运行1w个活跃用户约点2GB的内存，这是我们不能接受的。
Apache Tomcat: 和Jetty有点象，同样需要非常多的内存.
Apache Mina: 一个NIO的（非阻塞的IO）的框架，我用来建立一个HTTP服务器。不幸的是mina严重缺小文档，我们并没有大规模测试了。
这些尝试后，发现了，他们无法处理大量的负载,我正要放弃。 但后来，我偶然发现后救世主：  
 JBoss Netty: 一个NIO的（非阻塞的IO）的框架, 创始人是Apache mina的原作者，它的设计很出色，和其它的服务器对比过后，发现他的并发相当惊人的.
 最低标准
 我们使用Netty 打开10w+的comet连接，在一台四核的服务器上只使用了数GB的内存和20%的cpu。也就是说，我们已经解决了C10k * 10 问题使用非阻塞技术和一些非常令人印象深刻的库（即Java NIO的和Netty）。 
一个大的荣誉是属于 Trustin Lee 对他的Netty惊人的工作！ 
 原文:http://amix.dk/blog/post/19456
]]></description>
			<content:encoded><![CDATA[<p>Comet是一个时髦词语，象ajax一样，非常Cool。Comet的主要作用是，服务器可以把新数据主动传送给客户端。 </p>
<p>我认为，Comet将是下一波互联网应用更新潮的主流.象google wave使用Comet一样，实现实时更新. </p>
<p>Plurkr 的增长速度非常快，我们正在成为最大的Python网站之一，我们的每天都有10w+的在线用户。<br />
我们大约用了一个星期寻找不同的解决方案.<span id="more-804"></span></p>
<p>以下是我积累的一些技术资料：</p>
<p><a href="http://twistedmatrix.com/trac/">Python Twisted</a>: 无阻塞的Python服务器。遗憾的是吃了很多的CPU，不能规模化应用。</p>
<p><a href="http://www.oschina.net/p/jetty">Jetty</a>: 他们声称对Comet有良好的支持，我们运行1w个活跃用户约点2GB的内存，这是我们不能接受的。</p>
<p><a href="http://www.oschina.net/p/tomcat">Apache Tomcat</a>: 和Jetty有点象，同样需要非常多的内存.</p>
<p><a href="http://www.oschina.net/p/mina">Apache Mina</a>: 一个NIO的（非阻塞的IO）的框架，我用来建立一个HTTP服务器。不幸的是mina严重缺小文档，我们并没有大规模测试了。</p>
<p>这些尝试后，发现了，他们无法处理大量的负载,我正要放弃。 但后来，我偶然发现后救世主：  </p>
<p> <a href="http://www.oschina.net/p/netty">JBoss Netty</a>: 一个NIO的（非阻塞的IO）的框架, 创始人是Apache mina的原作者，它的设计很出色，和其它的服务器对比过后，发现他的并发相当惊人的.</p>
<p> <strong>最低标准</strong></p>
<p><strong> </strong>我们使用Netty 打开10w+的comet连接，在一台四核的服务器上只使用了数GB的内存和20%的cpu。也就是说，我们已经解决了<a href="http://www.kegel.com/c10k.html" target="_blank">C10k * 10</a> 问题使用非阻塞技术和一些非常令人印象深刻的库（即Java NIO的和Netty）。 </p>
<p>一个大的荣誉是属于 <a href="http://gleamynode.net/" target="_blank">Trustin Lee</a> 对他的Netty惊人的工作！ </p>
<p> 原文:http://amix.dk/blog/post/19456</p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/804/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

