《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析(一)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析(一)

引言

上一节[[《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty]] 中介绍了Netty的入门程序,本节如标题所言将会一步步分析入门程序的代码含义。

思维导图

image.png

服务端最简化代码


public static void main(String[] args) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        NioEventLoopGroup boos = new NioEventLoopGroup();
        NioEventLoopGroup worker = new NioEventLoopGroup();
        serverBootstrap
      .group(boos, worker)
      .channel(NioServerSocketChannel.class)
      .childHandler(new ChannelInitializer<NioSocketChannel>() {
        protected void initChannel(NioSocketChannel ch) {
          ch.pipeline().addLast(new StringDecoder());
          ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
            @Override
            protected void channelRead0(ChannelHandlerContext ctx, String msg) {
              System.out.println(msg);
            }
          });
        }
      })
      .bind(8000);
    }

两个NioEventLoopGroup

服务端一上来先构建两个对象NioEventLoopGroup,这两个对象将直接决定Netty启动之后的工作模式,在这个案例中boos和JDK的NIO编程一样负责进行新连接的“轮询”,他会定期检查客户端是否已经准备好可以接入。worker则负责处理boss获取到的连接,当检查连接有数据可以读写的时候就进行数据处理。


NioEventLoopGroup boos = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();

那么应该如何理解?其实这两个Group对象简单的看成是线程池即可,和JDBC的线程池没什么区别。通过阅读源码可以知道,bossGroup只用了一个线程来处理远程客户端的连接,workerGroup 拥有的线程数默认为2倍的cpu核心数

那么这两个线程池是如何配合的?boss和worker的工作模式和我们平时上班,老板接活员工干活的模式是类似的。bossGroup负责接待,再转交给workerGroup来处理具体的业务

整体概念上贴合NIO的设计思路,不过它要做的更好。

ServerBootstrap


ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.
  .xxx()
  .xxx()

服务端引导类是ServerBootstrap,引导器指的是引导开发者更方便快速的启动Netty服务端/客户端,

这里使用了比较经典的建造者设计模式。

group设置


.group(boos, worker)

group方法绑定boos和work使其各司其职,这个操作可以看作是绑定线程池。

注意gorup方法一旦确定就意味着Netty的线程模型被固定了,中途不允许切换,整个运行过程Netty会按照代码实现计算的线程数提供服务。

下面是group的api注释:

Set the EventLoopGroup for the parent (acceptor) and the child (client). These EventLoopGroup's are used to handle all the events and IO for ServerChannel and Channel's.

机翻过来就是:为父(acceptor)和子(client)设置EventLoopGroup。这些EventLoopGroup是用来处理ServerChannelChannel的所有事件和IO的。注意这里的 Channel's 是Netty中的概念,初学的时候可以简单的类比为BIO编程的Socket套接字。

channel


.channel(NioServerSocketChannel.class)

设置底层编程模型或者说底层通信模式,一旦设置中途不允许更改。所谓的底层编程模型,其实就是JDK的BIO,NIO模型(Netty摈弃了JDK的AIO编程模型),除此之外Netty还提供了自己编写的Epoll模型,当然日常工作中是用最多的还是NIO模型。

childHandler

childHandler方法主要作用是初始化和定义处理链来处理请求处理的细节。在案例代码当中我们添加了Netty提供的字符串解码handler(StringDecoder)和由Netty实现的SimpleChannelInboundHandler简易脚手架,脚手架中自定义的处理逻辑为打印客户端发送的请求数据。


.childHandler(new ChannelInitializer<NioSocketChannel>() {
        protected void initChannel(NioSocketChannel ch) {
          ch.pipeline().addLast(new StringDecoder());
          ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
            @Override
            protected void channelRead0(ChannelHandlerContext ctx, String msg) {
              System.out.println(msg);
            }
          });
        }
      })

Handler负责处理一个I/O事件或拦截一个I/O操作,处理完成将其转发给其ChannelPipeline中的下一个处理Handler,以此形成经典的处理链条。 比如案例里面StringDecoder解码处理数据之后将会交给SimpleChannelInboundHandlerchannelRead0方法,该方法中将解码读取到的数据打印到控制台。

借助pipeline,我们可以定义连接收到请求后续的数据读写细节和处理逻辑。为了方便理解,这里可以认为NIoSocketChanne 对应BIO编程模型的Socket套接字 ,NioServerSocketChannel对应BIO编程模型的ServerSocket

bind


.bind(8000)

bind操作是一个异步方法,它会返回ChannelFuture,服务端编码中可以通过添加监听器方式,自定义在Netty服务端启动回调通知之后的下一步处理逻辑,当然也可以完全不关心它是否启动继续往下执行其他业务代码的处理。

Netty的 ChannelFuture 类注释中有一个简单直观的例子介绍ChannelFuture的使用。


