Fari

netty学习笔记

预备

I/O模型

Java共支持3中网络编程模型I/O模式:BIO、NIO、AIO

NIO

Channel1 —> Buffer1 Channel2 —> Buffer2 Channel3 —> Buffer3

Buffer1 —> Client1 Buffer2 —> Client2 Buffer3 —> Client3


1. 每一个Channel都会对应一个Buffer
2. Selector对应一个线程,一个线程对应多个Channel(连接)
3. 该图反映了三个Channel注册到该Selector
4. 程序切换到哪个Channel是由事件(Event)决定的
5. Selector会根据不同的事件在各个Channel上切换
6. Buffer本质是一个数组
7. 数据的读取和写入都是要通过Buffer,通过flip方法切换

##### Buffer
本质是一个可以读写的数据块,可以理解成一个容器对象(包含了一个数组),该对象提供了一组方法可以轻松的使用内存块。缓冲区对象内置了一些机制,能够追踪和记录缓冲区的状态变化情况,JDK提供了除了boolean之外的其他七种基本类型的Buffer

```java
public static void main(String[] args) {
    IntBuffer buffer = IntBuffer.allocate(3);  
    for (int i = 0; i < buffer.capacity(); i++) {        
       buffer.put(i);    
    }    
    // 读写转换(重要)
    buffer.flip();    

    // 是否还存在值
    while (buffer.hasRemaining())  {        
        // get方法内部存在一个游标        
        System.out.println(buffer.get());    
    }
}
Channel

类似于流,但可以双向读写(实际上channel是Stream中的一个属性) Channel在Java中是一个接口,常用的实现类:FileChannel、ServerSocketChannel、SocketChannel等 使用transferFrom/transterTo方法实现文件的快速拷贝 提供了MappedByteBuffer支持直接在堆外内存中修改文件,而不用操作系统多拷贝一次

本地文件写示例: file

关于Buffer和Channel的注意事项和细节
  1. ByteBuffer支持类型化put和get,例如intput,doubleput等,put放入的是什么类型,get就应该按put的顺序使用相应的类型,否则会造成BufferUnderflowException
  2. 可以将一个普通的Buffer转换成只读的Buffer
  3. NIO还童工了MappedByteBuffer,可以让文件直接在堆外内存进行修改
  4. NIO还支持通过多个Buffer即Buffer数组来完成读写操作,即Scattering和Gathering(channel的read和write方法支持传入一个buffer数组,仅此而已)
  5. 在Channel的两端都存在Buffer
Selector

能够检测到多个注册的通道上是否有事件发生(注册的时候会传入需要监听的事件ID),只有在channel有真正的读写事件时才会读写 Selector内部有一个专门存放SelectionKey集合,内部的SelectionKey代表一个发生事件的channel,同时该SelectionKey会携带发生的事件ID。调用select()方法返回所有发生事件的channel集合 file

NIO与零拷贝

java程序中,常用的零拷贝有mmap(内存映射)和sendFile 零拷贝:从操作系统角度看,是没有CPU拷贝的意思,即内核缓冲区中没有重复的数据

Netty概述

file

Netty的高性能架构设计
目前存在的线程模型
  1. 传统阻塞IO模型
  2. Reactor模式,根据Reactor的数量和处理资源池线程的数量不同,有3中典型实现
    1. 单Reactor单线程
    2. 单Reactor多线程
    3. 主从Reactor多线程,Netty线程模型主要是基于主从Reactor多线程模型,并作出一定改进
Netty的异步模型

Netty的IO操作都是异步的,包括 Bind、Write、Connect等操作都会简单地返回一个ChannelFuture(接口) 调用者并不能立即得到调用结果,而是通过 Future-Listener 机制,用户可以主动获取或者通过通知机制获取IO操作的结果 Netty的异步模型是建立在future和callback之上的,callback就是回调。Future的核心思想:假设一个方法func非常耗时,就可以再调用func的时候立马返回一个Future,后续可以通过Future取监控func的处理过程(即:Future-Listener机制)

Netty核心模块组件

file

Bootstrap、ServerBootsrp类

一个Netty应用通常由一个Bootstrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中Bootstrap类是客户端程序的启动引导类,ServerBootstrap是服务端启动引导类 例如:如果要创建一个Netty服务端,则先创建一个BossGroup和一个WorkerGroup类,然后创建一个ServerBootstrap类,在其构造器中传入上述两个Group,然后调用它的channel方法设置服务端通道实现(一般就是NioSocketChannel),再调用其他方法配置诸如pipeline等,调用bind方法设置端口号等

Future、ChannelFuture类

异步操作IO

Channel类

用于处理IO读写请求,不同的协议、不同的阻塞类型需要用不同的channel,例如TCP连接使用NioSocketChannel、UDP连接使用NioDatagramChannel等

Selector

对应Netty模型的BossGroup、WorkerGroup

ChannelHandler接口

处理IO事件或拦截IO事件,并将其转发到其ChannelPipeline中的下一个处理程序,该接口有较多实现,例如 ChannelInboundHandler用于处理入站IO事件 ChannelOutboundHandler用于处理出站IO事件 出站入站指的是如果事件运动方向是从客户端到服务端即为出站 我们通常需要继承这些类然后重写其中的部分事件方法,例如数据读取完毕等事件

Pipeline和ChannelPipeline

ChannelPipeline是一个Handler的集合,负责处理和拦截inbound或者outbound的事件和操作,相当于一个贯穿Netty的链 Netty中每个Channel有且仅有一个ChannelPipeline与之对应(双向绑定) 一个Channel包含了一个ChannelPipeLine,而ChannelPipeLine中又维护了一个由ChannelHandlerContext组成的双向链表,并且每个ChannelHandlerContext中又关联着一个ChannelHandler 出站请求从Pipeline中的第一个ChannelHandlerContext走到最后一个ChannelHandlerContext,出站反之

ChannelHandlerContext

保存了Channel相关的所有上下文信息,同时关联一个ChannelHandler对象。即ChannelHandlerContext中包含了一个具体的事件处理器ChannelHandler

EventLoopGroup接口、NioEventLoopGroup实现类

EventLoopGroup是一组EventLoop的抽象,为了利用多核CPU资源,一般有多个EventLoop同时工作,每个EventLoop维护着一个Sekector实例。 Netty一般需要两个EventLoopGroup:BoosEventLoopGroup和WorkerEventLoopGroup。BoosEventLoopGroup只处理Accept事件,WorkerEventLoopGroup处理具体的事件

Unpooled类

Netty提供了一个专门用来操作缓冲区的工具类,例如可以通过给定的数据和字符编码返回一个ByteBuf对象(类似于NIO中的ByteBuffer但是有区别) file 注:上图中的getByte方法使用错误,应该是readByte(),否则writerIndex不会移动

Netty 编解码机制

编写网络应用程序时,因为数据在网络中以二进制字节码进行传输,所以在发送接收的时候就需要编解码 Codec(编解码器)的组成分为:decoder和encoder

Netty自身提供了一些codec,例如 StringEncoder/StringDecoder:对字符串进行编/解码 ObjectEncoder/ObjectDecoder:对Java对象进行编/解码

基于上述问题,Google提出了Protobuf项目,全称 Google Protocol Buffers

Protobuf

Protobuf是一种轻便高效的结构化数据存储格式,但和json不同。 使用Protobuf编译器能自动生成代码,Protobuf是将类的定义使用.proto文件进行描述(需手动写),然后通过protoc.exe编译器根据proto文件自动生成.java文件 同时,需要在netty的pipeline中添加proto的编解码器 file

file

但是这样就有一个问题,如何传输多种类型的对象,并且接收端可以判断传输的对象类型 更新后的proto文件 file

发送端可以随机发送不同类型的对象 file

接收端根据解码器解析出的类型进行判断 file

粘包与拆包

TCP粘包和拆包基本介绍

TCP是面向连接,面向流的。收发两端都要有一一对应的socket,因此,发送端为了将多个包更有效地发送给接收端,tcp使用了优化方法(Nagle算法),将多次间隔较小且数据量较小的数据合并成为一个大的数据块,然后进行封包。这种做法虽然提高了效率,但是接收端就难以分辨出完整的数据包了,因为面向流的通信时无消息保护边界的 举例: file

解决方案

使用自定义协议 + 编解码器 解决,关键是要解决服务端每次读取数据的长度

Tags: