Netty学习(二)-- 概述和初体验

1、概述

1.1、什么是 Netty?

Netty 是由 Trustin Lee 提供的一个 Java 开源框架,现为 GitHub 上的独立项目。Netty 是一个基于 NIO 的客户、服务器端的编程框架。

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

1.3、Netty 的地位

Netty 在 Java 网络应用框架中的地位就好比:Spring 框架在 JavaEE 开发中的地位。

以下框架都是用了 Netty ,它们都有网络通信需求!

  • Cassandra - NoSQL 数据库
  • Spark - 大数据分布式计算框架
  • Hadoop - 大数据分布式存储框架
  • RockMQ - 阿里开源的消息队列
  • ElasticSearch - 搜索引擎
  • gRPC - rpc 框架
  • Dubbo - rpc 框架
  • Spring 5.x - Fulx api 完全抛弃了 Tomcat,使用 Netty 作为服务端
  • Zookeeper - 分布式协调框架

1.4、Netty 的优势

  • Netty 对比 NIO(工作量大,BUG多)
    • 需要自己构建协议
    • 解决 TCP 传输问题,如黏包、半包
    • epoll 空轮询导致 CPU 100%
    • 对 API 进行增强,使之更易使用。如 FastThreadLocal => ThreadLocal、ByteBuf=>ByteBuffer。
  • Netty 对比 其他网络应用框架
    • Mina 由 Apache 维护。Netty API 更简洁,文档更优秀。

2、Hello World

2.1、初体验

开发一个简单的服务器和客户端

  • 客户端向服务器发送 hello,world
  • 服务器仅接收,不返回

2.2、导入依赖

1、netty 依赖

<dependency>
  <groupId>io.netty</groupId>
  <artifactId>netty-all</artifactId>
  <version>4.0.42.Final</version>
</dependency>

2、日志依赖:

<!-- lombok,IDEA 需要配置插件 --> 
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>
<!-- slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.7</version>
</dependency>
<!-- slf4j 到 log4j 的实现 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.9.1</version>
</dependency>

3、log4j2.xml 文件配置

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > debugModeOpen > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="INFO" monitorInterval="30">
    <!--先定义所有的appender-->
    <appenders>
        <!--这个输出控制台的配置-->
        <console name="Console" target="SYSTEM_OUT">
                <!--输出日志的格式-->
            	<!-- <PatternLayout pattern="[%d{MM-dd HH:mm:ss:SSS}] [%p] - %l - %m%n"/> -->
                <PatternLayout pattern="%date{HH:mm:ss} [%-5level] [%thread] %logger{17} - %m%n"/>
        </console>

        <!--日志大于50M时,自动备份存档,生成新的文件-->
<!--        <RollingFile name="logfile" fileName="${sys:catalina.home}/logs/system.log" filePattern="${sys:catalina.home}/logs/$${date:yyyy-MM}/system-%d{yyyy-MM-dd}-%i.log">-->
        <RollingFile name="logfile" fileName="G:/Typora/Netty/Netty/logs/system.log" filePattern="G:/Typora/Netty/Netty/logs/$${date:yyyy-MM}/system-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%date{HH:mm:ss} [%-5level] [%thread] %logger{17} - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="50 MB"/>
            </Policies>
            <!--最多10个日志文件-->
            <DefaultRolloverStrategy compressionLevel="0" max="10"/>
        </RollingFile>
    </appenders>

    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
	<!--    <loggers>
        &lt;!&ndash;过滤掉spring、apache和druid的一些无用的DEBUG信息&ndash;&gt;
        <logger name="org.springframework" level="ERROR"> </logger>
        <logger name="com.alibaba.druid" level="ERROR"> </logger>
        <logger name="org.apache" level="ERROR"> </logger>
        &lt;!&ndash;vm和jsp两种视图同时存在时,如果找不到vm,就会去找jsp文件。但是这里老是会打印error日志&ndash;&gt;
        <logger name="org.apache.velocity" level="FATAL"> </logger>

        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="logfile"/>
        </root>
    </loggers>-->
    <loggers>
        <!-- com.example 包下打印日志 -->
        <logger name="com.example" level="DEBUG" additivity="false">
            <appender-ref ref="Console" />
        </logger>
        <root level="ERROR">
            <appender-ref ref="Console" />
        </root>
    </loggers>
</configuration>

2.3、服务器端代码

public class HelloServer {
    
    
    public static void main(String[] args) {
    
    
        // 1、服务端启动器,负责组装 netty 组件,启动服务器
        new ServerBootstrap()
                // 2、BossEventLoop WorkerEventLoop(包含selector, Thread), group 组
                .group(new NioEventLoopGroup())
                // 3、选择 服务器的 ServerSocketChannel 实现 (EpollServerSocketChannel、OioServerSocketChannel)
                .channel(NioServerSocketChannel.class)
                // 4、boss负责处理连接 worker(child)负责处理读写。决定 worker(child) 能执行哪些操作(handler)
                .childHandler(
                        // 5、Channel 代表和客户端进行数据读写的通道;Initializer 初始化,负责添加别的 handler
                    new ChannelInitializer<NioSocketChannel>() {
    
    
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
    
    
                        // 6、添加具体的 handler
                        // 将 ByteBuf 转换为字符串
                        nioSocketChannel.pipeline().addLast(new StringDecoder());
                        // 自定义 handler
                        nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
    
    
                            @Override   // 读事件
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
                                // 打印上一步转换好的字符串
                                System.out.println(msg);
                            }
                        });
                    }
                })
                // 7、绑定监听端口
                .bind(8888);
    }
}

2.4、客服端代码

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(new ChannelInitializer<NioSocketChannel>() {
    
    
                    @Override   // 连接建立后被调用
                    protected void initChannel(NioSocketChannel ch) throws Exception {
    
    
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                // 5、连接到服务器
                .connect(new InetSocketAddress("localhost", 8888))
                .sync()
                .channel()
                // 6、向服务器发送数据
                .writeAndFlush("hello, world");
    }
}

2.5、执行流程图

在这里插入图片描述

2.6、一些理解

  • 把 Channel 理解为数据的通道。
  • 把 msg 理解为流动的数据,最开始输入 ByteBuf,但经过 pipeline(流水线) 的加工,会变成其他类型对象,最后输出又变成 ByteBuf。
  • 把 handler 理解为数据处理的处理工序
    • 工序有多道,合在一起就是 pipeline(流水线),pipeline 负责发布事件(读、读取完成…)传播给每个 handler,handler 对自己感兴趣的事件进行处理(重写了相应事件处理方法)
    • handler 分 Inbound(入站) 和 Outbound(出站) 两类
  • 把 EventLoop 理解为处理数据的工人
    • 工人可以管理多个 Channel 的 IO 操作,并且一旦工人负责了某个 Channel,就要负责到底(绑定)。
    • 工人既可以执行 IO 操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个 Channel 的待处理任务,任务分为普通任务、定时任务。
    • 工人按照 pipeline 顺序,依次按照 handler 的规则(代码)处理数据。可以为每道工序指定不同的工人(非 IO 操作)。

猜你喜欢

转载自blog.csdn.net/weixin_43989102/article/details/126735668