// GOOD
  Bootstrap b = ...;
  // Configure the connect timeout option.
  b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
  ChannelFuture f = b.connect(...);
  f.awaitUninterruptibly();
  // Now we are sure the future is completed.
  assert f.isDone();
  if (f.isCancelled()) {
      // Connection attempt cancelled by user
  } else if (!f.isSuccess()) {
      f.cause().printStackTrace();
  } else {
      // Connection established successfully
  }

这个过程类似外面员把外卖送到指定地点之后打电话通知我们。

实践:服务端启动失败自动递增端口号重新绑定端口

第一个案例是通过服务端启动失败自动递增端口号重新绑定端口。

需求

服务端启动必须要关心的问题是指定的端口被占用导致启动失败的处理,这里的代码实践是利用Netty的API完成服务端端口在检测到端口被占用的时候自动+1重试绑定直到所有的端口耗尽。

思路

实现代码如下:


public class NettyServerStart {  
    public static void main(String[] args) {  
        ServerBootstrap serverBootstrap = new ServerBootstrap();  
        NioEventLoopGroup boss = new NioEventLoopGroup();  
        NioEventLoopGroup worker = new NioEventLoopGroup();  
        int port = 10022;  
        serverBootstrap  
                .group(boss, worker)  
                .channel(NioServerSocketChannel.class)  
                .handler(new ChannelInitializer() {  
                    @Override  
                    protected void initChannel(Channel ch) throws Exception {  
                        // 指定服务端启动过程的一些逻辑  
                        System.out.println("服务端启动当中");  
                    }  
                })  
                // 指定自定义属性,客户端可以根据此属性进行一些判断处理  
                // 可以看作给Channel维护一个Map属性,这里的channel是服务端  
                // 允许指定一个新创建的通道的初始属性。如果该值为空,指定键的属性将被删除。  
                .attr(AttributeKey.newInstance("hello"), "hello world")  
                // 给每个连接指定自定义属性,Channel 进行属性指定等  
                // 用给定的值在每个 子通道 上设置特定的AttributeKey。如果该值为空,则AttributeKey将被删除。  
                // 区别是是否是 子channel,子Channel代表给客户端的连接设置  
                .childAttr(AttributeKey.newInstance("childAttr"), "childAttr")  
                // 客户端的 Channel 设置TCP 参数  
                // so_backlog 临时存放已完成三次握手的请求队列的最大长度,如果频繁连接可以调大此参数  
                .option(ChannelOption.SO_BACKLOG, 1024)  
                // 给每个连接设置TCP参数  
                // tcp的心跳检测,true为开启  
                .childOption(ChannelOption.SO_KEEPALIVE, true)  
                // nagle 算法开关,实时性要求高就关闭  
                .childOption(ChannelOption.TCP_NODELAY, true)  
                .childHandler(new ChannelInitializer<NioSocketChannel>() {  
                    @Override  
                    protected void initChannel(NioSocketChannel ch) throws Exception {  
                        ch.pipeline().addLast(new StringDecoder());  
                        ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {  
                            @Override  
                            protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {  
                                System.err.println(msg);  
                            }  
                        });  
                    }  
                });  
        bind(serverBootstrap, port);  
    }  
    /**  
     * 自动绑定递增端口  
     * @param serverBootstrap  
     * @param port  
     */  
    public static void bind(ServerBootstrap serverBootstrap, int port){  
        serverBootstrap.bind(port).addListener(future -> {  
            if(future.isSuccess()){  
                System.out.println("端口绑定成功");  
                System.out.println("绑定端口"+ port +"成功");  
            }else{  
                System.out.println("端口绑定失败");  
                bind(serverBootstrap, port+1);  
            }  
        });  
    }  
}

服务端API其他方法

详细介绍和解释API个人认为意义不大,这里仅仅对于常用的API进行解释:

  • handler():代表服务端启动过程当中的逻辑,服务端启动代码中基本很少使用。
  • childHandler():用于指定每个新连接数据的读写处理逻辑,类似流水线上安排每一道工序的处理细节。
  • attr():底层实际上就是一个Map,用户可以为服务端Channel指定属性,可以通过自定义属性实现一些特殊业务。(不推荐这样做,会导致业务代码和Netty高度耦合)
  • childAttr():为每一个连接指定属性,可以使用channel.attr()取出属性。
  • option():可以为Channel配置TCP参数。
  • so_backlog:表示临时存放三次握手请求队列(syns_queue:半连接队列)的最大容量,如果连接频繁处理新连接变慢,适当扩大此参数。这个参数的主要作用是预防“DOS”攻击占用。
  • childOption():为每个连接设置TCP参数。
  • TCP_NODELAY:是否开启Nagle算法,如果需要减少网络交互次数建议开启,要求高实时性建议关闭。
  • SO_KEEPALIVE:TCP底层心跳机制。

