写点什么

【Netty】「萌新入门」(一)Hello, World!

作者:sidiot
  • 2023-06-12
    浙江
  • 本文字数:3797 字

    阅读完需:约 12 分钟

前言


本篇博文是《从 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! 的所有内容了,希望本篇博文对大家有所帮助!


参考:


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

💖 我是 𝓼𝓲𝓭𝓲𝓸𝓽,期待你的关注;

👍 创作不易,请多多支持;

🔥 系列专栏:探索 Netty:源码解析与应用案例分享

发布于: 刚刚阅读数: 2
用户头像

sidiot

关注

还未添加个人签名 2023-06-04 加入

还未添加个人简介

评论

发布
暂无评论
【Netty】「萌新入门」(一)Hello, World!_Java_sidiot_InfoQ写作社区