Netty

Netty笔记

1 简介

Netty是⼀个⾼性能的、异步的、基于事件驱动的⽹络应⽤框架

同步、异步是相对的,在请求或执⾏过程中,如果会阻塞等待,就是同步操作,反之就是异步操作
在这里插入图片描述

1.1.2、核⼼架构

在这里插入图片描述

  • 核⼼

    • 可扩展的事件模型
    • 统⼀的通信api
      • ⽆论是http还是socket都使⽤统⼀的api,简化了操作
    • 零拷⻉机制与字节缓冲区
  • 传输服务

    • ⽀持socket以及datagram(数据报)

    • ⽀持http协议

    • In-VM Pipe (管道协议)

  • 协议⽀持

    • http 以及 websocket
    • SSL 安全套接字协议⽀持
    • Google Protobuf (序列化框架)
    • ⽀持zlib、gzip压缩
    • ⽀持⼤⽂件的传输
    • RTSP(实时流传输协议,是TCP/IP协议体系中的⼀个应⽤层协议)
    • ⽀持⼆进制协议并且提供了完整的单元测试

1.2 Netty优势

  • Netty是基于Java的NIO实现的,Netty将各种传输类型、协议的实现API进⾏了统⼀封装,实现了
    阻塞和⾮阻塞Socket。

  • 基于事件模型实现,可以清晰的分离关注点,让开发者可以聚焦业务,提升开发效率。
    ⾼度可定制的线程模型-单线程、⼀个或多个线程池,如SEDA(Staged Event-Driven
    Architecture)

    • SEDA:把⼀个请求处理过程分成⼏个Stage,不同资源消耗的Stage使⽤不同数量的线程来处
      理,Stage间使⽤事件驱动的异步通信模式。
  • Netty只依赖了JDK底层api,没有其他的依赖,如:Netty 3.X依赖JDK5以上,Netty4.x依赖JDK6以
    上。

  • Netty在⽹络通信⽅⾯更加的⾼性能、低延迟,尽可能的减少不必要的内存拷⻉,提⾼性能。

  • 在安全⽅⾯,完整的SSL/TLS和StartTLS⽀持。

  • 社区⽐较活跃,版本迭代周期短,发现bug可以快速修复,新版本也会不断的加⼊

1.3 版本说明

Netty的版本分为,3.x、4.x和5.x,其中5.x版本已经被官⽅废弃,详情查看github的issue:[https://git
hub.com/netty/netty/issues/4466](https://git
hub.com/netty/netty/issues/4466)

在这里插入图片描述

废弃5.x的主要原因是,使⽤ForkJoinPool后复杂度提升了,但是性能⽅⾯并没有明显的优势,反⽽给项
⽬的维护带来了很⼤的⼯作量,因此还有到发布新版本的时机,所以将5.x废弃。

Netty的下载:

在这里插入图片描述

⽬前Netty的最新版本为4.1.50.Final,本套课程基于此版本学习的

1.4 为什么选择Netty,而不选择原生的NIO

在⽹络编程⽅⾯,⼀般都不会选择原⽣的NIO,⽽是会选择Netty、Mina等封装后的框架,主要原因
是:

  • NIO的类库和API繁杂,使⽤麻烦,需要熟练掌握Selector、ServerSocketChannel、
    SocketChannel、ByteBuffer等。
  • 需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程涉及到Reactor模
    式,你必须对多线程和⽹路编程⾮常熟悉,才能编写出⾼质量的NIO程序。
  • 可靠性能⼒补⻬,⼯作量和难度都⾮常⼤。例如客户端⾯临断连重连、⽹络闪断、半包读写、失败
    缓存、⽹络拥塞和异常码流的处理等问题,NIO编程的特点是功能开发相对容易,但是可靠性能⼒
    补⻬的⼯作量和难度都⾮常⼤。
  • JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官
    ⽅声称在JDK 1.6版本的update18修复了该问题,但是直到JDK 1.7版本该问题仍旧存在,只不过该
    BUG发⽣概率降低了⼀些⽽已,它并没有得到根本性解决。
    • 具体问题查看:https://www.jianshu.com/p/3ec120ca46b2

1.5 Netty应⽤场景

Netty的应⽤场景是⾮常⼴泛的,⽐如:互联⽹⾏业的、游戏⾏业、⼤数据⾏业、医疗⾏业、⾦融等⾏
业。

  • 互联⽹⾏业
    • 在互联⽹⾏业项⽬中,最具代表性的就是分布式系统架构的远程服务调⽤,通过RPC的⽅式
      进⾏⾼性能的服务调⽤,⽬前主流的RPC框架底层均采⽤了Netty作为⽹络通信组件。
    • ⽐如:阿⾥巴巴的分布式服务治理框架Dubbo,底层就是使⽤Netty作为通信组件。
    • gRPC,是Google提供的⾼性能RPC框架,底层也使⽤了Netty。
  • ⼤数据⾏业
    • ⼤数据⾏业中的许多技术也采⽤了Netty作为通信组件,如:Flink、Spark、Elasticsearch
      等。

官⽅列出了使⽤Netty的⼀些项⽬:https://netty.io/wiki/related-projects.html

在这里插入图片描述

1.6 电商系统⾃研RPC

