netty入门讲解(NettyJava领域网络编程的王者)

一、简介

1. 课程背景

分布式系统的根基在于网络编程,而 Netty 是 Java 领域网络编程的王者。

2. 课程内容

  • 第一部分 NIO 编程,三大组件

  • 第二部分 netty 入门学习,EventLoop、Channel、Future、Pipeline、Handler、byteBuf

  • 第三部分 Netty 进阶学习,粘包半包的解决方法、协议的设计、序列化知识

  • 第四部分 Netty 常见参数的学习及优化

  • 第五部分 源码

二、NIO 基础

non Blocking IO 非阻塞 IO

1. 三大组件

1.1 Channel & Buffer

channel 有一点类似于 stream,它就是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是写入,要么是输出。

常见的 Channel 有:

  • FileChannel

  • DatagramChannel

  • socketChannel

  • ServerSocketChannel

buffer 则用来缓冲读写数据,常见的 buffer 有:

  • ByteBuffer

    • MappedByteBuffer

    • DirectByteBuffer

    • HeapByteBuffer

  • short/Int/Long/Float/Double/Char Buffer

1.2 Selector

使用多线程技术

为每个连接分别开辟一个线程,分别去处理对应的 socket 连接

netty入门讲解(NettyJava领域网络编程的王者)(1)

:exclamation: 多线程缺点

  • 内存占用高

  • 线程上下文切换成本高

  • 只适合连接数较少的场景

使用线程池技术

使用线程池,让线程池中的线程去处理连接

netty入门讲解(NettyJava领域网络编程的王者)(2)

这种方式存在以下几个问题:

  • 阻塞模式下,线程仅能处理一个连接

  • 仅适合短连接场景

使用 Selector

selector 的作用就是配合一个线程来管理多个 channel(fileChannel 因为是阻塞式的,所以无法使用 selector),获取这些 channel 上发生的事件,这些 channel 工作在非阻塞模式下,当一个 channel 中没有执行任务时,可以去执行其他 channel 中的任务。适合连接数多,但流量较少的场景。

netty入门讲解(NettyJava领域网络编程的王者)(3)

若事件未就绪,调用 selector 的 select 方法会阻塞线程,直到 channel 发生了就绪事件。这些事件就绪后,select 方法就会返回这些事件交给 thread 来处理。

2.ByteBuffer

使用案例

有一普通文本文件 data.txt 内容为

1234567890abc

使用 FileChannel 来读取文件内容

@Slf4jpublicclassTestByteBuffer{publicstaticvoidmain(String[] args) {// FileChannel// 1.输入输出流 2.RandomAccessFiletry {FileChannel fileChannel = new FileInputStream("data.txt").getChannel;// 准备缓冲区ByteBuffer buf = ByteBuffer.allocate(10);// 从 Channel 中读取数据,向 Buffer 写入int len;while ((len = fileChannel.read(buf)) != -1) {log.info("读取到的字节:{}", len);buf.flip; // 切换至读模式log.debug("输出内容为:{}", new String(buf.array, 0, len));// while (buf.hasRemaining) { // 是否还剩余数据// byte b = buf.get;// log.debug("输出内容为:{}", (char) b);// }// 切换为写模式buf.clear;}} catch (IOException e) {e.printStackTrace;}}}

2.1 ByteBuffer 使用步骤

  1. 向 buffer 写入数据,e.g. 调用 channel.read(buf)

  2. 调用 flip切换至读模式

  3. 向 buffer 读取数据,e.g. 调用 buf.get

  4. 调用 clearcompact切换至写模式

  5. 重复 1~4 步骤

2.2 ByteBuffer 结构

核心属性

字节缓冲区的父类 Buffer 中有几个核心属性,如下:

// Invariants: mark <= position <= limit <= capacityprivate int mark = -1;private int position = 0;private int limit;private int capacity;

  • capacity:缓冲区的容量。通过构造函数赋予,一旦设置,无法更改。

  • limit:缓冲区的界限。位于 limit 后的数据不可读写。缓冲的限制不能为负,并且不能大于其容量。

  • position:下一个读写位置的索引(类似 PC)。缓冲区的位置不能为负,并且不能大于 limit。

  • mark:记录当前 position 的值。position 被改变后,可以通过调用reset方法恢复到 mark 的位置。

核心方法:

put方法

  • put 方法可以将一个数据放入缓冲区

  • 进行该操作后,position 的值会 1,指向下一个可以放入的位置。capacity = limit。

flip方法

  • flip 方法会切换对缓冲区的操作模式,由写 -> 读 / 读 -> 写

  • 进行该操作后

    • 如果是 写模式 -> 读模式,position = 0,limit 指向最后一个元素的下一个位置,capacity 不变

    • 如果是读 -> 写,则恢复为 put 方法中的值

get方法

  • get方法会读取缓冲区中的一个值

  • 进行该操作后,position 会 1,如果超过了 limit 则会抛出异常

  • 注意:get(i)方法不会改变 position 的值

rewind方法

  • 该方法只能在读写模式下使用

  • rewind方法后,会恢复 position、limit 和 capacity 的值,变为进行 get 前的值

clean方法

  • clean 方法会将缓冲区中的各个属性恢复为最初的状态,position = 0,capacity = limit

  • 此时,缓冲区的数据依然存在,处于“被遗忘”状态,下次进行写操作时会覆盖这些数据

markreset方法

  • mark 方法会将 position 的值保存到 mark 属性中

  • reset 方法会将 position 的值改为 mark 中保存的值

compact方法

此方法为 ByteBuffer 的方法,而不是 Buffer 的方法

  • compact 会把未读完的数据向前压缩,然后切换到写模式

  • 数据前移后,原位置的值并未清零,写时会覆盖之前的值

2.2 ByteBuffer 结构

ByteBuffer 有以下重要属性

  • capacity

  • position

  • limit

刚开始

netty入门讲解(NettyJava领域网络编程的王者)(4)

写模式下,position 是写入位置,limit 等于容量,下图表示写入了 4 个字节后的状态。

netty入门讲解(NettyJava领域网络编程的王者)(5)

flip 动作发生后,position 切换为读取位置,limit 切换为读取限制。

netty入门讲解(NettyJava领域网络编程的王者)(6)

读取 4 个 byte 后,状态:

netty入门讲解(NettyJava领域网络编程的王者)(7)

clear 动作发生后,状态变为一开始。

compact 方法,是把未读完的部分向前压缩,然后切换至写模式。

netty入门讲解(NettyJava领域网络编程的王者)(8)

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com