网络编程与netty

NIO 网络编程

  • NIO 网络编程 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)

Buffer(缓冲区)

  • 缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,Channel 读取或写入的数据都必须经由 Buffer
    在这里插入图片描述在这里插入图片描述
  • ByteBuffer 支持类型化的put 和 get, put 放入的是什么数据类型,get就应该使用相应的数据类型来取出,否则可能有BufferUnderflowException 异常
  • 可以将一个普通Buffer 转成只读Buffer
  • NIO 还提供了 MappedByteBuffer, 可以让文件直接在内存(堆外的内存)中进行修改, 而如何同步到文件由NIO 来完成
  • NIO 还支持通过多个Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和 Gathering

Channel(通道)

  • 通道类似于流,但有些区别如下
    • 通道可以同时进行读写,而流只能读或者只能写
    • 通道可以实现异步读写数据
    • 通道可以从缓冲读数据,也可以写数据到缓冲
      在这里插入图片描述
  • Channel 常用的方法
    在这里插入图片描述

Selector(选择器)

  • Java 的 NIO,用非阻塞的 IO 方式,可以用一个线程使用 Selector 处理多个的客户端连接
  • 每个 Channel 以事件的方式可以注册到同一个Selector,Selector 能够检测多个注册的 Channel 上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求
    在这里插入图片描述
  • 常用方法
//得到一个选择器对象
public static Selector  open();
//返回有事件发生的 Channel 个数,如果没有发生事件的 Channel,则阻塞指定ms,然后返回 0
public int  select(long timeout);
//返回有事件发生的 Channel 对应的 SelectionKey
public Set<SelectionKey>  selectedKeys();
//阻塞,直到有 Channel 发生事件
selector.select();
//唤醒阻塞的 selector
selector.wakeup();
//不阻塞,立马返回有事件发生的 Channel 个数 or 0
selector.selectNow();

在这里插入图片描述

SelectionKey

  • Selector 不会直接返回有事件发生的 Channel,而是返回有事件发生的 Channel 对应的 SelectionKey
  • 具体的事件种类,有如下图所示的四种
    在这里插入图片描述

零拷贝

  • 零拷贝是网络编程的关键,常用的零拷贝有 mmap(内存映射) 和 sendFile
  • 零拷贝,是从操作系统的角度来说的,因为内核缓冲区之间,没有数据是重复的(只有 kernel buffer 有一份数据)
    在这里插入图片描述
  • mmap,用户空间可以共享内核空间的数据,就不用将文件内容拷贝到用户空间了,适合小数据量读写
    在这里插入图片描述
  • sendFile,内核空间中,修改后的数据直接复制到协议引擎,适合大文件传输
    在这里插入图片描述
  • NIO 使用零拷贝通过如图所示方法
    在这里插入图片描述

原生NIO存在的问题

在这里插入图片描述

线程模型

传统阻塞 I/O 服务模型

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

Reactor 模式

在这里插入图片描述

单 Reactor 单线程

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

单 Reactor 多线程

  • 其实就是将具体的业务逻辑交给线程池的线程执行
    在这里插入图片描述
    在这里插入图片描述

主从 Reactor 多线程

  • 其实就是将所有工作分成三个部分,接受连接请求并管理的Reactor 主线程 + 负责分发任务的 Reactor 子线程 + 负责执行业务逻辑的 Worker线程池
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

Netty 实现网络编程

在这里插入图片描述

Netty 的线程模型(架构)

  • Netty 主要基于主从 Reactor 多线程模型做了一定的改进,其中主从Reactor 多线程模型有多个 Reactor
  • Netty 抽象出两组线程池,类型都是 NioEventLoopGroup
    • BossGroup 专门负责接收客户端的连接,等同于 MainReactor
    • WorkerGroup 专门负责网络的读写,等同于 SubReactor
  • NioEventLoopGroup 相当于一个事件循环组, 这个组中含有多个事件循环 ,每一个事件循环是 NioEventLoop
    • 简单理解就是,NioEventLoopGroup 是一个线程池,里面包含很多 NioEventLoop,也就是一个个线程
  • NioEventLoop 表示一个不断循环的执行处理任务的线程, 每个 NioEventLoop 都有一个 selector , 用于监听绑定在其上的 socket 的网络通讯(Channel)
    • 这个不断循环,是指每次都执行相同的逻辑,但不是自己循环,而是事件触发每一次的执行