市⾯上有很多的RPC框架,⽐如:dubbo、gRPC、thrift等产品,在开发项⽬时,我们可以选择使⽤已
有的RPC产品,也可以⾃研RPC,⼀线⼤⼚⼀般会选择⾃研RPC,会根据⾃身的业务特点进⾏研发,以
追求更⾼的性能

RPC基本的调⽤示意图:

在这里插入图片描述

在实现⾃研RPC后,我们将基于此来实现电商系统中的订单模块的业务,当然了,这⾥所实现的业务⽐
较简单,主要是学习⾃研RPC为主。

在这里插入图片描述

2、Netty的⾼性能设计

Netty就是使⽤Java的NIO实现了Reactor线程模型

2.1 Java中的IO模型

在JDK1.4之前,基于Java所有的socket通信都采⽤了同步阻塞模型(BIO),这种模型性能低下,当时
⼤型的服务均采⽤C或C++开发,因为它们可以直接使⽤操作系统提供的异步IO或者AIO,使得性能得到
⼤幅提升。

2002年,JDK1.4发布,新增了java.nio包,提供了许多异步IO开发的API和类库。新增的NIO,极⼤的促
进了基于Java的异步⾮阻塞的发展和应⽤。

2011年,JDK7发布,将原有的NIO进⾏了升级,称为NIO2.0,其中也对AIO进⾏了⽀持。

2.1.1 BIO模型

java中的BIO是blocking I/O的简称,它是同步阻塞型IO,其相关的类和接⼝在java.io下。
BIO模型简单来讲,就是服务端为每⼀个请求都分配⼀个线程进⾏处理,如下:

在这里插入图片描述

示例代码:

BIOServer

