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

简介: 《跟闪电侠学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

相关文章
|
11月前
|
Java 关系型数据库 数据库连接
Javaweb之Mybatis入门程序的详细解析
本文详细介绍了一个MyBatis入门程序的创建过程,从环境准备、Maven项目创建、MyBatis配置、实体类和Mapper接口的定义,到工具类和测试类的编写。通过这个示例,读者可以了解MyBatis的基本使用方法,并在实际项目中应用这些知识。
291 11
|
数据采集 搜索推荐 API
小红书笔记详情 API 接口:获取、应用与收益全解析
小红书(RED)是国内领先的生活方式分享平台,汇聚大量用户生成内容(UGC),尤以“种草”笔记闻名。小红书笔记详情API接口为开发者提供了获取笔记详细信息的强大工具,包括标题、内容、图片、点赞数等。通过注册开放平台账号、申请API权限并调用接口,开发者可构建内容分析工具、笔记推荐系统、数据爬虫等应用,提升用户体验和运营效率,创造新的商业模式。本文将详细介绍该API的获取、应用及潜在收益,并附上代码示例。
1871 13
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
979 37
|
SQL 安全 Windows
SQL安装程序规则错误解析与解决方案
在安装SQL Server时,用户可能会遇到安装程序规则错误的问题,这些错误通常与系统配置、权限设置、依赖项缺失或版本不兼容等因素有关
1111 2
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
205 0
|
XML Java 数据格式
手动开发-简单的Spring基于XML配置的程序--源码解析
手动开发-简单的Spring基于XML配置的程序--源码解析
312 0
|
设计模式 存储 算法
PHP中的设计模式:策略模式的深入解析与应用在软件开发的浩瀚海洋中,PHP以其独特的魅力和强大的功能吸引了无数开发者。作为一门历史悠久且广泛应用的编程语言,PHP不仅拥有丰富的内置函数和扩展库,还支持面向对象编程(OOP),为开发者提供了灵活而强大的工具集。在PHP的众多特性中,设计模式的应用尤为引人注目,它们如同精雕细琢的宝石,镶嵌在代码的肌理之中,让程序更加优雅、高效且易于维护。今天,我们就来深入探讨PHP中使用频率颇高的一种设计模式——策略模式。
本文旨在深入探讨PHP中的策略模式,从定义到实现,再到应用场景,全面剖析其在PHP编程中的应用价值。策略模式作为一种行为型设计模式,允许在运行时根据不同情况选择不同的算法或行为,极大地提高了代码的灵活性和可维护性。通过实例分析,本文将展示如何在PHP项目中有效利用策略模式来解决实际问题,并提升代码质量。
|
开发者 API 开发框架
Xamarin 在教育应用开发中的应用:从课程笔记到互动测验,全面解析使用Xamarin.Forms构建多功能教育平台的技术细节与实战示例
【8月更文挑战第31天】Xamarin 作为一款强大的跨平台移动开发框架,在教育应用开发中展现了巨大潜力。它允许开发者使用单一的 C# 代码库构建 iOS、Android 和 Windows 应用,确保不同设备上的一致体验。Xamarin 提供广泛的 API 支持,便于访问摄像头、GPS 等原生功能。本文通过一个简单的教育应用示例——课程笔记和测验功能,展示了 Xamarin 在实际开发中的应用过程。从定义用户界面到实现保存笔记和检查答案的逻辑,Xamarin 展现了其在教育应用开发中的高效性和灵活性。
224 0
|
存储 网络协议 Java
【Netty 神奇之旅】Java NIO 基础全解析:从零开始玩转高效网络编程!
【8月更文挑战第24天】本文介绍了Java NIO,一种非阻塞I/O模型,极大提升了Java应用程序在网络通信中的性能。核心组件包括Buffer、Channel、Selector和SocketChannel。通过示例代码展示了如何使用Java NIO进行服务器与客户端通信。此外,还介绍了基于Java NIO的高性能网络框架Netty,以及如何用Netty构建TCP服务器和客户端。熟悉这些技术和概念对于开发高并发网络应用至关重要。
400 0

推荐镜像

更多
  • DNS