BossGroup

  • 每个BossGroup 中的 NioEventLoop 循环执行的步骤有3步
    • 轮询 accept 事件
    • 处理 accept 事件 , 与 client 建立连接 , 生成Channel , 并将其注册到 WorkerGroup 的某个 NIOEventLoop 上的 selector
    • 处理任务队列 TaskQueue 的任务 , 即 runAllTasks

WorkerGroup

  • 每个 WorkerGroup 中的 NIOEventLoop 循环执行的步骤
    • selector 轮询 read, write 事件
    • 针对每个触发 read, write 事件的 Channel,分别执行相应的 I/O 操作 和 逻辑处理
    • 处理任务队列 TaskQueue 的任务 , 即 runAllTasks

BossGroup 与 WorkerGroup

  • BossGroup 、WorkerGroup 实际上是 NioEventLoopGroup 类的实例
  • NioEventLoopGroup 是 EventLoopGroup 接口的实现类
    在这里插入图片描述
    在这里插入图片描述
  • 常用方法
//构造方法
public NioEventLoopGroup()
//断开连接,关闭线程
public Future<?> shutdownGracefully()

NioEventLoop

  • NioEventLoop 是
  • 针对 WorkerGroup 中的 NioEventLoop有如下特点
  • 每个 NioEventLoop 都有一个 selector,用于监听绑定在其上的 socket 网络通道(Channel)
  • NioEventLoop 内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由 IO 线程 NioEventLoop 负责
  • 每个 Channel 都绑定有一个自己的 ChannelPipeline
  • 每个 NioEventLoop 都有一个 TaskQueue 任务队列,可以在 handler(ChannelHandler 实现类中的方法) 中添加异步任务 or 定时任务,但是因为 NioEventLoop 内部采用串行化设计,所以任务之间是串行化执行的
  • 对于耗时较长的操作,可以自定义线程池执行,不要添加到 NioEventLoop 中,特别是高并发,或追求高吞吐量的场景下

异步模型(ChannelFuture)

  • Netty 中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture
  • 调用者并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果
  • Netty 的异步模型是建立在 future 和 callback 的之上的
    在这里插入图片描述在这里插入图片描述
  • 简单来说,使用 Netty 的API,一般都是异步操作,会返回一个 ChannelFuture 实例,通过这个实例,可以添加 Listener ,其实就是一个 callback 方法
  • 常用的 Listener 为 ChannelFutureListener
// Netty server 端绑定接受请求的端口,这个绑定是一个异步操作,通过下面步骤可以在完成绑定后输出内容
serverBootstrap.bind(port).addListener(future -> {
	if(future.isSuccess()) {
		System.out.println(newDate() + ": 端口["+ port + "]绑定成功!");
	} else{
		System.err.println("端口["+ port + "]绑定失败!");
	}
});

Bootstrap、ServerBootstrap

  • Bootstrap 意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联各个组件
  • Bootstrap 类是客户端程序的启动引导类
  • ServerBootstrap 类是服务端程序的启动引导类
  • 常见的方法如下
//该方法用于服务器端,用来设置两个 EventLoop,BossGroup 、WorkerGroup 
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
//该方法用于客户端,用来设置一个 EventLoop
public B group(EventLoopGroup group) 
//该方法用来设置一个服务器端的通道 Channel 实现
public B channel(Class<? extends C> channelClass)
//用来给 Channel 添加配置,针对 BossGroup 
public <T> B option(ChannelOption<T> option, T value)
//用来给接收到的 Channel 添加配置,针对 WorkerGroup 
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value)
//该方法用来设置业务处理类即 自定义的 ChannelHandler 实现类,针对 WorkerGroup ,还有一个 handler(ChannelHandler handler) 针对 BossGroup 
public ServerBootstrap childHandler(ChannelHandler childHandler)
//该方法用于服务器端,用来设置占用的端口号
public ChannelFuture bind(int inetPort) 
//该方法用于客户端,用来连接服务器
public ChannelFuture connect(String inetHost, int inetPort) 