package com.jeaw.netty.bio;

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BIOServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(6666);
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true) {
            System.out.println("等待客户端连接。。。。");
            Socket socket = serverSocket.accept(); //阻塞
            executorService.execute(() -> {
                try {
                    InputStream inputStream = socket.getInputStream(); //阻塞
                    byte[] bytes = new byte[1024];
                    while (true) {
                        int length = inputStream.read(bytes);
                        if (length == -1) {
                            break;
                        }
                        System.out.println(new String(bytes, 0, length, "UTF-8"));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

BIOClient

package com.jeaw.netty.bio;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class BIOClient {
    public static void main(String[] args) throws Exception {
        //1 创建Socket对象
        Socket socket = new Socket("127.0.0.1", 9999);

        while (true) {
            //2 从连接中取出输出流 发送消息
            OutputStream os = socket.getOutputStream();
            System.out.println("请输入:");
            Scanner sc = new Scanner(System.in);
            String s = sc.nextLine();
            os.write(s.getBytes());
            //3.从连接中取出输入流并接收回话
            InputStream is = socket.getInputStream();
            byte[] b = new byte[20];
            is.read(b);
            System.out.println("服务器说:" + new String(b).trim());
            //4 关闭
            is.close();
            os.close();
            socket.close();
        }
    }
}

这种模式存在的问题

  • 客户端的并发数与后端的线程数成1:1的⽐例,线程的创建、销毁是⾮常消耗系统资源的,随着并
    发量增⼤,服务端性能将显著下降,甚⾄会发⽣线程堆栈溢出等错误。
  • 当连接创建后,如果该线程没有操作时,会进⾏阻塞操作,这样极⼤的浪费了服务器资源。

2.1.2 NIO模型

NIO,称之为New IO 或是 non-block IO (⾮阻塞IO),这两种说法都可以,其实称之为⾮阻塞IO更恰
当⼀些。 NIO相关的代码都放在了java.nio包下,其三⼤核⼼组件:Buffer(缓冲区)、Channel(通道)、
Selector(选择器/多路复⽤器)

  • Buffer
    • 在NIO中,所有的读写操作都是基于缓冲区完成的,底层是通过数组实现的,常⽤的缓冲区是
      ByteBuffer,每⼀种java基本类型都有对应的缓冲区对象(除了Boolean类型),如:
      CharBuffer、IntBuffer、LongBuffer等。
  • Channel
  • 在BIO中是基于Stream实现,⽽在NIO中是基于通道实现,与流不同的是,通道是双向的,
    既可以读也可以写。
  • Selector
    • Selector是多路复⽤器,它会不断的轮询注册在其上的Channel,如果某个Channel上发⽣ 读或写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey获取 就绪Ch//annel的集合,进⾏IO的读写操作。

基本示意图如下:

在这里插入图片描述

可以看出,NIO模型要优于BIO模型,主要是:

  • 通过多路复⽤器就可以实现⼀个线程处理多个通道,避免了多线程之间的上下⽂切换导致系统开销
    过⼤。
  • NIO⽆需为每⼀个连接开⼀个线程处理,并且只有通道真正有有事件时,才进⾏读写操作,这样⼤
    ⼤的减少了系统开销。

示例代码:

NIOServer

package com.jeaw.netty.nio.net;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.net.InetSocketAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) throws IOException {
        //1 得到一个ServerSocketChannel 对象  老大
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //2 得到一个Selector对象 间谍
        Selector selector = Selector.open();

        //3 绑定端口
        serverSocketChannel.bind(new InetSocketAddress(9999));

        //4 设置非阻塞
        serverSocketChannel.configureBlocking(false);

        //5 注册 serverSocketChannel对象给Selector对象
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //6 业务代码

        while (true) {
            if (selector.select(2000) == 0) {
                System.out.println("Server:没有客户端搭理我,我就干点别的事");
                continue;
            }
            //6.2 得到SelectionKey,判断通道里的事件
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                System.out.println("OP_ACCEPT");
                if (key.isAcceptable()) {
                    //客户端连接请求事件
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ, 				                     							ByteBuffer.allocate(1024));
                }else if(key.isReadable()){
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    ByteBuffer direct = ByteBuffer.allocateDirect(3);
                    channel.read(buffer);
                    System.out.println("客户端发来数据:"+new String(buffer.array()));
                }else if (key.isWritable()){
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    Scanner sc = new Scanner(System.in);
                    System.out.println("请输入:");
                    String s = sc.nextLine();
                    ByteBuffer byteBuffer = ByteBuffer.wrap(s.getBytes());
                    channel.write(byteBuffer.put(byteBuffer.array()));
                }
                // 6.3 手动从集合中移除当前key,防止重复处理
                keyIterator.remove();
            }
        }
    }
}

NIOClient

package com.jeaw.netty.nio.net;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NIOClient {
    public static void main(String[] args) throws IOException {
        //1. 得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        //2. 设置非阻塞方式
        socketChannel.configureBlocking(false);
        //3. 提供服务器端的IP 地址和端口号
        InetSocketAddress address=new InetSocketAddress("127.0.0.1",9999);
        //4. 连接服务器端
        if (!socketChannel.connect(address)) {
            while (!socketChannel.finishConnect()) {//nio 作为非阻塞式的优势
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Client:连接服务器端的同时,我还可以干别的一些事情");
            }
        }
        //5. 得到一个缓冲区并存入数据
        String msg ="hello Server";
        //6. 发送数据
        while (true){
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入:");
            String s = sc.nextLine();
            ByteBuffer byteBuffer = ByteBuffer.wrap(s.getBytes());
            socketChannel.write(byteBuffer);
        }
    }
}

2.1.3 AIO模型

在NIO中,Selector多路复⽤器在做轮询时,如果没有事件发⽣,也会进⾏阻塞,如何能把这个阻塞也
优化掉呢?那么AIO就在这样的背景下诞⽣了。

AIO是asynchronous I/O的简称,是异步IO,该异步IO是需要依赖于操作系统底层的异步IO实现。

AIO的基本流程是:⽤户线程通过系统调⽤,告知kernel内核启动某个IO操作,⽤户线程返回。kernel
内核在整个IO操作(包括数据准备、数据复制)完成后,通知⽤户程序,⽤户执⾏后续的业务操作。

  • kernel的数据准备

    • 将数据从⽹络物理设备(⽹卡)读取到内核缓冲区。
  • kernel的数据复制

    • 将数据从内核缓冲区拷⻉到⽤户程序空间的缓冲区。

在这里插入图片描述

⽬前AIO模型存在的不⾜:

  • 需要完成事件的注册与传递,这⾥边需要底层操作系统提供⼤量的⽀持,去做⼤量的⼯作。
  • Windows 系统下通过 IOCP 实现了真正的异步 I/O。但是,就⽬前的业界形式来说,Windows 系
    统,很少作为百万级以上或者说⾼并发应⽤的服务器操作系统来使⽤。
  • ⽽在 Linux 系统下,异步IO模型在2.6版本才引⼊,⽬前并不完善。所以,这也是在 Linux 下,实
    现⾼并发⽹络编程时都是以 NIO 多路复⽤模型模式为主。

2.2 Reactor线程模型

Reactor线程模型不是Java专属,也不是Netty专属,它其实是⼀种并发编程模型,是⼀种思想,具有指
导意义。⽐如,Netty就是结合了NIO的特点,应⽤了Reactor线程模型所实现的。
Reactor模型中定义的三种⻆⾊:

  • Reactor:负责监听和分配事件,将I/O事件分派给对应的Handler。新的事件包含连接建⽴就绪、
    读就绪、写就绪等。
  • Acceptor:处理客户端新连接,并分派请求到处理器链中。
  • Handler:将⾃身与事件绑定,执⾏⾮阻塞读/写任务,完成channel的读⼊,完成处理业务逻辑
    后,负责将结果写出channel。

常⻅的Reactor线程模型有三种,如下:

  • Reactor单线程模型
  • Reactor多线程模型
  • 主从Reactor多线程模型

2.2.1 单Reactor单线程模型

在这里插入图片描述

说明:

  • Reactor充当多路复⽤器⻆⾊,监听多路连接的请求,由单线程完成
  • Reactor收到客户端发来的请求时,如果是新建连接通过Acceptor完成,其他的请求由Handler完
    成。
  • Handler完成业务逻辑的处理,基本的流程是:Read --> 业务处理 --> Send 。

这种模型的优缺点:

  • 优点
    • 结构简单,由单线程完成,没有多线程、进程通信等问题。
    • 适合⽤在⼀些业务逻辑⽐较简单、对于性能要求不⾼的应⽤场景。
  • 缺点
  • 由于是单线程操作,不能充分发挥多核CPU的性能。
  • 当Reactor线程负载过重之后,处理速度将变慢,这会导致⼤量客户端连接超时,超时之后往
    往会进⾏重发,这更加重Reactor线程的负载,最终会导致⼤量消息积压和处理超时,成为系
    统的性能瓶颈。
  • 可靠性差,如果该线程进⼊死循环或意外终⽌,就会导致整个通信系统不可⽤,容易造成单
    点故障。

2.2.2 单Reactor多线程模型

在这里插入图片描述

说明:

  • 在Reactor多线程模型相⽐较单线程模型⽽⾔,不同点在于,Handler不会处理业务逻辑,只是负
    责响应⽤户请求,真正的业务逻辑,在另外的线程中完成。
  • 这样可以降低Reactor的性能开销,充分利⽤CPU资源,从⽽更专注的做事件分发⼯作了,提升整
    个应⽤的吞吐。

但是这个模型存在的问题:

  • 多线程数据共享和访问⽐较复杂。如果⼦线程完成业务处理后,把结果传递给主线程Reactor进⾏
    发送,就会涉及共享数据的互斥和保护机制。

  • Reactor承担所有事件的监听和响应,只在主线程中运⾏,可能会存在性能问题。例如并发百万客
    户端连接,或者服务端需要对客户端握⼿进⾏安全认证,但是认证本身⾮常损耗性能。

为了解决性能问题,产⽣了第三种主从Reactor多线程模型。

2.2.3 主从Reactor多线程模型

在这里插入图片描述

在主从模型中,将Reactor分成2部分:

  • MainReactor负责监听server socket,⽤来处理⽹络IO连接建⽴操作,将建⽴的socketChannel指
    定注册给SubReactor。
  • SubReactor主要完成和建⽴起来的socket的数据交互和事件业务处理操作。\

该模型的优点:

  • 响应快,不必为单个同步事件所阻塞,虽然Reactor本身依然是同步的。
  • 可扩展性强,可以⽅便地通过增加SubReactor实例个数来充分利⽤CPU资源。
  • 可复⽤性⾼,Reactor模型本身与具体事件处理逻辑⽆关,具有很⾼的复⽤性。

2.3、Netty模型

Netty模型是基于Reactor模型实现的,对于以上三种模型都有⾮常好的⽀持,也⾮常的灵活,⼀般情
况,在服务端会采⽤主从架构模型,基本示意图如下:

在这里插入图片描述

说明:

  • 在Netty模型中,负责处理新连接事件的是BossGroup,负责处理其他事件的是WorkGroup。
    Group就是线程池的概念。
  • NioEventLoop表示⼀个不断循环的执⾏处理任务的线程,⽤于监听绑定在其上的读/写事件。
  • 通过Pipeline(管道)执⾏业务逻辑的处理,Pipeline中会有多个ChannelHandler,真正的业务逻
    辑是在ChannelHandler中完成的。

3 Netty快速⼊⻔

开发环境:JDK8 + Idea

3.1 创建MyRPC项⽬

pom.xml⽂件:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.jeaw</groupId>
  <artifactId>netty-test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>netty-test Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>

    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.51.Final</version>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.7.0-M1</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>netty-test</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

3.2 服务端

MyRPCServer

package com.jeaw.netty.netty.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class MyRPCServer {

    public void start(int port) throws Exception {
        //主线程 ,不处理任何业务,只是接收客户端的连接请求
        EventLoopGroup boss = new NioEventLoopGroup(1);
        //工作线程,线程数量是: CPU*2
        NioEventLoopGroup worker = new NioEventLoopGroup(10);

        try {
            //服务启动类
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(boss, worker) //设置线程组
                    .channel(NioServerSocketChannel.class) //配置Server通道
                    .childHandler(new MyChannelInitializer());   //worker线程的处理器

            ChannelFuture future = serverBootstrap.bind(port).sync();
            System.out.println("服务器启动完成,端口号为:"+port);
            //等待服务端监听端口关闭
            future.channel().closeFuture().sync();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }

    }
}

MyChannelInitializer

package com.jeaw.netty.netty.server;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;

public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // 将业务处理器加入到列表中
        socketChannel.pipeline().addLast(new MyChannelHandler());
    }
}

MyChannelHandler

package com.jeaw.netty.netty.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.EventExecutorGroup;

import java.nio.charset.Charset;

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

        ByteBuf byteBuf = (ByteBuf) msg;
        String s = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("客户端发来的数据:"+s);
        ctx.writeAndFlush(Unpooled.copiedBuffer("ok",CharsetUtil.UTF_8));
    }

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

3.3 测试⽤例

package com.jeaw.netty.netty.server;

import org.junit.Test;

public class TestServer {

    @Test
    public void testServer() throws Exception{
        MyRPCServer myRPCServer = new MyRPCServer();
        myRPCServer.start(9999);  //65536
    }
}

在这里插入图片描述

3.4 客户端

3.4.1 MyRPCClient

package com.jeaw.netty.netty.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import javafx.concurrent.Worker;

public class MyRPCClient {

    public void start(String host,int port) throws Exception {
        //定义工作线程组
        NioEventLoopGroup worder = new NioEventLoopGroup();

        try {
            //client 使用Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(worder)
                    .channel(NioSocketChannel.class)
                    .handler(new MyClientHandler());

            ChannelFuture future = bootstrap.connect(host, port).sync();

            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            worder.shutdownGracefully();
        }

    }
}

3.4.2 MyClientHandler

package com.jeaw.netty.netty.client;

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 MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf msg) throws Exception {
        System.out.println("接收到服务器的消息"+msg.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //向服务器端发送数据
        String s ="hello";
        ctx.writeAndFlush(Unpooled.copiedBuffer(s,CharsetUtil.UTF_8));
    }

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

3.4.3 测试⽤例

package cn.itcast.myrpc;

import cn.itcast.myrpc.client.MyRPCClient;
import org.junit.Test;

public class TestClient {
    
	@Test
	public void testClient() throws Exception{
		new MyRPCClient().start("127.0.0.1", 5566);
	}
}

4 Netty核⼼组件

4.1 Channel

Channel可以理解为是socket连接,在客户端与服务端连接的时候就会建⽴⼀个Channel,它负责基本
的IO操作,⽐如:bind()、connect(),read(),write() 等。

Netty 的 Channel 接⼝所提供的 API,⼤⼤地降低了直接使⽤ Socket 类的复杂性。
不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应,常⽤的 Channel 类型:

  • NioSocketChannel,NIO的客户端 TCP Socket 连接。
  • NioServerSocketChannel,NIO的服务器端 TCP Socket 连接。
  • NioDatagramChannel, UDP 连接。
  • NioSctpChannel,客户端 Sctp 连接。
  • NioSctpServerChannel,Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP ⽹络 IO 以及⽂件IO。

4.2 EventLoop、EventLoopGroup

有了 Channel 连接服务,连接之间可以消息流动。如果服务器发出的消息称作“出站”消息,服务器接受
的消息称作“⼊站”消息。那么消息的“出站”/“⼊站”就会产⽣事件(Event)。

例如:连接已激活;数据读取;⽤户事件;异常事件;打开链接;关闭链接等等。

有了事件,就需要⼀个机制去监控和协调事件,这个机制(组件)就是EventLoop。

在 Netty 中每个 Channel 都会被分配到⼀个 EventLoop。⼀个 EventLoop 可以服务于多个 Channel。
每个 EventLoop 会占⽤⼀个 Thread,同时这个 Thread 会处理 EventLoop 上⾯发⽣的所有 IO 操作和
事件。

在这里插入图片描述

EventLoopGroup 是⽤来⽣成 EventLoop 的,在前⾯的例⼦中,第⼀⾏代码就是 new
NioEventLoopGroup();

// 主线程,不处理任何业务逻辑,只是接收客户的连接请求
EventLoopGroup boss = new NioEventLoopGroup(1);
// ⼯作线程,线程数默认是:cpu*2
EventLoopGroup worker = new NioEventLoopGroup();

如果没有指定线程数⼤⼩,默认线程数为:cpu核数*2,源码如下:

static {
		DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
			"io.netty.eventLoopThreads", NettyRuntime.availableProcessors()
		* 2)); //可⽤cpu核数 * 2
		if (logger.isDebugEnabled()) {
			logger.debug("-Dio.netty.eventLoopThreads: {}",
			DEFAULT_EVENT_LOOP_THREADS);
		}
}

上图关系为:

  • ⼀个 EventLoopGroup 包含⼀个或者多个 EventLoop;
  • ⼀个 EventLoop 在它的⽣命周期内只和⼀个 Thread 绑定;
  • 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
  • ⼀个 Channel 在它的⽣命周期内只注册于⼀个 EventLoop;
  • ⼀个 EventLoop 可能会被分配给⼀个或多个 Channel。

4.3 ChannelHandler

ChannelHandler对使⽤者⽽⾔,可以说是最重要的组件了,因为对于数据的⼊站和出站的业务逻辑的
编写都是在ChannelHandler中完成的。

在前⾯的例⼦中,MyChannelHandler就是实现了channelRead⽅法,获取到客户端传来的数据。
对于数据的出站和⼊站,有着不同的ChannelHandler类型与之对应:

  • ChannelInboundHandler ⼊站事件处理器
  • ChannelOutBoundHandler 出站事件处理器

接⼝继承关系如下:

在这里插入图片描述

ChannelHandlerAdapter提供了⼀些⽅法的默认实现,可减少⽤户对于ChannelHandler的编写。

ChannelInboundHandlerAdapter 与 SimpleChannelInboundHandler的区别:

  • 在服务端编写ChannelHandler时继承的是ChannelInboundHandlerAdapter
  • 在客户端编写ChannelHandler时继承的是SimpleChannelInboundHandler
  • 两者的区别在于,前者不会释放消息数据的引⽤,⽽后者会释放消息数据的引⽤。

在这里插入图片描述

4.4 ChannelPipeline

在Channel的数据传递过程中,对应着有很多的业务逻辑需要处理,⽐如:编码解码处理、读写操作
等,那么对于每种业务逻辑实现都需要有个ChannelHandler完成,也就意味着,⼀个Channel对应着多
个ChannelHandler,多个ChannelHandler如何去管理它们,它们的执⾏顺序⼜该是怎么样的,这就需
要ChannelPipeline进⾏管理了。

⼀个Channel包含了⼀个ChannelPipeline,⽽ChannelPipeline中维护了⼀个ChannelHandler的列
表。

ChannelHandler与Channel和ChannelPipeline之间的映射关系,由ChannelHandlerContext进⾏维
护。

它们关系如下:

在这里插入图片描述

ChannelHandler按照加⼊的顺序会组成⼀个双向链表,⼊站事件从链表的head往后传递到最后⼀个
ChannelHandler,出站事件从链表的tail向前传递,直到最后⼀个ChannelHandler,两种类型的
ChannelHandler相互不会影响。

4.5 Bootstrap

Bootstrap是引导的意思,它的作⽤是配置整个Netty程序,将各个组件都串起来,最后绑定端⼝、启动
Netty服务。

Netty中提供了2种类型的引导类,⼀种⽤于客户端(Bootstrap),⽽另⼀种(ServerBootstrap)⽤于服务
器。

它们的区别在于:

  • ServerBootstrap 将绑定到⼀个端⼝,因为服务器必须要监听连接,⽽ Bootstrap 则是由想要连接
    到远程节点的客户端应⽤程序所使⽤的。
  • 引导⼀个客户端只需要⼀个EventLoopGroup,但是⼀个ServerBootstrap则需要两个。
    • 因为服务器需要两组不同的 Channel
    • 第⼀组将只包含⼀个 ServerChannel,代表服务器⾃身的已绑定到某个本地端⼝的正在监听
      的套接字。
    • 第⼆组将包含所有已创建的⽤来处理传⼊客户端连接。

在这里插入图片描述

与ServerChannel相关联的EventLoopGroup 将分配⼀个负责为传⼊连接请求创建 Channel 的
EventLoop。⼀旦连接被接受,第⼆个 EventLoopGroup 就会给它的 Channel 分配⼀个 EventLoop。

4.6 Future

Future提供了⼀种在操作完成时通知应⽤程序的⽅式。这个对象可以看作是⼀个异步操作的结果的占位
符,它将在未来的某个时刻完成,并提供对其结果的访问。

JDK 预置了 interface java.util.concurrent.Future,但是其所提供的实现,只允许⼿动检查对应的操作
是否已经完成,或者⼀直阻塞直到它完成。这是⾮常繁琐的,所以 Netty 提供了它⾃⼰的实现——
ChannelFuture,⽤于在执⾏异步操作的时候使⽤。

  • ChannelFuture提供了⼏种额外的⽅法,这些⽅法使得我们能够注册⼀个或者多个
    ChannelFutureListener实例。
  • 监听器的回调⽅法operationComplete(),将会在对应的 操作完成时被调⽤ 。然后监听器可以判
    断该操作是成功地完成了还是出错了。
  • 每个 Netty 的出站 I/O 操作都将返回⼀个 ChannelFuture,也就是说,它们都不会阻塞。 所以
    说,Netty完全是异步和事件驱动的。

在这里插入图片描述

上图是 serverBootstrap.bind(port) ⽅法底层的逻辑实现。

4.7 ⼩结

在这里插入图片描述

通过以上图将Netty中的核⼼组件串起来。

5 详解ByteBuf

5.1 ⼯作原理

Java NIO 提供了ByteBuffer 作为它 的字节容器,但是这个类使⽤起来过于复杂,⽽且也有些繁琐。
Netty 的 ByteBuffer 替代品是 ByteBuf,⼀个强⼤的实现,既解决了JDK API 的局限性, ⼜为⽹络应⽤
程序的开发者提供了更好的API。

从结构上来说,ByteBuf 由⼀串字节数组构成。数组中每个字节⽤来存放信息。

ByteBuf 提供了两个索引,⼀个⽤于读取数据,⼀个⽤于写⼊数据。这两个索引通过在字节数组中移
动,来定位需要读或者写信息的位置。

当从 ByteBuf 读取时,它的 readerIndex(读索引)将会根据读取的字节数递增。

同样,当写 ByteBuf 时,它的 writerIndex(写索引) 也会根据写⼊的字节数进⾏递增。

+-------------------+------------------+------------------+
| discardable bytes | readable bytes   | writable bytes   |
| 					| (CONTENT) 	   |                  |
+-------------------+------------------+------------------+
| 					|				   |				  |
0 	 <= 	 readerIndex 	<=  writerIndex   <=   capacity
#discardable bytes -- 可丢弃的字节空间
#readable bytes -- 可读的字节空间
#writable bytes --可写的字节空间
#capacity -- 最⼤的容量

如果 readerIndex 超过了 writerIndex 的时候,Netty 会抛出 IndexOutOf-BoundsException 异常。

5.2 基本使⽤

5.2.1 读取操作

package com.jeaw.netty.byteBuf;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;

public class TestByteBuf01 {
    public static void main(String[] args) {
        //构造
        ByteBuf byteBuf = Unpooled.copiedBuffer("hello world",
                CharsetUtil.UTF_8);
        System.out.println("byteBuf的容量为:" + byteBuf.capacity());
        System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes());
        System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes());
        System.out.println("------------------⽅法⼀---------------------------");
        //⽅法⼀:内部通过移动readerIndex进⾏读取
        while (byteBuf.isReadable()) {
            System.out.println((char) byteBuf.readByte());
        }
        System.out.println("---------------------⽅法⼆------------------------");
        //⽅法⼆:通过下标直接读取
        for (int i = 0; i < byteBuf.readableBytes(); i++) {
            System.out.println((char) byteBuf.getByte(i));
        }
        System.out.println("---------------------⽅法三------------------------");
        //⽅法三:转化为byte[]进⾏读取
        byte[] bytes = byteBuf.array();
        for (byte b : bytes) {
            System.out.println((char) b);
        }
    }
}

5.2.2 写⼊操作

package com.jeaw.netty.byteBuf;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;

public class TestByteBuf02 {
    public static void main(String[] args) {
        //构造空的字节缓冲区,初始⼤⼩为10,最⼤为20
        ByteBuf byteBuf = Unpooled.buffer(10, 20);
        System.out.println("byteBuf的容量为:" + byteBuf.capacity());
        System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes());
        System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes());
        for (int i = 0; i < 5; i++) {
            byteBuf.writeInt(i); //写⼊int类型,⼀个int占4个字节
        }
        System.out.println("ok");
        System.out.println("byteBuf的容量为:" + byteBuf.capacity());
        System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes());
        System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes());
        while (byteBuf.isReadable()) {
            System.out.println(byteBuf.readInt());
        }
    }
}

5.2.3 丢弃已读字节

#通过discardReadBytes()⽅可以将已经读取的数据进⾏丢弃处理,就可以回收已经读取的字节空间
BEFORE discardReadBytes()
+-------------------+------------------+------------------+
| discardable bytes |  readable bytes  |  writable bytes  |
+-------------------+------------------+------------------+
|					|  				   | 				  |
0   <=   readerIndex   <=   writerIndex   <=   capacity
AFTER discardReadBytes()
+------------------+--------------------------------------+
|  readable bytes  |  writable bytes  (got more space)    |
+------------------+--------------------------------------+
| 				   |                                      |
	readerIndex (0)    <=    writerIndex (decreased)
package com.jeaw.netty.byteBuf;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;

public class TestByteBuf03 {
    public static void main(String[] args) {
        ByteBuf byteBuf = Unpooled.copiedBuffer("hello world",
                CharsetUtil.UTF_8);
        System.out.println("byteBuf的容量为:" + byteBuf.capacity());
        System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes());
        System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes());
        while (byteBuf.isReadable()) {
            System.out.println((char) byteBuf.readByte());
        }
        byteBuf.discardReadBytes(); //丢弃已读的字节空间
        System.out.println("byteBuf的容量为:" + byteBuf.capacity());
        System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes());
        System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes());
    }
}

5.2.4 clear()

#通过clear() 重置readerIndex 、 writerIndex 为0,需要注意的是,重置并没有删除真正的内容BEFORE clear()
+-------------------+------------------+------------------+ 
| discardable bytes |  readable bytes  |  writable bytes  |
+-------------------+------------------+------------------+
| 					|				   |                  |
0 	<= 	readerIndex 	<= 	writerIndex 	<= 	capacity
AFTER clear()
+---------------------------------------------------------+
|         writable bytes (got more space)  				  |
+---------------------------------------------------------+
| 														  |
0  =  readerIndex  =  writerIndex  <= 	 capacity
package com.jeaw.netty.byteBuf;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;

public class TestByteBuf04 {
    public static void main(String[] args) {
        ByteBuf byteBuf = Unpooled.copiedBuffer("hello world",
                CharsetUtil.UTF_8);
        System.out.println("byteBuf的容量为:" + byteBuf.capacity());
        System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes());
        System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes());

        byteBuf.clear(); //重置readerIndex 、 writerIndex 为0

        System.out.println("byteBuf的容量为:" + byteBuf.capacity());
        System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes());
        System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes());
    }
}

5.3 ByteBuf 使⽤模式

根据存放缓冲区的不同分为三类:

  • 堆缓冲区(HeapByteBuf),内存的分配和回收速度⽐较快,可以被JVM⾃动回收,缺点是,如
    果进⾏socket的IO读写,需要额外做⼀次内存复制,将堆内存对应的缓冲区复制到内核Channel
    中,性能会有⼀定程度的下降。
    由于在堆上被 JVM 管理,在不被使⽤时可以快速释放。可以通过 ByteBuf.array() 来获取 byte[] 数
    据。
  • 直接缓冲区(DirectByteBuf),⾮堆内存,它在对外进⾏内存分配,相⽐堆内存,它的分配和回
    收速度会慢⼀些,但是将它写⼊或从Socket Channel中读取时,由于减少了⼀次内存拷⻉,速度⽐
    堆内存块。
  • 复合缓冲区,顾名思义就是将上述两类缓冲区聚合在⼀起。Netty 提供了⼀个 CompsiteByteBuf,
    可以将堆缓冲区和直接缓冲区的数据放在⼀起,让使⽤更加⽅便。
//默认使⽤的是DirectByteBuf,如果需要使⽤HeapByteBuf模式,则需要进⾏系统参数的设置
System.setProperty("io.netty.noUnsafe", "true"); //netty中IO操作都是基于Unsafe完成的
//ByteBuf 的分配要设置为⾮池化,否则不能切换到堆缓冲器模式
serverBootstrap.childOption(ChannelOption.ALLOCATOR,
UnpooledByteBufAllocator.DEFAULT);

5.4 ByteBuf 的分配

Netty 提供了两种 ByteBufAllocator 的实现,分别是:

  • PooledByteBufAllocator,实现了 ByteBuf 的对象的池化,提⾼性能减少并最⼤限度地减少内存
    碎⽚。
  • UnpooledByteBufAllocator,没有实现对象的池化,每次会⽣成新的对象实例。
