前言

本篇博文是《从0到1学习 Netty》中入门系列的第一篇博文,次要内容是构建 Netty 的第一个程序,Hello World!,往期系列文章请拜访博主的 Netty 专栏,博文中的所有代码全副收集在博主的 GitHub 仓库中;

概述

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

Netty 是一个异步的、基于事件驱动的网络应用框架,用于疾速开发可保护、高性能的网络服务器和客户端。

须要留神的是,Netty 中的异步操作是通过多路复用来实现的。在 Java NIO 中,能够应用一个线程同时解决多个通道的读写操作,这就是所谓的多路复用。Netty 正是基于 Java NIO 实现的,因而它也采纳了多路复用技术来实现异步操作。

尽管 Netty 的异步操作并没有实现真正意义上的异步 I/O,然而它的性能体现十分杰出,可能很好地满足大部分利用的需要。同时,Netty 的编程模型比纯 NIO 更加简洁易用,能够帮忙开发者疾速构建高性能、牢靠的网络应用程序。

接下来,通过应用 Netty 构建服务端与客户端,实现第一个 Netty demo。

服务端

1、首先,通过创立一个 ServerBootstrap 实例来启动服务器,它会组装和配置 Netty 组件,并且启动服务器:

new ServerBootstrap()

2、应用 NioEventLoopGroup 类型的事件循环组作为 BossEventLoopWorkerEventLoopBossEventLoop 治理连贯申请,WorkerEventLoop 治理连贯的 I/O 数据处理:

group(new NioEventLoopGroup())

不相熟的读者能够看到博主的上一篇博文 NIO-多线程优化,博文里具体介绍了 Boss 与 Worker 的用法;

3、抉择了 NioServerSocketChannel 来实现服务器端监听 Socket 的 Channel,示意该服务器将应用 NIO 形式进行网络通信:

channel(NioServerSocketChannel.class)

4、childHandler() 办法设置了一个初始化器,它将在每个新连贯被承受时调用。该办法中的匿名外部类 ChannelInitializer 将为每个新连贯增加一个新的管道 pipeline,并将 initChannel() 办法回调给这个新的管道:

