[转载] Netty教程

转载自http://blog.csdn.net/kobejayandy/article/details/11493717

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

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

[java] view plaincopy
 
  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. }  
[java] view plaincopy
 
  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 {
 
    privatestaticfinal long serialVersionUID = 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) {
            throw new ChannelException("Failed to create a selector.", t);
        }
 
        // Start the worker thread with the new Selector.
        boolean success = false;
        try {
            DeadLockProofWorker.start(executor, new ThreadRenamingRunnable(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.
            }
        }
        assert selector != null && selector.isOpen();
    }

 

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

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

posted on 2015-07-01 00:04  追求卓越,厚积薄发  阅读(305)  评论(0编辑  收藏  举报

导航