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

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

既然是分布式的,自然要分多个服务。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即可。果然简洁优雅。

[转载] Netty教程的更多相关文章

  1. [转载] Netty

    转载自http://lippeng.iteye.com/blog/1907279 Netty是什么? 本质:JBoss做的一个Jar包 目的:快速开发高性能.高可靠性的网络服务器和客户端程序 优点:提 ...

  2. [转载] Netty源码分析

    转载自http://blog.csdn.net/kobejayandy/article/details/11836813 Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高 ...

  3. (转载)Ant教程

    ant教程(一) 写在所有之前 为了减少阅读的厌烦,我会使用尽量少的文字,尽量明白的表达我的意思,尽我所能吧.作为一个学习者,在我的文章中会存在各种问题,希望热心人指正.目录大概是这样 ant教程 ( ...

  4. Java NIO框架Netty教程(一) – Hello Netty

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

  5. [转载] FreeMarker教程

    转载自http://www.blogjava.net/freeman1984/archive/2010/11/04/337239.html FreeMarker是一个模板引擎,一个基于模板生成文本输出 ...

  6. Netty教程

    Netty是一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序. Netty是一个NIO客户端.服务端框架.允许快速简单 ...

  7. [转载]VIM 教程:Learn Vim Progressively

    文章来源:http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/   Learn Vim Progressively   TL ...

  8. [转载]CSS教程:实例讲解定位Position

    http://www.missyuan.com/thread-395406-1-1.html 1. position:static 所有元素的默认定位都是:position:static,这意味着元素 ...

  9. 转载-smarty教程(基本语法)

    转自:http://hi.baidu.com/qxxgvpdtzhbckpr/item/681049160d7be60db98a1aec 1.smarty的配置      首先,使用smarty第一件 ...

随机推荐

  1. C# 如何添加Word文本和图片超链接

    超链接简单来讲就是内容链接,通过设置超链接可以实现对象与网页.站点之间的连接.链接目标可以是网页.图片.邮件地址.文件夹或者是应用程序.设置链接的对象可以是文本或者图片. 在以下内容中,我将介绍如何用 ...

  2. Jquery地图热点效果-鼠标经过弹出提示信息

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...

  3. 怎样让PDM图形列表显示name和code等需要的信息

    1. 工具(TOOLS)-〉显示参数设置(DISPLAY PREFERENCES) 2. 在弹出来的框中选中Content-〉Table 3. 点右下角那个Advanced 按钮 4. 在弹出的框个选 ...

  4. SpringMVC拦截器Interceptor

    SpringMVC拦截器(Interceptor)实现对每一个请求处理前后进行相关的业务处理,类似与servlet中的Filter. SpringMVC 中的Interceptor 拦截请求是通过Ha ...

  5. 从template到DOM(Vue.js源码角度看内部运行机制)

    写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(https://github.com/answershuto/learnVue)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些 ...

  6. win10 uwp 圆角按钮

    本文讲的是如何做圆角按钮,我们在UWP本来的按钮都是矩形,圆角Radius没有,所以本文就用简单方法去做圆角按钮. 我们按钮需要圆角,而自带没有,其实做一个很简单,把原来的按钮变为背景透明,然后使用矩 ...

  7. 使用Java 8中的Stream

    Stream是Java 8 提供的高效操作集合类(Collection)数据的API. 1. 从Iterator到Stream 有一个字符串的list,要统计其中长度大于7的字符串的数量,用迭代来实现 ...

  8. Spring 笔记总结

    1.Spring框架的核心是提供一个容器(BeanFactory 或 ApplicationContext),提供以下功能: 1)创建和销毁组件对象,类似"工厂类" 2)采用不同的 ...

  9. MySQL常用存储引擎

    MySQL存储引擎主要有两大类: 1. 事务安全表:InnoDB.BDB. 2. 非事务安全表:MyISAM.MEMORY.MERGE.EXAMPLE.NDB Cluster.ARCHIVE.CSV. ...

  10. LeetCode 119. Pascal's Triangle II (杨辉三角之二)

    Given an index k, return the kth row of the Pascal's triangle. For example, given k = 3,Return [1,3, ...