Java NIO框架Netty教程

先啰嗦两句,如果你还不知道Netty是做什么的能做什么。那可以先简单的搜索了解一下。我只能说Netty是一个NIO的框架,可以用于开发分布式的Java程序。具体能做什么,各位可以尽量发挥想象。技术,是服务于人而不是局限住人的。

如果你已经万事具备,那么我们先从一段代码开始。程序员们习惯的上手第一步,自然是"Hello world",不过Netty官网的例子却偏偏抛弃了"Hello world"。那我们就自己写一个最简单的"Hello world"的例子,作为上手。

  1. /** 
  2.  * Netty 服务端代码 
  3.  *  
  4.  * @author lihzh 
  5.  * @alia OneCoder 
  6.  * @blog http://www.coderli.com 
  7.  */  
  8. public class HelloServer {  
  9.   
  10.     public static void main(String args[]) {  
  11.         // Server服务启动器  
  12.         ServerBootstrap bootstrap = new ServerBootstrap(  
  13.                 new NioServerSocketChannelFactory(  
  14.                         Executors.newCachedThreadPool(),  
  15.                         Executors.newCachedThreadPool()));  
  16.         // 设置一个处理客户端消息和各种消息事件的类(Handler)  
  17.         bootstrap  
  18.                 .setPipelineFactory(new ChannelPipelineFactory() {  
  19.                     @Override  
  20.                     public ChannelPipeline getPipeline()  
  21.                             throws Exception {  
  22.                         return Channels  
  23.                                 .pipeline(new HelloServerHandler());  
  24.                     }  
  25.                 });  
  26.         // 开放8000端口供客户端访问。  
  27.         bootstrap.bind(new InetSocketAddress(8000));  
  28.     }  
  29.   
  30.     private static class HelloServerHandler extends  
  31.             SimpleChannelHandler {  
  32.   
  33.         /** 
  34.          * 当有客户端绑定到服务端的时候触发,打印"Hello world, I'm server." 
  35.          *  
  36.          * @alia OneCoder 
  37.          * @author lihzh 
  38.          */  
  39.         @Override  
  40.         public void channelConnected(  
  41.                 ChannelHandlerContext ctx,  
  42.                 ChannelStateEvent e) {  
  43.             System.out.println("Hello world, I'm server.");  
  44.         }  
  45.     }  
  46. }  
  1. /** 
  2.  * Netty 客户端代码 
  3.  *  
  4.  * @author lihzh 
  5.  * @alia OneCoder 
  6.  * @blog http://www.coderli.com 
  7.  */  
  8. public class HelloClient {  
  9.   
  10.     public static void main(String args[]) {  
  11.         // Client服务启动器  
  12.         ClientBootstrap bootstrap = new ClientBootstrap(  
  13.                 new NioClientSocketChannelFactory(  
  14.                         Executors.newCachedThreadPool(),  
  15.                         Executors.newCachedThreadPool()));  
  16.         // 设置一个处理服务端消息和各种消息事件的类(Handler)  
  17.         bootstrap.setPipelineFactory(new ChannelPipelineFactory() {  
  18.             @Override  
  19.             public ChannelPipeline getPipeline() throws Exception {  
  20.                 return Channels.pipeline(new HelloClientHandler());  
  21.             }  
  22.         });  
  23.         // 连接到本地的8000端口的服务端  
  24.         bootstrap.connect(new InetSocketAddress(  
  25.                 "127.0.0.1"8000));  
  26.     }  
  27.   
  28.     private static class HelloClientHandler extends SimpleChannelHandler {  
  29.   
  30.   
  31.         /** 
  32.          * 当绑定到服务端的时候触发,打印"Hello world, I'm client." 
  33.          *  
  34.          * @alia OneCoder 
  35.          * @author lihzh 
  36.          */  
  37.         @Override  
  38.         public void channelConnected(ChannelHandlerContext ctx,  
  39.                 ChannelStateEvent e) {  
  40.             System.out.println("Hello world, I'm client.");  
  41.         }  
  42.     }  
  43. }  

既然是分布式的,自然要分多个服务。Netty中,需要区分Server和Client服务。所有的Client都是绑定在Server上的,他们之间是不能通过Netty直接通信的。(自己采用的其他手段,不包括在内。)。白话一下这个通信过程,Server端开放端口,供Client连接,Client发起请求,连接到Server指定的端口,完成绑定。随后便可自由通信。其实就是普通Socket连接通信的过程。

Netty框架是基于事件机制的,简单说,就是发生什么事,就找相关处理方法。就跟着火了找119,抢劫了找110一个道理。所以,这里,我们处理的是当客户端和服务端完成连接以后的这个事件。什么时候完成的连接,Netty知道,他告诉我了,我就负责处理。这就是框架的作用。Netty,提供的事件还有很多,以后会慢慢的接触和介绍。

你应该已经可以上手了:)

===

"Hello World"的代码固然简单,不过其中的几个重要概念(类)和 Netty的工作原理还是需要简单明确一下,至少知道其是负责什。方便自己以后更灵活的使用和扩展。

 
声明,笔者一介码农,不会那么多专业的词汇和缩写,只能以最简单苍白的话来形容个人的感受和体会。如果您觉得这太不专业,笔者首先只能抱歉。然后,笔者曾转过《Netty代码分析》,您可参考。
  • ChannelEvent
先说这个ChannelEvent,因为Netty是基于事件驱动的,就是我们上文提到的,发生什么事,就通知"有关部门"。所以,不难理解,我们自己的业务代码中,一定有跟这些事件相关的处理。在样例代码,我们处理的事件,就是channelConnected。以后,我们还会处理更多的事件。
 
  • ChannelPipeline
Pipeline,翻译成中文的意思是:管道,传输途径。也就是说,在这里他是控制ChannelEvent事件分发和传递的。事件在管道中流转,第一站到哪,第二站到哪,到哪是终点,就是用这个ChannelPipeline 处理的。比如:开发事件。先给A设计,然后给B开发。一个流转图,希望能给你更直观的感觉。
  • ChannelHandler
刚说Pipeline负责把事件分发到相应的站点,那个这个站点在Netty里,就是指ChannelHandler。事件到了ChannelHandler这里,就要被具体的进行处理了,我们的样例代码里,实现的就是这样一个处理事件的“站点”,也就是说,你自己的业务逻辑一般都是从这里开始的。
  • Channel
有了个部门的协调处理,我们还需要一个从整体把握形势的,所谓“大局观”的部门,channel。
channel,能够告诉你当前通道的状态,是连同还是关闭。获取通道相关的配置信息。得到Pipeline等。是一些全局的信息。Channel自然是由ChannelFactory产生的。Channel的实现类型,决定了你这个通道是同步的还是异步的(nio)。例如,我们样例里用的是NioServerSocketChannel。

这些基本的概念,你懂了吧。

===

说了这么多废话,才提到对象的传输,不知道您是不是已经不耐烦了。一个系统内部的消息传递,没有对象传递是不太现实的。下面就来说说,怎么传递对象。