客户端最简化代码

客户端的启动代码如下。


public static void main(String[] args) throws InterruptedException {  
    Bootstrap bootstrap = new Bootstrap();  
    NioEventLoopGroup eventExecutors = new NioEventLoopGroup();  
    // 引导器引导启动  
    bootstrap.group(eventExecutors)  
            .channel(NioSocketChannel.class)  
            .handler(new ChannelInitializer<Channel>() {  
                @Override  
                protected void initChannel(Channel channel) throws Exception {  
                    channel.pipeline().addLast(new StringEncoder());  
                }  
            });  
    // 建立通道  
    Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();  
    while (true){  
        channel.writeAndFlush(new Date() + " Hello world");  
        Thread.sleep(2000);  
    }  
}

客户端代码最主要的三个关注点是:线程模型IO模型IO业务处理逻辑,其他代码和服务端的启动比较类似。这里依旧是从上往下一条条分析代码。

《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析(二)https://developer.aliyun.com/article/1395287

相关文章
|
17天前
|
消息中间件 编解码 网络协议
Netty从入门到精通:高性能网络编程的进阶之路
【11月更文挑战第17天】Netty是一个基于Java NIO(Non-blocking I/O)的高性能、异步事件驱动的网络应用框架。使用Netty,开发者可以快速、高效地开发可扩展的网络服务器和客户端程序。本文将带您从Netty的背景、业务场景、功能点、解决问题的关键、底层原理实现,到编写一个详细的Java示例,全面了解Netty,帮助您从入门到精通。
60 0
|
1月前
|
存储 弹性计算 NoSQL
"从入门到实践,全方位解析云服务器ECS的秘密——手把手教你轻松驾驭阿里云的强大计算力!"
【10月更文挑战第23天】云服务器ECS(Elastic Compute Service)是阿里云提供的基础云计算服务,允许用户在云端租用和管理虚拟服务器。ECS具有弹性伸缩、按需付费、简单易用等特点,适用于网站托管、数据库部署、大数据分析等多种场景。本文介绍ECS的基本概念、使用场景及快速上手指南。
72 3
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
前端大模型入门(三):编码(Tokenizer)和嵌入(Embedding)解析 - llm的输入
本文介绍了大规模语言模型(LLM)中的两个核心概念:Tokenizer和Embedding。Tokenizer将文本转换为模型可处理的数字ID,而Embedding则将这些ID转化为能捕捉语义关系的稠密向量。文章通过具体示例和代码展示了两者的实现方法,帮助读者理解其基本原理和应用场景。
280 1
|
2月前
|
JSON JavaScript 前端开发
深入解析ESLint配置:从入门到精通的全方位指南,精细调优你的代码质量保障工具
深入解析ESLint配置:从入门到精通的全方位指南,精细调优你的代码质量保障工具
89 0
|
存储 缓存 NoSQL
跟着源码学IM(十一):一套基于Netty的分布式高可用IM详细设计与实现(有源码)
本文将要分享的是如何从零实现一套基于Netty框架的分布式高可用IM系统,它将支持长连接网关管理、单聊、群聊、聊天记录查询、离线消息存储、消息推送、心跳、分布式唯一ID、红包、消息同步等功能,并且还支持集群部署。
13504 1
|
7月前
|
消息中间件 Oracle Dubbo
Netty 源码共读(一)如何阅读JDK下sun包的源码
Netty 源码共读(一)如何阅读JDK下sun包的源码
132 1
|
NoSQL Java Redis
跟着源码学IM(十二):基于Netty打造一款高性能的IM即时通讯程序
关于Netty网络框架的内容,前面已经讲了两个章节,但总归来说难以真正掌握,毕竟只是对其中一个个组件进行讲解,很难让诸位将其串起来形成一条线,所以本章中则会结合实战案例,对Netty进行更深层次的学习与掌握,实战案例也并不难,一个非常朴素的IM聊天程序。 原本打算做个多人斗地主练习程序,但那需要织入过多的业务逻辑,因此一方面会带来不必要的理解难度,让案例更为复杂化,另一方面代码量也会偏多,所以最终依旧选择实现基本的IM聊天程序,既简单,又能加深对Netty的理解。
167 1
|
7月前
|
编解码 前端开发 网络协议
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
166 0
|
7月前
|
编解码 安全 前端开发
Netty Review - StringEncoder字符串编码器和StringDecoder 解码器的使用与源码解读
Netty Review - StringEncoder字符串编码器和StringDecoder 解码器的使用与源码解读
256 0
|
分布式计算 网络协议 前端开发
【Netty底层数据交互源码】
【Netty底层数据交互源码】