1. Netty核心功能与线程模型详解


本文是按照自己的理解进行笔记总结,如有不正确的地方,还望大佬多多指点纠正,勿喷。

课程内容:

01、Netty核心组件快速了解
02、Hello,Netty!
03、深入理解Channel、EventLoop(Group)
04、深入理解EventLoop和EventLoopGroup
05、ChannelHandler和它的适配器
06、ChannelPipeline辨析
07、ChannelHandlerContext辨析
08、用Netty解决TCP粘包/半包
09、Netty中的编解码器
10、实战Netty快速实现Web服务器
11、序列化框架Netty集成实战
12、Netty中的单元测试

1. 认识Netty

Netty 4.1.42.Final版本进行

<dependency>
   <groupId>io.netty</groupId>
   <artifactId>netty-all</artifactId>
   <version>4.1.42.Final</version>
   <scope>compile</scope>
</dependency>

基于Netty的知名项目

  • 数据库:Cassandra
  • 大数据处理:Spark、Hadoop
  • 消息中间件:RocketMQ
  • 检索:Elasticsearch
  • 框架:gRPC、Apache Dubbo、Spring5 WebFlux
  • 分布式协调器:ZooKeeper

Netty的优势

  1. API使用简单,开发门槛低;
  2. 功能强大,预置了多种编解码功能,支持多种主流协议;
  3. 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
  4. 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;
  5. 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
  6. 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入;
  7. 经历了大规模的商业应用考验,质量得到验证。

为什么不用Netty5 ?
Netty5有两个重大改变:支持AIO(异步编程,就是要回调),将线程模型改成了favk/join。通过测试发现其实对性能的提高不是很大,几乎不明显。

因为Netty5还处于Alpha1版本,几乎停止维护了

为什么Netty使用NIO而不是AIO?
Linux下的AIO是一个伪AIO,Linux下的AIO性能也不如NIO的强。

为什么不用Mina ?

其实根据诞生关系是Netty的诞生稍微比Mina早了那么一点,这个Netty与Mina是同一个作者,这个作者在完成了Mina之后又投身于Netty种了,相当于Mina处于缺少维护的状态。并且Netty之后发布的版本的性能都比Mina优秀,因此就没有使用Mina

2. 第一个Netty程序

Netty的核心组件初步认识

  • Bootstrap、EventLoop(Group)、Channel
  • 事件和ChannelHandler、ChannelPipeline
  • ChannelFuture

Bootstrap是Netty框架启动类和主入口类。Bootstrap分为服务端的与客户端的,

在 Netty 中,Channel 是网络通信的基本概念和抽象。它代表着一个开放的连接,可以进行数据的读写和事件的处理。
具体来说,Channel 可以理解为应用程序与网络套接字之间的通道,它提供了以下主要功能:

  1. 数据的读写:通过 Channel,应用程序可以从网络中读取数据或将数据写入网络。它提供了高效的读写操作,支持异步非阻塞的 I/O 操作。可以通过 Channel 的读写方法来实现数据的传输和交换。
  2. 事件的处理:Channel 是一个事件驱动的实体,它可以处理各种网络事件,例如连接建立、数据到达、写就绪等。通过注册不同类型的事件处理器(ChannelHandler),应用程序可以根据需要处理这些事件,执行相应的逻辑处理。
  3. 状态的管理:Channel 提供了连接的状态管理,包括打开、关闭、活动状态等。应用程序可以通过 Channel 的状态变化事件进行相应的处理,例如连接建立或断开时的处理操作。
  4. 通道的配置:Channel 可以进行各种配置,如设置缓冲区大小、接收和发送缓冲区的设置、超时设置等。通过配置 Channel,可以调整网络通信的性能和行为。
    总之,Channel 在 Netty 中扮演着非常重要的角色,它是应用程序与网络之间的桥梁,提供了数据读写、事件处理和状态管理等功能。通过使用 Channel,开发者可以方便地进行网络编程,实现高性能和可靠的网络通信。

EventLoop(Group)
在 Netty 中,EventLoop 是用于处理 I/O 事件和任务的线程。它是 Netty 异步事件驱动的核心组件,负责管理 Channel 的生命周期、处理事件和执行任务。
EventLoop 在 EventLoopGroup 中扮演着重要角色,而 EventLoopGroup 则是一组维护和管理 EventLoop 的容器。一般情况下,一个应用程序只需要一个 EventLoopGroup,而其中的多个 EventLoop 可以同时处理多个 Channel 的 I/O 操作。
EventLoop 的主要功能包括:

  1. I/O 事件的处理:EventLoop 负责处理 Channel 的 I/O 事件,如数据的读写、连接的建立和关闭等。当一个 I/O 事件发生时,EventLoop 会调用相应的 ChannelHandler 处理事件,并触发相应的回调方法。通过事件驱动的方式,实现了高效的异步非阻塞 I/O。
  2. 任务的执行:除了处理 I/O 事件外,EventLoop 还可以执行用户自定义的任务。这些任务可以是一些耗时的计算、定时任务、事件调度等。通过将任务提交给 EventLoop,可以在事件循环中异步执行,避免了阻塞主线程的情况。
  3. 定时任务的处理:EventLoop 提供了定时任务的调度和执行,支持在一定的时间间隔或固定的时间点执行任务。这对于需要定时执行一些操作的应用场景非常有用,例如心跳检测、定时数据刷新等。