如果,您看过前面的介绍,如果您善于专注本质,勤于思考。您应该也会想到,我们说过,Netty的消息传递都是基于流,通过ChannelBuffer传递的,那么自然,Object也需要转换成ChannelBuffer来传递。好在Netty本身已经给我们写好了这样的转换工具。 ObjectEncoder和ObjectDecoder。

工具怎么用?再一次说说所谓的本质,我们之前也说过,Netty给我们处理自己业务的空间是在灵活的可子定义的Handler上的,也就是说,如果我们自己去做这个转换工作,那么也应该在Handler里去做。而Netty,提供给我们的ObjectEncoder和Decoder也恰恰是一组 Handler。于是,修改Server和Client的启动代码:

 
 
服务端
01
02
03
04
05
06
07
08
09
10
// 设置一个处理客户端消息和各种消息事件的类(Handler)
bootstrap.setPipelineFactory(newChannelPipelineFactory() {
    @Override
    publicChannelPipeline getPipeline()throwsException {
        returnChannels.pipeline(
        newObjectDecoder(ClassResolvers.cacheDisabled(this
                .getClass().getClassLoader())),
        newObjectServerHandler());
    }
});
客户端
1
2
3
4
5
6
7
8
// 设置一个处理服务端消息和各种消息事件的类(Handler)
bootstrap.setPipelineFactory(newChannelPipelineFactory() {
    @Override
    publicChannelPipeline getPipeline()throwsException {
        returnChannels.pipeline(newObjectEncoder(),
                newObjectClientHandler());
    }
});

要传递对象,自然要有一个被传递模型,一个简单的Pojo,当然,实现序列化接口是必须的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * @author lihzh
 * @alia OneCoder
 */
public class Command implementsSerializable {
 
    privatestaticfinallongserialVersionUID = 7590999461767050471L;
 
    privateString actionName;
 
    publicString getActionName() {
        returnactionName;
    }
 
    publicvoidsetActionName(String actionName) {
        this.actionName = actionName;
    }
}

服务端和客户端里,我们自定义的Handler实现如下:

ObjectServerHandler .java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * 对象传递服务端代码
 *
 * @author lihzh
 * @alia OneCoder
 */
public class ObjectServerHandler extendsSimpleChannelHandler {
 
    /**
     * 当接受到消息的时候触发
     */
    @Override
    publicvoidmessageReceived(ChannelHandlerContext ctx, MessageEvent e)
            throwsException {
        Command command = (Command) e.getMessage();
        // 打印看看是不是我们刚才传过来的那个
        System.out.println(command.getActionName());
    }
}
ObjectClientHandler .java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
 * 对象传递,客户端代码
 *
 * @author lihzh
 * @alia OneCoder
 */
public class ObjectClientHandler extendsSimpleChannelHandler {
 
    /**
     * 当绑定到服务端的时候触发,给服务端发消息。
     *
     * @author lihzh
     * @alia OneCoder
     */
    @Override
    publicvoidchannelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
        // 向服务端发送Object信息
        sendObject(e.getChannel());
    }
 
    /**
     * 发送Object
     *
     * @param channel
     * @author lihzh
     * @alia OneCoder
     */
    privatevoidsendObject(Channel channel) {
        Command command =newCommand();
        command.setActionName("Hello action.");
        channel.write(command);
    }
 
}

启动后,服务端正常打印结果:Hello action.

简单梳理一下思路:

通过Netty传递,都需要基于流,以ChannelBuffer的形式传递。所以,Object -> ChannelBuffer.
Netty提供了转换工具,需要我们配置到Handler。
样例从客户端 -> 服务端,单向发消息,所以在客户端配置了编码,服务端解码。如果双向收发,则需要全部配置Encoder和Decoder。
这里需要注意,注册到Server的Handler是有顺序的,如果你颠倒一下注册顺序:

1
2
3
4
5
6
7
8
9
bootstrap.setPipelineFactory(newChannelPipelineFactory() {
    @Override
    publicChannelPipeline getPipeline()throwsException {
        returnChannels.pipeline(newObjectServerHandler(),
                newObjectDecoder(ClassResolvers.cacheDisabled(this
                        .getClass().getClassLoader()))
                );
    }
});

结果就是,会先进入我们自己的业务,再进行解码。这自然是不行的,会强转失败。至此,你应该会用Netty传递对象了吧。

===

有一段事件没有更新文章了,各种原因都有吧。搬家的琐事,搬家后的安逸呵呵。不过,OneCoder明白,绝不能放松。对于Netty的学习,也该稍微深入一点了。

所以,这次OneCoder花了几天时间,仔细梳理了一下Netty的源码,总结了一下ServerBootStrap的启动和任务处理流程,基本涵盖了Netty的关键架构。
 
OneCoder总结了一张流程图:
 
 
该图是OneCoder通过阅读Netty源码,逐渐记录下来的。基本可以说明Netty服务的启动流程。这里在具体讲解一下。
 
首先说明,我们这次顺利的流程是基于NioSocketServer的。也就是基于Java NIO Selector的实现方式。在第六讲《Java NIO框架Netty教程(六)-Java NIO Selector模式》中,我们已经知道使用Selector的几个关键点,所以这里我们也重点关注一下,这些点在Netty中是如何使用的。
 
很多看过Netty源码的人都说Netty源码写的很漂亮。可漂亮在哪呢?Netty通过一个ChannelFactory决定了你当前使用的协议类型 (Nio/oio等),比如,OneCoder这里使用的是NioServerSocket,那么需要声明的Factory即为 NioServerSocketChannelFactory,声明了这个Factory,就决定了你使用的Channel,pipeline以及 pipeline中,具体处理业务的sink的类型。这种使用方式十分简洁的,学习曲线很低,切换实现十分容易。
 
Netty采用的是基于事件的管道式架构设计,事件(Event)在管道(Pipeline)中流转,交由(通过pipelinesink)相应的处理器(Handler)。这些关键元素类型的匹配都是由开始声明的ChannelFactory决定的。
 
Netty框架内部的业务也遵循这个流程,Server端绑定端口的binder其实也是一个Handler,在构造完Binder后,便要声明一个 Pipeline管道,并赋给新建一个Channel。Netty在newChannel的过程中,相应调用了Java中的 ServerSocketChannel.open方法,打开一个channel。然后触发fireChannelOpen事件。这个事件的接受是可以复写的。Binder自身接收了这个事件。在事件的处理中,继续向下完成具体的端口的绑定。对应Selector中的 socketChannel.socket().bind()。然后触发fireChannelBound事件。默认情况下,该事件无人接受,继续向下开始构造Boss线程池。我们知道在Netty中Boss线程池是用来接受和分发请求的核心线程池。所以在channel绑定后,必然要启动Boss线城池,随时准备接受client发来的请求。在Boss构造函数中,第一次注册了selector感兴趣的事件类型,SelectionKey.OP_ACCEPT。至此,在第六讲中介绍的使用Selector的几个关键步骤都体现在Netty中了。在Boss中回启动一个死循环来查询是否有感兴趣的事件发生,对于第一次的客户端的注册事件,Boss会将Channel注册给worker保存。
 