ChannelOption

  • Netty 在创建 Channel 实例后,一般都通过 ChannelOption 参数设置 Channel
  • ChannelOption 可以视为一个转为配置参数,参数配置类,作为 option(ChannelOption<T> option, T value)childOption(ChannelOption<T> childOption, T value)的入参
  • 常用的配置参数
    在这里插入图片描述

Channel

  • 不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应,常用的 Channel 类型如下
NioSocketChannel,异步的客户端 TCP Socket 连接
NioServerSocketChannel,异步的服务器端 TCP Socket 连接
NioDatagramChannel,异步的 UDP 连接
NioSctpChannel,异步的客户端 Sctp 连接
NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO以及文件 IO

Selector

在这里插入图片描述

ChannelHandler 及其实现类

  • ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序
  • 对所有请求(Channel)的逻辑处理都是通过 ChannelHandler 的实现类完成的,ChannelHandler 的实现类是区分出站/入站的

出站/入站

  • 具体看请求的方向,如果方向是 Channel—>ChannelPipeline ,那就是入站
  • 如果是 ChannelPipelinel—>Channe,那就是出站
    在这里插入图片描述

ChannelHandler 实现类中的各种方法

  • ChannelHandler 实现类中的各种方法都是不同的事件触发并执行的
    • channelActive方法,通道就绪事件就会触发
    • channelRead方法,通道读取数据事件就会触发
    • channelInactive方法,通道断开事件就会触发
    • exceptionCaught方法,异常事件触发,可以用于发生异常后程序的善后处理,尽量不要影响到其它 Channel
    • userEventTriggered方法,当前的 ChannelHandler 的该方法 由 前面的 Netty 心跳检测机制 ChannelHandler 的触发
    • 还有很多,结合业务场景自定义重写

ChannelPipeline

  • ChannelPipeline 是一个 ChannelHandler 实现类的集合,它负责处理和拦截 inbound 或者outbound 的事件和操作,相当于一个贯穿 Netty 的链
  • ChannelPipeline 实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何相互交互

常用方法

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

ChannelHandlerContext

  • 一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler
  • 入站事件和出站事件在一个双向链表中,入站事件会从链表 head 往后传递到最后一个入站的 ChannelHandler,出站事件会从链表 tail 往前传递到最前一个出站的 handler,两种类型的 ChannelHandler(inbound/outbound) 互不干扰
  • 保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象
  • ChannelHandlerContext 中包含一个具体的事件处理器 ChannelHandler ,同时 ChannelHandlerContext 中也绑定了对应的 ChannelPipeline 和 Channel 的信息,方便对 ChannelHandler进行调用
  • 常用方法
//关闭通道 Channel 
ChannelFuture close()
//刷新
ChannelOutboundInvoker flush()
//将数据写到 ChannelPipeline 中当前 ChannelHandler 的下一个 ChannelHandler 然后开始处理(出站)
ChannelFuture writeAndFlush(Object msg)

在这里插入图片描述