需要注意的是,EventLoop 在内部维护一个事件循环(Event Loop),它会不断地轮询 I/O 事件和任务,以便及时处理。每个 EventLoop 都有一个独立的线程来执行这个循环,确保了并发的安全性。

总之,EventLoop 是 Netty 中负责处理 I/O 事件和任务的线程,通过事件驱动的方式实现了高效的异步非阻塞 I/O。它在 EventLoopGroup 中起着重要的作用,为应用程序提供了高性能和可扩展的网络编程能力。

在 Netty 中,事件(Event)、ChannelHandler 和 ChannelPipeline 是实现高效网络通信的重要组件。

  1. 事件(Event):事件是 Netty 中的核心概念,它代表了网络通信过程中发生的各种状态和操作,如连接建立、数据到达、数据写就绪等。Netty 使用事件来驱动整个网络通信过程,通过触发和处理事件来实现异步的、非阻塞的网络通信。
  2. ChannelHandler:ChannelHandler 是用于处理事件和执行业务逻辑的组件。它定义了一组回调方法,如 channelRead()、channelActive()、channelInactive() 等,用于处理不同类型的事件。开发者可以实现自己的 ChannelHandler,并将其注册到 ChannelPipeline 中,以便在特定事件发生时执行相应的逻辑处理。
  3. ChannelPipeline:ChannelPipeline 是 ChannelHandler 的容器,它负责管理和执行 ChannelHandler 的调用顺序。每个 Channel 都会有一个对应的 ChannelPipeline,用于处理该 Channel 上的事件和数据。当一个事件被触发时,ChannelPipeline 会按照预定的顺序依次调用注册的 ChannelHandler 来处理事件。通过配置不同的 ChannelHandler,可以实现数据的编解码、数据处理、安全认证等功能。

ChannelPipeline 的设计遵循了责任链模式,每个 ChannelHandler 只负责自己关心的事件,通过调用下一个 ChannelHandler 来传递事件和数据。这样可以实现组件的解耦和灵活的功能扩展。

通过将不同的 ChannelHandler 注册到 ChannelPipeline 中,可以构建一个处理器链,每个处理器负责特定的功能。数据在经过 ChannelPipeline 时,会按照处理器链的顺序依次经过每个处理器,最终被发送到合适的目标。

总结来说,事件、ChannelHandler 和 ChannelPipeline 是 Netty 中实现高效网络通信的核心组件。事件用于驱动整个网络通信过程,ChannelHandler 用于处理事件和执行业务逻辑,而 ChannelPipeline 则是管理和执行 ChannelHandler 的调用顺序,实现了组件的解耦和灵活的功能扩展。通过合理配置和使用这些组件,可以实现高性能、可扩展的网络应用。

在 Netty 中,ChannelFuture 是用于异步操作结果的表示和处理的对象。它表示一个尚未完成的 I/O 操作,可以通过 ChannelFuture 来获取操作的结果、添加监听器以及执行后续的操作。
ChannelFuture 提供了以下主要功能:

  1. 异步操作的结果:ChannelFuture 可以获取异步操作的结果,如数据的发送、连接的建立等。它提供了方法来判断操作是否完成,获取操作的成功或失败状态,以及获取操作返回的结果对象。
  2. 添加操作完成的监听器:可以通过 ChannelFuture 添加一个或多个监听器,以便在操作完成后执行相应的操作。例如,可以添加一个监听器来处理操作完成时的逻辑,或者添加一个监听器来处理操作失败时的错误处理。通过监听器,可以对异步操作的结果进行处理。
  3. 等待和同步操作:可以通过 ChannelFuture 进行等待和同步操作,即阻塞当前线程,直到操作完成。这对于需要等待操作结果并进行后续处理的情况非常有用。可以使用 ChannelFuture 的一些方法,如 await()、sync() 等来实现等待和同步操作。
  4. 链式操作:ChannelFuture 支持链式操作,可以在一个操作完成后继续执行下一个操作。通过调用 ChannelFuture 的一些方法,如 addListener()、addListener(ChannelFutureListener) 等,可以实现链式操作的编写,简化了代码的编写。