这里补充一下,也是图中忽略的部分,就是关于worker线程池的初始化时机问题。worker池的构造,在最开始构造ChannelFactory的时候就已经准备好了。在NioServerSocketChannelFactory的构造函数里,会new一个NioWorkerPool。在 NioWorkerPool的基类AbstractNioWorkerPool的构造函数中,会调用OpenSelector方法,其中也打开了一个 selector,并且启动了worker线程池。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void openSelector() {
        try{
            selector = Selector.open();
        }catch(Throwable t) {
            thrownewChannelException("Failed to create a selector.", t);
        }
 
        // Start the worker thread with the new Selector.
        booleansuccess =false;
        try{
            DeadLockProofWorker.start(executor,newThreadRenamingRunnable(this,"New I/O  worker #"+ id));
            success =true;
        }finally{
            if(!success) {
                // Release the Selector if the execution fails.
                try{
                    selector.close();
                }catch(Throwable t) {
                    logger.warn("Failed to close a selector.", t);
                }
                selector =null;
                // The method will return to the caller at this point.
            }
        }
        assertselector !=null&& selector.isOpen();
    }

至此,会分线程启动AbstractNioWorker中run逻辑。同样是循环处理任务队列。

1
2
3
4
processRegisterTaskQueue();
processEventQueue();
processWriteTaskQueue();
processSelectedKeys(selector.selectedKeys());
 
这样,设计使事件的接收和处理模块完全解耦。
由此可见,如果你想从nio切换到oio,只需要构造不同的ChannelFacotry即可。果然简洁优雅。


===

Netty4更新

分类: netty 2598人阅读 评论(0)收藏 举报

netty现在应该是java界最流行的网络框架之一了,高性能,可扩展,代码优雅。之前做的页游都是用netty3.x来做网络层通信。最近看到netty4快要出来了,一些新的特性还是很值得推介的。

 

  • 1.Buff

ChannelBuffer变成了ByteBuff。还引入了Buff对象池Unpooled来管理回收不用的buff,避免gc的频率。在netty3中,buff都是固定大小或者dynamic(write*需要更多的空间)。

 

为了解决这些问题。 netty4中的所有buff都是动态大小的,可以随意更改capacity,并且性能更好。

 

CompositeByteBuf是新引入的,可以避免大块内存的拷贝。当你需要组合一个新的buff时,用Unpooled.wrappedBuffer(...)或者Unpooled.compositieBuff(...) 重点要介绍一些ByteBuf对象池,当一个ByteBuf被对象池回收后,可以重复利用,从而避免了频繁的内存分配和gc。当一个ByteBuf被write到目标socket是,它会自动被回收到对象池。

 

  • 2.Channel

概念的更改,变得更容易理解了。Upstream变成Inbound,Downstream变成了Outbound。

 

之前netty3每次io读写,都会new一个ChannelBuffer,会导致gc频繁。netty4将socket的数据直接存入inbound buffer,并且使用了对象池技术后会改善这种状况。

 

netty3的事件处理笼统归为handleUpstream和handleDownstream 。

netty4引入了更具体的事件,包括:

void channelRegistered(ChannelHandlerContext ctx) throws Exception;

void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

void channelActive(ChannelHandlerContext ctx) throws Exception;

void channelInactive(ChannelHandlerContext ctx) throws Exception;

void inboundBufferUpdated(ChannelHandlerContext ctx) throws Exception;

 

netty4还简化了channel状态模型。之前一个channel connect后,会触发channelOpen, channelBound, 和 channelConnected.

channel关闭后会触发 channelDisconnected, channelUnbound, 和channelClosed.

上面的几个事件被合并成channelActive 和 channelInActive。 messageReceived 和 writeRequested 被 inboundBufferUpdated 和flush 替换了。

 

Channel支持 half-closed,可以指定关闭后接收剩余的数据。

 

  • 3.I/0线程分配

netty4中,EventLoopGroup取代了ChannelFactory,一个channel必须显式地registere 到EventLoopGroup。这样有个好处,就是在不同的业务中,Channel可以随时deregister和registered到某个EventLoopGroup中去。

 

EventLoop 是本质上也是一个ScheduledExecutorService,所以一个channel的一些定时操作也可以交由它去执行。从而保证了一个channel的所有操作都是在同一个线程中执行了。。这样的内存模型可以简化很多的线程问题。

 

  • 4.配置项和attach对象

之前ChannelConfig 的配置项项都是用字符串定义的。容易出问题。netty4重新定义了常量。例如 cfg.setOption(ChannelOption.TCP_NODELAY, true);

 

引入了AttributeMap,可以存储任何对象到Channel中。

 

  • 总结

netty4的改进是很值得推荐的,尤其是内存分配方面的改进,今后更高并发的系统将变得可能,gc造成的延迟也讲得到改善。 准备在netty4正式版出来,稳定之后用于下一个新游戏项目中。到时再跟大家分享一些心得和体会。


 准备将blog搬到这里了,希望能跟大家多分享交流技术,thanks


===

Netty 4.0 新的特性及需要注意的地方

英文原文:Netty4.0 New and noteworthy

标签:Netty
88人收藏此文章,我要收藏 沉寂brain 推荐于 1年前 (共 24 段, 翻译完成于 04-03) (19评)

这篇文章和你一起过下Netty的主发行版本的一些显著的改变和新特性,让你在把你的应用程序转换到新版本的时候有个概念。

项目结构改变

Netty的包名从org.jboss.netty改为io.netty,因为我们不在是JBoss.org的一部分了

二进制JAR包被分为了多个子模块以便用户能够从类路径中去掉非必需的特性。当前的结构如下:

模块 描述
netty project parent
common utility and logging
buffer buffer API
transport channel API and its core implementations
transport-rtrx RTRX transport implementation
transport-sctp SCTP transport implementation
transport-udt UDT transport implementation
handler channel handlers
codec codec framework
codec-http HTTP, Web Sockets, SPDY, and RTSP codec
codec-socks Socks codec
example examples
all generates an all-in-one JAR
tarball generates a tarball distribution

所有的Netty的Jar(除了netty-all外)包现在都是OSGI的bundle,能够用在你喜欢的OSGI容器上。

常用API的变化

  • 现在Netty里的大部分操作都支持简洁的方法链。
  • 不能配置的getter现在都没有了get/is前缀 (如Channel.getRemoteAddress()→Channel.remoteAddress())
excepiton
excepiton
翻译于 1年前

5人顶

 翻译的不错哦!

Buffer API变化

ChannelBuffer ByteBuf

由于上文所提到的结构上的变化,buffer API现在可以作为一个单独的包被使用。为此,ChannelBuffer这个类型名也不那么讲得通了,而应该变更为ByteBuf。

用来创建新buffer的功能类ChannelBuffers被拆分为两个功能类:Unpooled和BufUtil。就像这个名字所暗示的,4.0引入了一个新的池化的ByteBufs,它可以通过ByteBuf的分配器(Allocator)的对应实现ByteBufAllocator来获得。

大多数的buffer变成了动态的,具备可配置的最大容量