//通过ChannelHandlerContext获取ByteBufAllocator实例
ctx.alloc();
//通过channel也可以获取
channel.alloc();
//Netty默认使⽤了PooledByteBufAllocator
//可以在引导类中设置⾮池化模式
serverBootstrap.childOption(ChannelOption.ALLOCATOR,
UnpooledByteBufAllocator.DEFAULT);
//或通过系统参数设置
System.setProperty("io.netty.allocator.type", "pooled");
System.setProperty("io.netty.allocator.type", "unpooled");

5.5 ByteBuf的释放

ByteBuf如果采⽤的是堆缓冲区模式的话,可以由GC回收,但是如果采⽤的是直接缓冲区,就不受GC的
管理,就得⼿动释放,否则会发⽣内存泄露。
关于ByteBuf的释放,分为⼿动释放⾃动释放

5.5.1 ⼿动释放

⼿动释放,就是在使⽤完成后,调⽤ReferenceCountUtil.release(byteBuf); 进⾏释放。
通过release⽅法减去 byteBuf 的使⽤计数,Netty 会⾃动回收 byteBuf 。
示例

/**
     * 获取客户端发来的数据
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws
            Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        String msgStr = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("客户端发来数据:" + msgStr);
        //释放资源
        ReferenceCountUtil.release(byteBuf);
    }

⼿动释放可以达到⽬的,但是这种⽅式会⽐较繁琐,如果⼀旦忘记释放就可能会造成内存泄露。

5.5.2 ⾃动释放

⾃动释放有三种⽅式,分别是:⼊站的TailHandler、继承SimpleChannelInboundHandler、HeadHandler的出站释放。

5.5.2.1 TailHandler

Netty的ChannelPipleline的流⽔线的末端是TailHandler,默认情况下如果每个⼊站处理器Handler都把
消息往下传,TailHandler会释放掉ReferenceCounted类型的消息。

 /**
     * 获取客户端发来的数据
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws
            Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        String msgStr = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("客户端发来数据:" + msgStr);
        //向客户端发送数据
        ctx.writeAndFlush(Unpooled.copiedBuffer("ok", CharsetUtil.UTF_8));
        ctx.fireChannelRead(msg); //将ByteBuf向下传递
    }

在DefaultChannelPipeline中的TailContext内部类会在最后执⾏:

	@Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        onUnhandledInboundMessage(ctx, msg);
    }

    //最后会执⾏
    protected void onUnhandledInboundMessage(Object msg) {
        try {
            logger.debug(
                    "Discarded inbound message {} that reached at the tail of the
                    pipeline." +
                    "Please check your pipeline configuration.", msg);
        } finally {
            ReferenceCountUtil.release(msg); //释放资源
        }
    }

需要注意的是,如果没有进⾏向下传递,那么在TailHandler中是不会进⾏释放操作的。

5.5.2.2 SimpleChannelInboundHandler

当ChannelHandler继承了SimpleChannelInboundHandler后,在SimpleChannelInboundHandler的
channelRead()⽅法中,将会进⾏资源的释放,我们的业务代码也需要写⼊到channelRead0()中。

//SimpleChannelInboundHandler中的channelRead()
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws
            Exception {
        boolean release = true;
        try {
            if (acceptInboundMessage(msg)) {
                @SuppressWarnings("unchecked")
                I imsg = (I) msg;
                channelRead0(ctx, imsg);
            } else {
                release = false;
                ctx.fireChannelRead(msg);
            }
        } finally {
            if (autoRelease && release) {
                ReferenceCountUtil.release(msg); //在这⾥释放
            }
        }
    }

使⽤:

package com.jeaw.netty.byteBuf;

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 MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws
            Exception {
        System.out.println("接收到服务端的消息:" +
                msg.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 向服务端发送数据
        String msg = "hello";
        ctx.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
5.5.2.3 HeadHandler

出站处理流程中,申请分配到的 ByteBuf,通过 HeadHandler 完成⾃动释放。

出站处理⽤到的 Bytebuf 缓冲区,⼀般是要发送的消息,通常由应⽤所申请。在出站流程开始的时候,
通过调⽤ ctx.writeAndFlush(msg),Bytebuf 缓冲区开始进⼊出站处理的 pipeline 流⽔线 。

在每⼀个出站Handler中的处理完成后,最后消息会来到出站的最后⼀棒 HeadHandler,再经过⼀轮复
杂的调⽤,在flush完成后终将被release掉。

示例:

package com.jeaw.netty.byteBuf;

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 MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws
            Exception {
        System.out.println("接收到服务端的消息:" +
                msg.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 向服务端发送数据
        String msg = "hello";
        ctx.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
    }

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

执⾏⽅法调⽤链:

在这里插入图片描述

5.5.3 ⼩结

  • ⼊站处理流程中,如果对原消息不做处理,调⽤ ctx.fireChannelRead(msg) 把原消息往下传,由
    流⽔线最后⼀棒 TailHandler 完成⾃动释放。
  • 如果截断了⼊站处理流⽔线,则可以继承 SimpleChannelInboundHandler ,完成⼊站ByteBuf ⾃
    动释放。
  • 出站处理过程中,申请分配到的 ByteBuf,通过 HeadHandler 完成⾃动释放。
  • ⼊站处理中,如果将原消息转化为新的消息并调⽤ ctx.fireChannelRead(newMsg)往下传,那必须
    把原消息release掉;
  • ⼊站处理中,如果已经不再调⽤ ctx.fireChannelRead(msg) 传递任何消息,也没有继承
    .netty.byteBuf;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值