ChannelHandler 、ChannelPipeline、NioEventLoop、Channel 关系

  • ChannelHandler 充当了处理入站和出站数据的应用程序逻辑的容器
  • 实现ChannelInboundHandler接口(或ChannelInboundHandlerAdapter),可以接收入站事件和数据,这些数据会被业务逻辑处理
  • 同理,实现ChannelOutboundHandler接口(或ChannelOutboundHandlerAdapter),可以接收出站事件和数据,这些数据会被业务逻辑处理
  • 出站/入站要关注 ChannelHandler 在 ChannelPipeline 中的顺序,一般来讲, ChannelHandler 在 ChannelPipeline 中的顺序即为代码中往 ChannelPipeline 中添加 ChannelHandler 的顺序
  • 第一个添加的就是 ChannelPipeline 双向链表的头节点(head),最后一个添加的就是 ChannelPipeline 双向链表的尾节点(tail)
  • 出/入站的 ChannelHandler 都在同一个 ChannelPipeline 的双向链表中,编解码器的 ChannelHandler 一般作为 head,和第二节点
    • 因为入站的 ChannelHandler 执行顺序是 heap —>tail,出站的 ChannelHandler 执行顺序是 tail —> heap
    • 出/入站 ChannelHandler 之间的顺序并不互相影响,它们只是在同一个 ChannelPipeline 双向链表中连续相连,但是执行还是各按各的,即双向链表中的顺序同时包含了 出站/入站的 ChannelHandler 执行顺序,看出站顺序的时候只需要忽略掉链表中的入站 ChannelHandler ,反之看入站顺序亦然
  • 因为每个 NioEventLoop 都有自己的 select ,是一对一的关系,Channel 只能注册到多个 select 中的一个,所以一个 Channel 只能对应一个 NioEventLoop
  • 每个 NioEventLoop 都有一个 ChannelPipeline,所以 一个 Channel 对应一个 ChannelPipeline
  • 总结:每一个 Channel 都会有属于自己的 NioEventLoop 和 ChannelPipeline
  • NioEventLoop 的数量是有限的,Channel 的数量远大于 NioEventLoop 数量,所以多个 NioEventLoop 是按照固定的次序,被用来处理不断新加进来的 Channel ,NioEventLoop 每次处理新加进来的 Channel 都会为其初始化一遍,然后创建新的 ChannelPipeline

Unpooled 类

  • Netty 提供一个专门用来操作缓冲区(即Netty的数据容器)的工具类
  • 常用方法
//通过给定的数据和字符编码返回一个 ByteBuf 对象(类似于 NIO 中的 ByteBuffer 但有区别)
public static ByteBuf copiedBuffer(CharSequence string, Charset charset)
  • ByteBuf 不需要 flip,可以同时写入数据和读取数据,主要有三个成员属性
    • readerindex ,指明当前 ByteBuf 已经读取到的位置, 0—readerindex 已经读取的区域
    • writerIndex,指明当前 ByteBuf 已经写入的位置,readerindex—writerIndex 为可读的区域
    • capacity,整个 ByteBuf 的容量,单位是字节

Netty心跳检测机制

编码与解码

  • 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码
  • codec(编解码器) 的组成部分有两个:decoder(解码器)和 encoder(编码器)。encoder 负责把业务数据转换成字节码数据,decoder 负责把字节码数据转换成业务数据
  • Netty提供一系列实用的编解码器,他们都实现了 ChannelInboundHadnler 或者 ChannelOutboundHandler 接口
  • 在这些类中,channelRead 方法已经被重写了
  • 以入站为例,对于从 Channel 读取的数据,channelRead 方法会被调用。随后,它将调用由解码器所提供的 decode 方法进行解码,并将已经解码的数据转发给 ChannelPipeline 中的下一个 ChannelInboundHandler
  • 常用解码器 ByteToMessageDecoder
    在这里插入图片描述
  • 其它解码器
    在这里插入图片描述
  • 常用编码器 MessageToByteEncoder
    在这里插入图片描述
  • MessageToByteEncoder、ByteToMessageDecoder 之所以能成为常用的编/解码器,是因为它支持泛型,可以很简单直接重写 encode/decode方法实现自定义的编/解码器,这对解决 粘包/拆包 问题非常重要

TCP 粘包和拆包

  • TCP是面向连接的,面向流的,提供高可靠性服务
  • 收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包
  • 这样做虽然提高了效率,但是接收端就难于分辨出完整的数据包了,因为面向流的通信是无消息保护边界的
    在这里插入图片描述
  • 解决粘包和拆包,关键就是要解决服务器端每次应该读取的数据长度的问题, 这个问题解决,就不会出现服务器多读或少读数据的问题,从而避免的TCP 粘包、拆包
    • 解决方式:使用自定义协议 + 编解码器
    • 由上面引申出在确定协议和编解码器后就不会粘包、拆包,出现粘包、拆包就是因为没有使用已存在的协议和对应的编解码器,然后又没有自定义,导致使用 Netty 接收请求时,不知道一次正确的请求数据,应该读取多少字节
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值