在3.x时期,buffer分为固定和动态两种类型。一个固定buffer的容量在创建之后就无法改变,而动态buffer的容量在write*(译者按:writeByte,writeInt,writeLong...)方法需要更多空间时自动扩容。

从4.0开始,所有buffer都变成了动态的。但是,相对于之前的动态进行了优化。你可以更容易也更安全的对一个buffer的容量进行扩大和缩小。之所以说它容易是因为有一个新的ByteBuf.capacity(int newCapacity)的方法。说它安全是因为你可以设置一个容量的最大值,以防止容量没有限制的增大。

1 // 不要再使用 dynamicBuffer() - 使用 buffer().
2 ByteBuf buf = ByteBuf.buffer();
3  
4 // 增加buffer的容量
5 buf.capacity(1024);
6 ...
7  
8 // 缩减buffer的容量 (最后的512个byte被丢弃)
9 buf.capacity(512);

唯一的例外是那些使用wrappedBuffer方法创建的,包装(warp)了一个buffer或一个byte数组的buffer。你无法扩大它的容量,因为这样会使包装一个已有buffer的目的是去意义——减少内存的复制。如果你想要在包装了一个buffer之后改变它的容量,你应该重新创建一个拥有足够容量的buffer,然后将你想要包装的那个buffer的内容复制过来。

燮羽
燮羽
翻译于 1年前

4人顶

 翻译的不错哦!

新接口: CompositeByteBuf

一个新的名叫CompositeByteBuf的接口为组合buffer(composite buffer)的实现定义了多种高级的操作。一个用户可以使用组合buffer,以只比随机访问大一点的代价达到一个批量内存复制的目的。要创建一个新的组合buffer,可以像以前一样使用Unpooled.wrappedBuffer(... 译者注:此处省略号应该是指省略方法参数,下同)或Unpooled.compositeBuffer(...)。

可预知的NIO buffer转型

在3.x中,ChannelBuffer.toByteBuffer()以及它的其他变体所提供的约定并不那么明确。用户无法确定这些方法会返回一个拥有共享数据的视图buffer还是一个拥有独立数据的通过复制得到的buffer。4.0将toByteBuffer()替换为ByteBuf.nioBufferCount(),nioBuffer(),以及nioBUffers()。如果调用nioBufferCount()返回0,用户总是可以通过调用copy().nioBuffer()来获得一个复制的buffer。

对小字节序变更的支持

对小字节序的支持经历了重大变化。在之前的版本中,一个用户为了得到一个小字节序的buffer有两种选择:特别指定一个LittleEndianHeapChannelBufferFactory;用目标字节序将已存在的buffer包装起来。4.0添加了一个新方法,ByteBuf.order(ByteOrder)。这个方法返回当前buffer对象的一个具有指定字节序的视图buffer:

01 import io.netty.buffer.ByteBuf;
02 import io.netty.buffer.Unpooled;
03 import java.nio.ByteOrder;
04   
05 ByteBuf buf = Unpooled.buffer(4);
06 buf.setInt(0, 1);
07 // 打印出 '00000001'
08 System.out.format("%08x%n", buf.getInt(0));
09   
10 ByteBuf leBuf = buf.order(ByteOrder.LITTLE_ENDIAN);
11 // 打印出 '01000000'
12 System.out.format("%08x%n", leBuf.getInt(0));
13   
14 assert buf != leBuf;
15 assert buf == buf.order(ByteOrder.BIG_ENDIAN);
燮羽
燮羽
翻译于 1年前

2人顶

 翻译的不错哦!

Pooled ByteBuf

前面已经提到Netty引入了pooledByteBufinstances。这在很多方面都很实用,举列如下:

  • 限制了GC压力,这是因为使用unpooled ByteBufs会造成沉重的分配与再分配问题
  • Better handling of direct (native)ByteBuf更好的处理直接(本地)的ByteBuf
  • 一个ByteBuf 可以被一个ByteBufAllocator包含.
01 public interface ByteBufAllocator {
02   
03     ByteBuf buffer();
04     ByteBuf buffer(int initialCapacity);
05     ByteBuf buffer(int initialCapacity, int maxCapacity);
06     ByteBuf heapBuffer();
07     ByteBuf heapBuffer(int initialCapacity);
08     ByteBuf heapBuffer(int initialCapacity, int maxCapacity);
09     ByteBuf directBuffer();
10     ByteBuf directBuffer(int initialCapacity);
11     ByteBuf directBuffer(int initialCapacity, int maxCapacity);
12     ByteBuf ioBuffer();
13   
14     CompositeByteBuf compositeBuffer();
15     CompositeByteBuf compositeBuffer(int maxNumComponents);
16     CompositeByteBuf compositeHeapBuffer();
17     CompositeByteBuf compositeHeapBuffer(int maxNumComponents);
18     CompositeByteBuf compositeDirectBuffer();
19     CompositeByteBuf compositeDirectBuffer(int maxNumComponents);
20 }
要想从一个handler那里获取当前的 ByteBufAllocator,可以使用ChannelHandlerContext.alloc()或Channel.alloc()方法:
1 Channel channel = ...;
2 ByteBuf buf = channel.alloc().buffer(512);
3 ....
4 channel.write(buf);
5   
6 ChannelHandlerContext ctx = ...
7 ByteBuf buf2 = ctx.alloc().buffer(512);
8 ....
9 channel.write(buf2)

一旦一个ByteBuf被写入远程节点,它会再次自动的释放进入释放到池(the pool)里。

默认的ByteBufAllocator为PooledByteBufAllocator.如果你不希望使用buffer pooling或使用你自己的allocator,你可以运用Channel.config().setAllocator(..),以及一个可供选择的 allocator,比如UnpooledByteBufAllocator。

蛋蛋为何忧伤
蛋蛋为何忧伤
翻译于 1年前

2人顶

 翻译的不错哦!

Channel API的变化

在4.0中,许多io.netty.channel包中的类都经历大量修改,因此文本上的简单搜索-替换是无法让你基于3.x的程序迁移到4.0上。这个部分会尝试将这些重大变更背后的思考过程展示出来,而不只是简单地作为展示所有变更。

翻新后的ChannelHandler接口

Upstream → Inbound, Downstream → Outbound

对于初学者来说,术语'upstream'(译者注:直译为向上游,有点像TCP/IP协议栈中从下往上,从物理层最终到达应用层这么一个流程)和'downstream'有点让人迷惑。在4.0中,只要可能,都会使用'inbound'(译者注:直译为开往内地的,相对于upstream确实更贴切,即指数据从外部网络经历层层filter到达我们的处理逻辑)和'outbound'来替换他们。

新的ChannelHandler继承层次

在3.x时代,ChannelHandler只是一个标记接口,而在ChannelUpstreamHandler、ChannelDownstreamHandler、LifeCycleAwareChannelHandler定义了具体的处理器方法。在Netty 4中,ChannelHandler将LifeCycleAwareChannelHandler接口和一堆实现辅助方法融合到了一起,具体见代码:

01 public interface ChannelHandler {
02   
03     void beforeAdd(ChannelHandlerContext ctx) throws Exception;
04     void afterAdd(ChannelHandlerContext ctx) throws Exception;
05     void beforeRemove(ChannelHandlerContext ctx) throws Exception;
06     void afterRemove(ChannelHandlerContext ctx) throws Exception;
07   
08     void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
09     void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
10     ...
11 }

下方的图表描述了这个新的类型集成层次:

fixme(原文中还没有插入此图)

燮羽
燮羽
翻译于 1年前

2人顶

 翻译的不错哦!

事件对象从ChannelHandler中消失了

在3.x时代,所有的I/O操作都会创建一个新的ChannelEvent对象。对每个读或写的操作,还会额外创建一个新的ChannelBuffer对象。由于将资源管理和buffer的池化交给了JVM,这实际上极大地简化了Netty的内部实现。但是,基于Netty开发的应用在高负载下运行时,有时会观察到GC(Garbage Collection)的压力增大或变化不定,这些问题的根源也来自于这里。

4.0通过把事件对象替换为直接与类型相对应(译者注:原文为strongly typed,但是我觉得直译为强类型不太容易理解)的方法调用,几乎完全避免了事件对象的创建。3.x中,有类似于handleUpstream()和handleDownstream()这种能够捕获所有相关类型事件的处理器方法,4.0中你将不会再看到它们的身影了。所有的事件类型现在都有各自对应的处理器方法:

01 // 3.x时代:
02 void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception;
03 void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception;
04   
05 // 4.0:
06 void channelRegistered(ChannelHandlerContext ctx) throws Exception;
07 void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
08 void channelActive(ChannelHandlerContext ctx) throws Exception;
09 void channelInactive(ChannelHandlerContext ctx) throws Exception;
10 void inboundBufferUpdated(ChannelHandlerContext ctx) throws Exception;
11   
12 void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelFuture future) throws Exception;
13 void connect(
14         ChannelHandlerContext ctx, SocketAddress remoteAddress,
15         SocketAddress localAddress, ChannelFuture future) throws Exception;
16 void disconnect(ChannelHandlerContext ctx, ChannelFuture future) throws Exception;
17 void close(ChannelHandlerContext ctx, ChannelFuture future) throws Exception;
18 void deregister(ChannelHandlerContext ctx, ChannelFuture future) throws Exception;
19 void flush(ChannelHandlerContext ctx, ChannelFuture future) throws Exception;
20 void read(ChannelHandlerContext ctx);
21 void sendFile(ChannelHandlerContext ctx, FileRegion region, ChannelPromise promise) throws Exception;

ChannelHandlerContext类也被修改来反映上述提到的变化:

1 // Before:
2 ctx.sendUpstream(evt);
3   
4 // After:
5 ctx.fireInboundBufferUpdated();

所有这些变化意味着用户无法去扩展ChannelEvent这个已经不存在的接口了。那用户要怎样才能定义他或她自己的事件类型呢,就像IdleStateEvent?4.0中的ChannelHandler有一个处理器方法叫做userEventTriggered(),它就是被设计用来满足这种特殊的用户需求。

燮羽
燮羽
翻译于 1年前

1人顶

 翻译的不错哦!

Simplified channel state model

在3.x中,当一个新的Channel被创建并连接成功,至少三个ChannelStateEvent会被触发:channelOpen、channelBound以及channelConnected。当一个Channel关闭,则对应channelDisconnected、channelUnbound以及channelClosed三个事件。

fixme

但是,触发这么多事件的意义并不那么明显。如果在一个Channel进入可读或可写的状态时通知用户,想来会更有帮助。

fixme

channelOpen、channelBoundchannelConnected被合并为channelActive。channelDisconnected、channelUnboundchannelClosed被合并为channelInactive。类似的,Channel.isBound()和Channel.isConnected()也被合并为了Channel.isActive()。

需要注意的是,channelRegistered和channelUnregistered这两个事件与channelOpen和channelClosed具有的意义是不一样的。它们(channelRegistered和channelUnregistered)是在支持Channel的动态注册、注销以及再注册时被引入的,就像下图所示:

fixme

燮羽
燮羽
翻译于 1年前

1人顶

 翻译的不错哦!

每个处理器的缓存

不像3.x那样在每次读操作都简历一个新堆里的缓存来触发上游的MessageEvent,4.0不会每次都创建新的 缓存。它直接从socket中读取数据到由用户的ChannelInboundByteHandler和ChannelInboundMessageHandler实现创建的入站缓存。

因为由上述处理器创建的入站缓存直到关联的通道关闭前都会重用,所以在上面的GC和内存带宽消耗都能保持较小。同样,当接收到的数据被销毁时用户已经完成操作,codec的实现就变得更简单和有效了

excepiton
excepiton
翻译于 1年前

2人顶

 翻译的不错哦!

在创建出站缓存时也是差不多的(不会新建)。用户的ChannelOutBoundBYteHandler和ChannelOutboundMessageHandler来操作。

不需要每条消息都有一个事件

4.0里不再有了messageReceived或writeRequested处理器方法。它们被inboundBufferUpdated和flush代替了。用户的入队一个或多个消息到一个入站(或出站)缓存同时会出发一个inboundBUfferUpdated(或flush)事件。

01 public void inboundBufferUpdated(ChannelHandlerContext ctx) {
02     Queue<MyMessage> in = ctx.inboundMessageBuffer();
03     Queue<MyNewMessage> out = ctx.nextInboundMessageBuffer();
04     for (;;) {
05         MyMessage m = in.poll();
06         if (m == null) {
07             break;
08         }
09         MyNewMessage decoded = decode(m);
10         out.add(decoded);
11     }
12     ctx.fireInboundBufferUpdated();
13 }
14  
15 public void flush(ChannelHandlerContext ctx, ChannelFuture future) {
16     Queue<MyNewMessage> in = ctx.outboundMessageBuffer();
17     Queue<MyMessage> out = ctx.nextOutboundMessageBuffer();
18     for (;;) {
19         MyNewMessage m = in.poll();
20         if (m == null) {
21             break;
22         }
23         MyMessage encoded = encode(m);
24         out.add(encoded);
25     }
26     ctx.flush(future);
27 }
作为选择,用户能够在每个单独的入站(或出站)消息中触发这样的事件来模拟老的行为,尽管相对新方法来说效率更低。
excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

消息处理器 vs. 字节处理器

在3.x里一个MessageEvent持有一个任意的对象。它能够是一个ChannelBuffer或是一个用户自定义的对象,它们都是同样对待的:

01 @Override
02 public void messageReceived(ChannelHandlerContext ctx, MessageEvent evt) {
03     Object msg = evt.getMessage();
04     if (msg instanceof ChannelBuffer) {
05         ChannelBuffer buf = (ChannelBuffer) msg;
06         ...
07     } else {
08         MyMessage myMsg = (MyMessage) msg;
09         ...
10     }
11 }
在4.0里,它们就分别对待了,因为一个处理器不再处理一个独立的消息,而是处理多种多样的消息:
01 public void inboundBufferUpdated(ChannelHandlerContext ctx) {
02     if (ctx.hasInboundByteBuffer()) {
03         ByteBuf buf = ctx.inboundByteBuffer();
04         ...
05     } else {
06         Queue<MyMessage> buf = ctx.inboundMessageBuffer();
07         for (;;) {
08             MyMessage msg = buf.poll();
09             if (buf == null) {
10                 break;
11             }
12             ...
13         }
14     }
15 }
你可能发现一个ServerChannel的处理器是一个入站缓存是Queue<Channel>的入站处理器是较为有趣的。

处理器适配器

大多数用户都发现创建和管理它的生命周期是繁琐的,因此它支持用户扩展预定义好的适配器类来使得更方便:

  • ChannelHandlerAdapter
  • ChannelStateHandlerAdapter
  • ChannelOperationHandlerAdapter
  • ChannelInboundMessageHandlerAdapter
  • ChannelInboundByteHandlerAdapter
  • ChannelOutboundMessageHandlerAdapter
  • ChannelOutboundByteHandlerAdapter
excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

明智的和不易出错的入站流量挂起

3.x有一个由Channel.setReadable(boolean)提供的不是很明显的入站流量挂起机制。它引入了在ChannelHandler之间的复杂交互操作,同时处理器由于不正确实现而很容易互相干扰。

4.0里,新的名为read()的出站操作增加了。如果你使用Channel.config().setAutoRead(false)来关闭默认的auto-read标志,Netty不会读入任何东西,直到你显式地调用read()操作。一旦你发布的read()操作完成,同时通道再次停止读,一个名为channelReadSuspended()的入站事件会触发一遍你能够重新发布另外的read()操作。你同样也可以拦截read()操作来执行更多高级的流量控制。

excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

暂停接收传入的连接

在3.x里,没有方法让一个用户告诉Netty来厅子接收传入连接,除非是阻塞I/O线程或者关闭服务器socket。在aotu-read标志没有设置的时候,4.0涉及到的read()操作就像一个普通的通道。

半关闭socket

TCP和SCTP允许用户关闭一个socket的出站流量而不用完全关闭它。这样的socket被称为“半关闭socket”,同时用户能够通过调用SocketChannel.shutdownOutput()方法来获取一个半关闭socket。如果一个远端关闭了出站通道,SocketChannel.read(..)会返回-1,这看上去并没有和一个关闭了的链接有什么区别。

3.x没有shutdownOutput()操作。同样,它总是在SocketChannel.read(..)返回-1的时候关闭链接。

要支持半关闭socket,4.0增加了SocketChannel.shutdownOutput()方法,同时用户能设置“ALLOW_HALF_CLOSURE”的ChanneOption来阻止Netty在SocketChannel.read(..)返回-1的时候自动关闭链接。

excepiton
excepiton
翻译于 1年前

2人顶

 翻译的不错哦!

灵活的I/O线程分配

在3.x里,一个Channel是由ChannelFactory创建的,同时新创建的Channel会自动注册到一个隐藏的I/O线程。4.0使用新的名为EventLoopGroup的接口来替换ChannelFactory,它由一个或多个EventLoop来构成。同样,一个新的Channel不会自动注册到EventLoopGroup,但用户可以显式调用EventLoopGroup.register()来注册。

感谢这个变化(举例来说,分离了ChannelFactory和I/O线程),用户可以注册不同的Channel实现到同一个EventLoopGroup,或者同一个Channel实现到不同的EventLoopGroup。例如,你可以运行一个NIO服务器socket,NIO UDP socket,以及虚拟机内部的通道在同一个I/O线程里。在编写一个需要最小延迟的代理服务器时这确实很有用。

excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

能够从一个已存在的jdk套接字上创建一个Channel

3.x没提供方法从已存在的jdk套接字(如java.nio.channels.SocketChannel)创建一个新的通道。现在你可以用4.0这样做了。

取消注册和重新注册一个Channel从/到一个I/O线程

一旦一个新的Channel在3.x里创建,它完全绑定到一个单一的I/O线程上,直到它底层的socket关闭。在4.0里,用户能够从I/O线程里取消注册一个Channel来完全控制它底层jdk套接字。例如,你能够利用Netty提供的高层次无阻塞I/O的优势来解决复杂的协议,然后取消注册Channel并且切换到阻塞模式来在可能的最大吞吐量下传输一个文件。当然,它能够再次注册已经取消了注册的Channel。

01 java.nio.channels.FileChannel myFile = ...;
02 java.nio.channels.SocketChannel mySocket = java.nio.channels.SocketChannel.open();
03   
04 // Perform some blocking operation here.
05 ...
06   
07 // Netty takes over.
08 SocketChannel ch = new NioSocketChannel(mySocket);
09 EventLoopGroup group = ...;
10 group.register(ch);
11 ...
12   
13 // Deregister from Netty.
14 ch.deregister().sync();
15   
16 // Perform some blocking operation here.
17 mySocket.configureBlocking(false);
18 myFile.transferFrom(mySocket, ...);
19   
20 // Register back again to another event loop group.
21 EventLoopGroup anotherGroup = ...;
22 anotherGroup.register(ch);
excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

调度任意的任务到一个I/O线程里运行

当一个Channel被注册到EventLoopGroup时,Channel实际上是注册到由EventLoopGroup管理EventLoop中的一个。EventLoop实现了java.utilconcurrent.ScheduledExecutorService接口。这意味着用户可以在一个用户通道归属的I/O线程里执行或调度一个任意的Runnable或Callable。随着新的娘好定义的线程模型的到来(稍后会介绍),它变得极其容易地编写一个线程安全的处理器。

01 public class MyHandler extends ChannelOutboundMessageHandlerAdapter {
02     ...
03     public void flush(ChannelHandlerContext ctx, ChannelFuture f) {
04         ...
05         ctx.flush(f);
06   
07         // Schedule a write timeout.
08         ctx.executor().schedule(new MyWriteTimeoutTask(), 30, TimeUnit.SECONDS);
09         ...
10     }
11 }
12   
13 public class Main {
14     public static void main(String[] args) throws Exception {
15         // Run an arbitrary task from an I/O thread.
16         Channel ch = ...;
17         ch.executor().execute(new Runnable() { ... });
18     }
19 }

简化的关闭

releaseExternalResources()不必再用了。你可以通过调用EventLoopGroup.shutdown()直接地关闭所有打开的连接同时使所有I/O线程停止,就像你使用java.util.concurrent.ExecutorService.shutdown()关闭你的线程池一样。

excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

类型安全的ChannelOptions

有两个方法来配置Netty的Channel的socket参数。第一个是明确地调用ChannelConfig的setter,例如SocketChannelConfig.setTcpNoDelay(true)。这是最为类型安全的方法。另外一个是调用ChannelConfig.setOption()方法。有时候你不得不决定在运行时的时候socket要配置什么选项,同时这个方法在这种情况下有点不切实际。然而,在3.x里它是容易出错的,因为一个用户必需用一对字符串和对象来指定选项。如果用户调用了错误的选项名或者值,他或她将会赵宇到一个ClassCastException或指定的选项甚至可能会默默地忽略了。