childHandler(      // channel 初始化,负责增加别的 handler      new ChannelInitializer<NioSocketChannel>() {          @Override          protected void initChannel(NioSocketChannel nsc) throws Exception {              ...        }      }  )

5、向 initChannel() 办法中增加两个 handler,别离是 StringDecoderChannelInboundHandlerAdapter,其中 StringDecoder 是 Netty 提供的一个具体的音讯解码器,将字节流转换成字符串;ChannelInboundHandlerAdapter 则是自定义的音讯处理器,当有音讯达到时,将其打印进去:

@Override  protected void initChannel(NioSocketChannel nsc) throws Exception {      nsc.pipeline().addLast(new StringDecoder());      nsc.pipeline().addLast(new ChannelInboundHandlerAdapter() {          @Override          public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {              System.out.println(msg);          }      });  }

6、调用 bind() 办法,监听特定的端口号 7999,以开始承受来自客户端的连贯申请:

bind(7999);

7、残缺代码:

public class HelloServer {    public static void main(String[] args) {        // 1. 启动器,负责组装 netty 组件并启动服务器        new ServerBootstrap()                // 2. BossEventLoop, WorkerEventLoop(selector, thread)                .group(new NioEventLoopGroup())                // 3. 抉择服务器的 ServerSocketChannel 实现                .channel(NioServerSocketChannel.class)                // 4. 配置 worker(child) 能执行的操作 handler                .childHandler(                        // 5. channel 初始化,负责增加别的 handler                        new ChannelInitializer<NioSocketChannel>() {                            @Override                            protected void initChannel(NioSocketChannel nsc) throws Exception {                                // 6. 增加具体的 handler                                nsc.pipeline().addLast(new StringDecoder());                                nsc.pipeline().addLast(new ChannelInboundHandlerAdapter() {                                    @Override                                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {                                        System.out.println(msg);                                    }                                });                            }                        }                )                // 7. 绑定监听端口                .bind(7999);    }}

客户端

1、创立 Bootstrap 实例,它是 Netty 库中用于创立客户端的启动类:

new ServerBootstrap()

2、增加一个 NioEventLoopGroup 实例作为 EventLoop,用于解决 I/O 操作:

group(new NioEventLoopGroup())

3、设置 channel 类型为 NioSocketChannel,示意应用 NIO 进行网络通信:

channel(NioServerSocketChannel.class)

4、增加一个 ChannelInitializer 实例,在连贯建设后对 channel 进行初始化。这里增加了一个 StringEncoder,用于将字符串编码成字节流以进行传输:

handler(new ChannelInitializer<NioSocketChannel>() {    @Override    protected void initChannel(NioSocketChannel nsc) throws Exception {        nsc.pipeline().addLast(new StringEncoder());    }})

5、调用 connect 办法,与服务器建设连贯,并返回一个 ChannelFuture 实例,通过调用 sync 办法期待连贯胜利:

.connect(new InetSocketAddress(7999))  .sync()

6、获取连贯胜利后的 channel 实例,通过 writeAndFlush 办法向服务器发送字符串音讯:

.channel()  .writeAndFlush("Hello World! --sidiot.");

7、残缺代码:

public class HelloClient {    public static void main(String[] args) throws InterruptedException {        // 1. 启动客户端        new Bootstrap()                // 2. 增加 EventLoop                .group(new NioEventLoopGroup())                // 3. 抉择 channel 实现                .channel(NioSocketChannel.class)                // 4. 增加 handler                .handler(new ChannelInitializer<NioSocketChannel>() {                    @Override                    protected void initChannel(NioSocketChannel nsc) throws Exception {                        nsc.pipeline().addLast(new StringEncoder());                    }                })                // 5. 连贯到服务器                .connect(new InetSocketAddress(7999))                .sync()                .channel()                // 6. 向服务器发送数据                .writeAndFlush("Hello World! --sidiot.");    }}

运行后果:

Hello World! --sidiot.

流程剖析

服务器后行启动,步骤1到步骤5按代码程序进行执行,然而 initChannel 须要在建设连贯后才会被执行,因而步骤6是绑定监听端口 bind(7999)

再启动客户端,步骤7到步骤11按代码程序进行执行,步骤12在连贯建设后对 channel 进行初始化,步骤13中 sync() 办法期待连贯胜利。

而后进行步骤14,客户端向服务端发送数据,在这个过程中,数据会先通过步骤15进行加密,再发送至服务端,由步骤16中相应的 eventLoop 进行解决,步骤17将接管到的数据进行解密,最初是步骤18,执行 read 办法,打印数据。

在这些步骤中,用到了 channelhandlereventLoop 等组件,接下来稍作解释:

  • channel:数据的传输通道;
  • handler:数据的解决工序:

    • handler 分为 InboundOutbound 两类:Inbound 示意入站,Outbound 示意出站;
    • pipeline 代表了 Netty 中的一个解决链,负责公布事件(读、读取实现等)流传给每个 handlerhandler 对本人感兴趣的事件进行解决(重写了相应事件处理办法),每个 handler 都会按程序顺次解决传入和传出的数据流,直到最初一个实现其工作并将响应发送回客户端。
  • eventLoop:解决数据的工人:

    • eventLoop 能够治理多个 channel 的 I/O 操作,并且一旦 eventLoop 负责了某个 channel,就会将其与这个 channel进行绑定,当前该 channel 中的 I/O 操作都由该 eventLoop 负责;
    • eventLoop 既能够执行 I/O 操作,也能够进行工作解决,每个 eventLoop 有本人的工作队列,队列里能够堆放多个 channel 的待处理工作,工作分为一般工作、定时工作;
    • eventLoop 依照 pipeline 程序,顺次依照 handler 的布局(代码)解决数据,能够为每个 handler 指定不同的 eventLoop

后记

以上就是 Hello, World! 的所有内容了,心愿本篇博文对大家有所帮忙!

参考:

  • Netty API reference;
  • 黑马程序员Netty全套教程 ;

上篇精讲:「NIO」(五)多线程优化

我是 ,期待你的关注;

创作不易,请多多反对;

系列专栏:摸索 Netty:源码解析与利用案例分享