总之,ChannelFuture 在 Netty 中用于表示和处理异步操作的结果。通过它,可以获取操作的结果、添加监听器来处理操作完成后的逻辑,进行等待和同步操作,以及实现链式操作。它提供了灵活的方式来处理和管理异步操作,使得网络编程更加简洁和高效。

在这里插入图片描述

Hello,Netty! 我们的第一个Netty程序

首先导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ding</groupId>
    <artifactId>netty</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>netty</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.6.2</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.42.Final</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

只要使用Netty,下面这个代码是必写的,唯一不同的是每个应用的handler不太一样。

package com.ding.nettybasic;


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

public class EchoServer {
    private static final Logger LOG = LoggerFactory.getLogger(EchoServer.class);

    private final int port;

    public EchoServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 9999;
        EchoServer echoServer = new EchoServer(port);
        LOG.info("服务器即将启动");
        echoServer.start();
        LOG.info("服务器关闭");
    }

    private void start() throws InterruptedException {
        /*线程组*/
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            /*服务端启动必备*/
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
                    .channel(NioServerSocketChannel.class)/*指定使用NIO的通信模式*/
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    });
            /*异步绑定到服务器,sync()会阻塞到完成*/
            ChannelFuture f = b.bind().sync();
            /*阻塞当前线程,知道服务器的ServerChannel被关闭*/
            f.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully().sync();
        }
    }
}

SocketChannel在与业务流转之间还有一个Buffer,因此这个里面也要有一个Buffer

在这里插入图片描述

package com.ding.nettybasic;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf in = (ByteBuf)msg;
        System.out.println("server accept:"+in.toString(CharsetUtil.UTF_8));
        ctx.writeAndFlush(in);
        ctx.close();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("连接已建立");
        super.channelActive(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
package com.ding.nettybasic;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

public class EchoClient {
    private static final Logger LOG = LoggerFactory.getLogger(EchoClient.class);

    private final int port;
    private final String host;

    public EchoClient(int port, String host) {
        this.port = port;
        this.host = host;
    }
    public void start() throws InterruptedException {
        /*线程组*/
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            /*服务端启动必备*/
            Bootstrap b = new Bootstrap();
            b.group(group)
                .channel(NioSocketChannel.class)/*指定使用NIO的通信模式*/
                .remoteAddress(new InetSocketAddress(host,port))
                .handler(new ChannelInitializer<SocketChannel>() {
                     @Override
                     protected void initChannel(SocketChannel ch) throws Exception {
                         ch.pipeline().addLast(new EchoClientHandler());
                     }
                 });
            /*异步绑定到服务器,sync()会阻塞到完成*/
            ChannelFuture f = b.connect().sync();
            /*阻塞当前线程,知道服务器的ServerChannel被关闭*/
            f.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new EchoClient(9999,"127.0.0.1").start();
    }
}
package com.ding.nettybasic;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        /*读取网络数据后进行业务处理,并关闭连接*/
        System.out.println("client Accept" + msg.toString(CharsetUtil.UTF_8));
        /*关闭连接*/
        ctx.close();
    }

    /*channel活跃后,做业务处理*/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello Netty",CharsetUtil.UTF_8));
    }
}

在这里插入图片描述
在这里插入图片描述

3. Netty组件

3.1 EventLoop和EventLoopGroup

关系说明

  • 一个EventLoopGroup包含一个或者多个EventLoop;
  • 一个EventLoop在它的生命周期内只和一个Thread 绑定;
  • 所有由EventLoop 处理的I/O事件都将在它专有的Thread上被处理;

在这里插入图片描述

在这里插入图片描述

Channel

  • Channel-Socket;
  • EventLoop—控制、多线程处理、并发;

在这里插入图片描述

ChannelPipeline和ChannelHandlerContext

ChannelPipeline 提供了ChannelHandler链的容器,并定义了用于在该链上传播入站和出站事件流的APl。

ChannelHandler 的生命周期

在这里插入图片描述
ChannelHandler 的生命周期

在ChannelHandler被添加到ChannelPipeline 中或者被从ChannelPipeline中移除时会调用下面这些方法。这些方法中的每一个都接受一个ChannelHandlerContext参数。

handlerAdded当把 ChannelHandler 添加到ChannelPipeline 中时被调用
handlerRemoved当从ChannelPipeline中移除ChannelHandler时被调用
exceptionCaught当处理过程中在 ChannelPipeline中有错误产生时被调用

ChannelPipeline中ChannelHandler

Netty会把出站Handler和入站Handler放到一个Pipeline中,数据结构上是一个双向链表

在这里插入图片描述

ChannelPipeline

在这里插入图片描述

那么分属出站和入站不同的Handler,在业务没要求的情况下是可以不考虑顺序的。
而同属一个方向的Handler则是有顺序的,因为上一个Handler处理的结果往往是下一个Handler的要求的输入。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值