4.0引入了名为ChannelOption的新的类型,它提供了类型安全地访问socket选项。

01 ChannelConfig cfg = ...;
02   
03 // Before:
04 cfg.setOption("tcpNoDelay", true);
05 cfg.setOption("tcpNoDelay", 0);  // Runtime ClassCastException
06 cfg.setOption("tcpNoDelays", true); // Typo in the option name - ignored silently
07   
08 // After:
09 cfg.setOption(ChannelOption.TCP_NODELAY, true);
10 cfg.setOption(ChannelOption.TCP_NODELAY, 0); // Compile error
excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

AttributeMap

在回应用户指令里,你可以附加任意的对象到Channel和ChannelHandlerContext。一个名为AttributeMap的新接口被加入了,它被Channel和ChannelHandlerContext继承。作为替代,ChannelLocal和Channel.attachment被移除。这些属性会在他们关联的Channel被垃圾回收的同时回收。

01 public class MyHandler extends ChannelInboundMessageHandlerAdapter<MyMessage> {
02   
03     private static final AttributeKey<MyState> STATE =
04             new AttributeKey<MyState>("MyHandler.state");
05   
06     @Override
07     public void channelRegistered(ChannelHandlerContext ctx) {
08         ctx.attr(STATE).set(new MyState());
09         ctx.fireChannelRegistered();
10     }
11   
12     @Override
13     public void messageReceived(ChannelHandlerContext ctx, MyMessage msg) {
14         MyState state = ctx.attr(STATE).get();
15     }
16     ...
17 }


新的bootstrap API

bootstrap API已经重头重写,尽管它的目的还是一样;它执行需要使服务器或客户端运行的典型步骤,通常能在样板代码里找到。新的bootstrap同样采取了流畅的接口。

01 public static void main(String[] args) throws Exception {
02     // Configure the server.
03     ServerBootstrap b = new ServerBootstrap();
04     try {
05         b.group(new NioEventLoopGroup(), new NioEventLoopGroup())
06          .channel(new NioServerSocketChannel())
07          .option(ChannelOption.SO_BACKLOG, 100)
08          .localAddress(8080)
09          .childOption(ChannelOption.TCP_NODELAY, true)
10          .childHandler(new ChannelInitializer<SocketChannel>() {
11              @Override
12              public void initChannel(SocketChannel ch) throws Exception {
13                  ch.pipeline().addLast(handler1, handler2, ...);
14              }
15          });
16   
17         // Start the server.
18         ChannelFuture f = b.bind().sync();
19   
20         // Wait until the server socket is closed.
21         f.channel().closeFuture().sync();
22     } finally {
23         // Shut down all event loops to terminate all threads.
24         b.shutdown();
25     }
26 }

excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

ChannelPipelineFactory → ChannelInitializer

和你在上面的例子注意到的一样,ChannelPipelineFactory不再存在了。而是由ChannelInitializer来替换,它给予了在Channel和ChannelPipeline的配置的更多控制。

请注意,你不能自己创建一个新的ChannelPipeline。通过观察目前为止的用例报告,Netty项目队伍总结到让用户去创建自己的管道实现或者是继承默认的实现是没有好处的。因此,ChannelPipeline不再让用户创建。ChannelPipeline由Channel自动创建。

excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

ChannelFuture拆分为ChannelFuture和ChannelPromise

ChannelFuture已经被拆分为ChannelFuture和ChannelPromise了。这不仅仅是让异步操作里的生产者和消费者间的约定更明显,同样也是得在使用从链中返回的ChannelFuture更加安全,因为ChannelFuture的状态是不能改变的。

由于这个编号,一些方法现在都采用ChannelPromiser而不是ChannelFuture来改变它的状态。

excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

良好定义的线程模型

在3.x里并没有良好设计的线程模型,尽管曾经要修复线程模型在3.5的不一致性。4.0定义的一个严格的线程模型来帮助用户编写ChannelHandler而不必担心太多关于线程安全的东西。

  • Netty将不会再同步地调用ChannelHandler的方法了,除非ChannelHandler由@Shareable注解。这不会理会处理器方法的类似——入站、操作、或者是生命周期时间处理器方法。
    • 用户不再需要同步入站或出站的事件处理器方法。
    • 4.0不允许加入加入一个ChannelHandler超过一次,除非它由@Sharable注解。
  • 每个由Netty调用的ChannelHandler的方法之间的关系总是happens-before
    • 用户不用定义一个volatile字段来保存处理器的状态。
  • 用户能够在他加入一个处理器到ChannelPipeline的时候指定EventExecutor。
    • 如果有指定,ChannelHandler的处理器方法总是由自动的EventExecutor来调用
    • 如果没指定,处理器方法总是由它关联的Channel注册到的EventLoop来调用。
  • 声明到一个处理器的EventExecutor和EventLoop总是单线程的。
    • 处理器方法总是由相同线程调用。
    • 如果指定了多线程的EventExecutor或EventLoop,线程中的一个会被选择,然后选择到的线程将会被使用,直到取消注册。
    • 如果在相同管道里的两个处理器声明到不同的EventExecutor,它们会同时被调用。如果多个一个处理器去访问共享数据,用户需要注意线程安全,即使共享数据只能被相同管道里的处理器访问。
  • 加入到ChannelFuture的ChannelFutureListener总是由关联到future相关的Channel的EventLoop线程调用。
excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

不再有ExecutionHandler ——它包含到核心里

在你加入一个ChannelHandler到一个ChannelPipeline来告诉管道总是通过指定的EventExecutor调用加入的ChannelHander处理器的方法的时候,你可以指定一个EventExecutor。

1 Channel ch = ...;
2 ChannelPipeline p = ch.pipeline();
3 EventExecutor e1 = new DefaultEventExecutor(16);
4 EventExecutor e2 = new DefaultEventExecutor(8);
5   
6 p.addLast(new MyProtocolCodec());
7 p.addLast(e1, new MyDatabaseAccessingHandler());
8 p.addLast(e2, new MyHardDiskAccessingHandler());

EventExecutor是EventLoop的超类,同时也继承了ScheduledExecutorService。

fixme

编码解码器框架变化

在编码解码器框架里有实质性的内部改变,因为4.0需要一个处理器来创建和管理它的缓存(看这篇文章的每个处理器缓存部分。)然而,从用户角度来看这些变化都不是很大的。

  • 核心编码界面器类移到io.netty.handler.codec包里。
  • FrameDecoder重命名为ByteToMessageDecoder。
  • OneToOneEncoder和OneToOneDecoder由MessageToMessageEncoder和MessageToMessageDecoder替换。
  • decode(),decodeLast(),encode()的方法前面稍微改变了来支持泛型同时移除冗余参数。
excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

编码解码器嵌入器→ EmbeddedChannel

编码解码器嵌入器已经被 io.netty.channel.embedded.EmbeddedByteChannel和EmbeddedMessageChannel替换了。EmbeddedChannel允许用户对任何包含编码解码器的管道进行单元测试。

HTTP编码解码器

HTTP解码器现在在每个HTTP消息中总生成多个消息对象:

1 1       * HttpRequest / HttpResponse
2 0 - n   * HttpContent
3 1       * LastHttpContent

要看更多的细节,请到转到已更新了的HttpSnoopServer例子。如果你希望为一个单一的HTTP消息处理多个消息,你可以把HttpObjectAggregator放入管道里。HttpObjectAggregator会把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse。

传输实现的变化

下面是传输协议新加入的东西:

  • 使用NIO.2AsynchronousSocketChannel的AIO套接字传输实现。
  • OIO SCTP 传输实现
  • UDT 传输实现
excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

用例学习:移植示例Factorial

这部分粗略地展示把示例Factorial从3.0移植到4.0的步骤。示例Factorial已经移植到4.0了,它放在io.netty.example.factorial包里。请浏览示例的源代码来看下每一处的变化。

移植服务端

  1. 重写FactorialServer.run()方法来使用新的 bootstrap API。
    1. 不再有ChannelFactory了。 由你自己去实例化NioEventLoop(一个是用来接收接入的链接,另外的就用来处理接收到的链接)。
  2. 从命名FactorialServerPipelineFactory为FactorialServerInitializer。
    1. 让它继承ChannelInitializer<Channel>。
    2. 取代创建新的ChannelPipeline,通过Channel.pipeline()来获取。
  3. 让FactorialServerHandler继承sChannelInboundMessageHandlerAdapter<BigInteger>。
    1. 用channelInactive()来替换channelDisconnected()。
    2. handleUpstream()不能再使用了。
  4. 让BigIntegerDecoder继承ByteToMessageDecoder<BigInteger>。
  5. 让NumberEncoder继承MessageToByteEncoder<Number>。
    1. encode()不在返回一个缓存了。由ByteToMessageDecoder来提供填充编码好的数据到缓存里。
excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

移植客户端

大部分和移植服务端差不多,但你要在你编写一个潜在的大数据流时要多注意下。

  1. 重写FactorialClient.run()方法来使用新的bootstrap API。
  2. 重命名FactorialClientPipelineFactory为FactorialClientInitializer。
  3. 使FactorialClientHandler继承ChannelInboundMessageHandlerAdapter<BigInteger>
    1. 在这一点,你发现在4.0里没有了Channel.isWritable()或者channelInterestChanged()。作为代替,你自己来管理那些未决定的写操作。新的sendNumbers()看起来如下:
    01 private void sendNumbers() {
    02     // Do not send more than 4096 numbers.
    03     boolean finished = false;
    04     MessageBuf<Object> out = ctx.nextOutboundMessageBuffer();
    05     while (out.size() < 4096) {
    06         if (i <= count) {
    07             out.add(Integer.valueOf(i));
    08             i ++;
    09         } else {
    10             finished = true;
    11             break;
    12         }
    13     }
    14  
    15     ChannelFuture f = ctx.flush();
    16     if (!finished) {
    17         f.addListener(numberSender);
    18     }
    19 }
    20  
    21 private final ChannelFutureListener numberSender = new ChannelFutureListener() {
    22     @Override
    23     public void operationComplete(ChannelFuture future) throws Exception {
    24         if (future.isSuccess()) {
    25             sendNumbers();
    26         }
    27     }
    28 };
excepiton
excepiton
翻译于 1年前

1人顶

 翻译的不错哦!

回页面顶部发表评论 网友评论19

  • excepiton
    excepiton 发表于 2013-03-31 16:38
    @燮羽 第二段原文是:4.0 introduced pooled ByteBufs which can be allocated via the ByteBufAllocator implementations.
    pooled和ByteBuf是分开的。
    http://netty.io/wiki/new-and-noteworthy.html
  • 燮羽
    燮羽 发表于 2013-03-31 18:43

    引用来自“throwable”的评论

    @燮羽 第二段原文是:4.0 introduced pooled ByteBufs which can be allocated via the ByteBufAllocator implementations.
    pooled和ByteBuf是分开的。
    http://netty.io/wiki/new-and-noteworthy.html

    Thank you, 已更正
  • 沉寂brain
    沉寂brain 发表于 2013-04-03 22:57
  • hongtaozhang
    hongtaozhang 发表于 2013-04-04 09:56
    变化好多
  • huaye2007
    huaye2007 发表于 2013-04-04 10:21
    传输实现的变化

    下面是传输协议新加入的东西:
    • 使用NIO.2AsynchronousSocketChannel的AIO套接字传输实现。
    • OIO SCTP 传输实现
    • UDT 传输实现
    哦,用新技术比较快,不知道mina那天能用下aio
  • WO不吹牛
    WO不吹牛 发表于 2013-04-04 12:57
    有对应的方法检测到client或server端连接挂了(非正常退出)?
  • wen66
    wen66 发表于 2013-04-04 13:35
    "
    每个处理器的缓存
    不像3.x那样在每次读操作都简历一个新堆里的缓存来触发上游的MessageEvent,"

    这里的简历, 应该是建立吧.
  • kut
    kut 发表于 2013-04-04 15:00
    又要大改了……
  • 华夏心理
    华夏心理 发表于 2013-04-04 16:00
    io 框架更改挺大的,特别是 jdk7 发布后
  • rabee
    rabee 发表于 2013-04-04 17:31
    新的框架,性能怎么样啊?
  • 杨伟荣
    杨伟荣 来自 Android 发表于 2013-04-04 19:18
    不知道性能改进否
  • 石头哥哥
    石头哥哥 发表于 2013-04-06 23:31
    个人认为还是最好看英文的原版较好
  • 七仔-飞扬
    七仔-飞扬 发表于 2013-05-20 08:52

    引用来自“石头哥哥”的评论

    个人认为还是最好看英文的原版较好

    看的比较吃力啊
  • Anterior
    Anterior 发表于 2013-06-11 00:33
    楼主v5
  • wqq686
    wqq686 发表于 2013-08-17 07:40
    mark
  • Gelopa
    Gelopa 发表于 2013-09-07 14:48
    @媛媛小译 如果有时间 翻译点这种文章 , 对你也是很有帮助的
  • 媛媛小译
    媛媛小译 发表于 2013-09-07 15:07

    引用来自“Gelopa”的评论

    @媛媛小译 如果有时间 翻译点这种文章 , 对你也是很有帮助的

    之前经常翻译啊 最近好懒。。
  • Gelopa
    Gelopa 发表于 2013-09-07 15:45

    引用来自“媛媛小译”的评论

    引用来自“Gelopa”的评论

    @媛媛小译 如果有时间 翻译点这种文章 , 对你也是很有帮助的

    之前经常翻译啊 最近好懒。。
    哦,也许是你累了。累了就少写点代码,多看看这种文档,多看几次 翻译的时候就顺了
  • 爱coding
    爱coding 发表于 2014-03-24 18:16
    楼主分析的不错,最近项目在用,感谢分享。。。

阅读更多
个人分类: 程序设计/JEE/Service
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