导图社区 Netty思维导图
Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。本图概括了常用IO操作,三大IO模型及其原理、多路复用IO模型、Netty原理及使用、TCP粘包拆包等。
编辑于2021-09-05 01:46:31Netty
用于快速开发高性能可扩展服务端和客户端的异步事件驱动的网络应用框架 ---场景--- 1. 互联网分布式网络服务框架:Dubbo 的 RPC 2. 网络游戏:地图服务的高性能通信 3. 大数据:Hadoop 的 RPC, Akka, Flink, Spark
IO
Stream
InputStream
FileInputStream
ByteArrayInputStream
Read
FileReader
CharArrayReader
StringReader
OutputStream
FileOutputStream
ByteArrayOutputStream
Writer
FileWriter
CharArrayWriter
StringWriter
序列化
ObjectInputStream
ObjectOutputStream
Filter
FilterInputStream
BufferedInputStream
FilterOutputStream
BufferedOutputStream
IO 模型
---NIO vs. BIO--- 1. BIO 以流的形式处理数据,NIO 以块的形式处理数据,块 IO 比流 IO 效率高 2. BIO 阻塞,NIO 非阻塞 3. BIO 基于字节流和字符流操作,NIO 基于 Channel 和 Buffer 操作,数据从 Channel 读取到 Buffer,或从 Buffer 写入 Channel。Selector 可以监听多个 Channel,即一个线程可以监听多个客户端连接 4. Buffer 和 Channel 之间的数据流是双向的 ---BIO、NIO、AIO 对比--- IO 模型: 同步阻塞 - 同步非阻塞(多路复用) - 异步非阻塞 编程难度:简单 - 复杂 - 复杂 可靠性: 差 - 好 - 好 吞吐量: 低 - 高 - 高
BIO
Blocking IO,Java 传统 IO 模型 BIO 面向流处理数据 一个连接一个线程 -> 连接不做任何事情时,线程开销是不必要的 连接数小,固定架构,程序简单,对服务器资源要求较高,JDK1.4 前唯一的选择
基本流程
1. 服务器端启动一个 ServerSocket 2. 客户端启动 Socket 对服务器进行通信,默认情况下服务器端需要对每个客户建立一个线程与之通讯。 3. 客户端发出请求后,先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝。 4. 如果有响应,客户端线程会等待请求结束后,再继续执行。
局限
1. 每个连接创建独立的线程,用于 Read 客户端数据,业务处理,Write 数据回客户端 2. 并发数较大时,创建大量线程来处理连接,系统资源占用较大 3. 连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源浪费
阻塞 I/O 服务模型
1. 每个连接需要独立线程完成输入、业务处理、输出操作,大量线程造成系统资源浪费 2. 阻塞方式获取输入数据,导致大量线程处于挂起状态而浪费
NIO
non-blocking IO, New IO 实现类在 java.nio,始于 JDK1.4 NIO 面向缓冲区,面向块编程 ---Selector、Channel、Buffer 关系--- 1. 一个线程对应一个 Selector,每个 Selector 可以多路复用 Channel(连接),线程切换到哪个 Channel 由 Event 决定,Selector 会根据不同的 Event 在各个 Channel 上切换 2. 一个 Channel 对应一个 Buffer,即一个内存块,Buffer 底层就是一个数组 3. Buffer 支持读写双向操作,可以通过 Buffer::flip() 切换 Channel 的方向(区别于只能是单向的 BIO 输入流和输出流) ---场景--- 连接数多且短的架构,比如聊天服务器,弹幕系统,编程比较复杂 HTTP2.0 基于多路复用技术,同一个连接并发处理多个请求
Buffer
缓冲区,一个可读写数据的内存块,容器对象 Channel 必须通过 Buffer 读取或写入磁盘、网络的数据 NIO 程序 <-> Buffer <-Channel-> (磁盘、网络)
类型
ByteBuffer ShortBuffer CharBuffer IntBuffer LongBuffer DoubleBuffer FloatBuffer (无 Boolean)
ByteBuffer
支持各类型的 put/get 操作,此时对应位置的类型必须保持一致,否则 BufferUnderflowException
MappedByteBuffer
可以将文件直接映射到(堆外)内存进行修改
属性
capacity - 容量,可容纳的最大数据量,Buffer 创建时设定,不可变 position - 位置,下一个要被读/写的元素的位置,每次读写操作都会改变该值 limit - 当前可达终点,不能操作操作 limit 位置的元素,可修改 mark - 标记,标识读写模式
常用方法
public ByteBuffer asReadOnlyBuffer() - 返回一个只读 Buffer public ByteBuffer hasRemaining() - 判断是否达到 position < limit
Channel
通道接口,类似于流,但区别于流: 1. 通道是读写双向,流式只读或只写 2. 通道可以实现异步读写数据 3. 必须通过 Buffer 读写数据 ---Scattering/Gathering--- read/write 方法可以接受 Buffer[],以实现 Scattering/Gathering 的效果
类型
FileChannel
用于本地文件 IO 操作 // 从通道读取数据到缓冲区 public int read(ByteBuffer dst) // 把缓冲区数据写入通道 public int write(ByteBuffer src) // 从目标通道复制数据到当前通道 public long transferFrom(ReadableByteChannel src, long position, long count) // 把当前通道的数据复制到目标通道 public long transferTo(long position, long count, WritableByteChannel target)
DatagramChannel
ServerSocketChannel
类似于 ServerSocket,在服务器端监听新客户端的 Socket 连接,只做监听,不做实际读写操作 ServerSocketChannel.open() - 获得 ServerSocketChannel 实例 ServerSocketChannel::bind(SocketAddress) - 绑定服务器端端口号 ServerSocketChannel::configureBloking(boolean) - 设置阻塞或非阻塞模式,false 表示非阻塞 ServerSocketChannel::accept() - 获得与客户端连接的 SocketChannel **监听一个 Accept 通道 ServerSocketChannel::register(Selector,int) - 注册一个选择器,并设置监听事件
SocketChannel
类似于 Socket,网络 IO 通道,负责具体读写操作 ---方法--- SocketChannel.open() - 得到一个 SocketChannel SocketChannel::configureBlocking(boolean) - 设置阻塞或非阻塞 SocketChannel::connect(SocketAddress) - 连接服务器 SocketChannel::finishConnect() - 如果 connect 失败,通过 finishConnect 完成连接 SocketChannel::write(ByteBuffer) - 往通道写入数据 SocketChannel::read(ByteBuffer) - 读取通道中的数据到 SocketChannel::register(Selector, int, Object) - 注册到选择器,并设置监听事件,Object 是共享数据 SocketChannel::close() - 关闭通道
register(selector, SelectionKey)
注册 channel 到 selector,SelectionKey 是监听事件,还可以通过第三个参数 Object 设置共享数据
Selector
选择器 1. 接受 Channel 的注册 - 单线程管理注册到 Selector 的多个 Channel,不需要每个连接创建一个线程,更不用去维护多个线程,避免了多线程切换上下文的开销 2. 监听 Channel 上发生的读/写事件,只在监听到读写事件时,进行读写操作 - 降低系统开销 3. 非阻塞 IO 的时间可以用于做其他 Channel 的 IO 操作,单线程可以管理多个输入输出通道 4. IO 操作非阻塞,避免了因 IO 阻塞导致的线程挂起,充分提高 IO 效率 5. 一个 I/O 线程处理多个客户端和读写操作,极大提示了架构性能、伸缩性、可靠性等
常用方法
open
得到一个选择器对象
select
监听所有注册的 Channel,当有 IO 操作可以进行时,将其 SelectionKey 加入到内部集合中,阻塞
select(long)
阻塞(ms)
selectNow
不阻塞,立刻返回
wakeup
唤醒 selector
selectedKeys
从内部集合中得到所有 SelectionKey,集有 IO 操作的 Channel
SelectionKey
Channel 与 Selector 之间的注册关系 ---种类--- OP_READ - 读操作,1, 1 << 0 OP_WRITE - 写操作, 4, 1 << 2 OP_CONNECT - 连接已经建立, 8, 1 << 3 OP_ACCEPT - 有新的网络连接,16,1 << 4 ---方法--- selector() - 得到与之关联的 Selector 对象 channel() - 得到与之关联的 Channel 对象 attachment() - 得到与之关联的共享数据 interestOps() - 设置或改变监听事件 isAcceptable() - 是否可 accept isReadable() - 是否可读 isWritable() - 是否可写
NIO 非阻塞网络编程原理
1. Server 打开一个 ServerSocketChannel,并注册到 Selector,监听 OP_ACCEPT 2. Client 打开一个连接 Server 的 SocketChannel,并注册到 Selector,监听 OP_READ 3. Server 通过 Selector 从 ServerSocketChannel 得到与 Client 连接的 SocketChannel,并注册到 Selector,监听 OP_READ 4. Selector 监听所有注册上来的 Channel 中发生事件的个数,得到 Selector 中 SelectionKey 集合,即可反向获得发生了事件的 SocketChannel 5. 通过得到的 Channel 处理业务
零拷贝
零拷贝是操作系统层面上的说法,只在 Kernel Buffer 之间,数据是不重复的 DMA(direct memory access) - 直接内存拷贝(不使用 CPU,Kernal Space、User Space 间的拷贝需要 CPU),从磁盘、协议栈到 Kernel Buffer 是 DMA ---好处--- 1. 更少的数据复制 2. 更少的上下文切换 3. 更少的 CPU 缓存伪共享 4. 无 CPU 校验和计算 ---mmap vs. sendFile--- 1. mmap 适合小数据读写,sendFile 适合大文件传输 2. mmap 需要 4 次上下文,3 次数据拷贝;sendFile 3 次上下文切换,最少 2 次数据拷贝 3. sendFile 可以利用 DMA 减少 CPU 拷贝,mmap 不能,必须从内核拷贝到 Socket Buffer
mmap
通过内存映射,将文件映射到内核空间,同时用户空间可以共享内核空间数据,减少网络传输时内核空间到用户空间的拷贝操作
sendFile
Linux 2.1 开始提供 数据不经过用户空间,直接从内核空间进入 SocketBuffer,同时由于与用户态无关,也减少了一次上下文切换 Linux 2.4 修改 数据不经过 SocketBuffer,直接进入 Protocol Engine(协议栈),少量(如length,offset)数据还是会进入 SocketBuffer,进一步降低了消耗
原生 NIO 缺点
1. 类库和 API 繁杂,需要熟练掌握 Selector,ServerSocketChannel,SocketChannel,ByteBuffer 2. 需要熟悉其他技能:Java 多线程,网络编程,Reactor 模式 3. 工作量和难度大:断连重连,网络闪断,半包读写,失败缓存,网络拥塞,异常流处理等 4. Epoll Bug 导致的 Selector 空轮询
AIO(NIO.2)
Asynchronous I/O 引入异步通道的概念,采用 Proactor 模式,简化程序编写,有效的请求才开启线程 连接数多且长,相册服务器,充分调用 OS 参与并发操作,编程复杂, 始于 JDK7
Reactor
反应器/分发者/通知者模式 ---核心组成--- 1. Reactor(ServiceHandler) - 监听和分发事件 2. Handlers(EventHandler) - 处理线程执行 I/O 事件要完成的实际任务 ---优势--- 1. 响应快,不必为单个同步事件所阻塞(虽然 Reactor 本身依然是同步的)避免了线程资源因挂起而浪费 2. 多路复用,最大程度避免多线程同步问题,避免多线程上线文切换的开销 3. 扩展性好,可以通过增加 Reactor 来充分利用 CPU 资源 4. 复用性好,Reactor 模型本身和具体事件无关 
单 Reactor 单线程
1. Reactor 通过 Select 监听客户端多路连接请求,收到事件后通过 dispatch 分发处理 2. 如果是建立连接的事件,则由 Acceptor 通过 accept 处理(创建一个 Handler 对象处理连接后的业务) 3. 如果不是连接事件,则分发调用对应 Handler 处理(Read -> 业务处理 -> Send) ---优点--- 模型简单,没有多线程、多进程的通信和竞争问题,全部在一个线程中完成 ---缺点--- 1. 性能存在瓶颈,只有一个线程,无法完全发挥 CPU 性能,Handler 只能同时处理一个任务,只是应 IO 型应用 2. 可靠性问题,线程终止或死循环时,整个系统不可用 ---场景--- 客户端数量有限,业务处理(CPU任务)非常快速,比如 Redis 在业务处理的时间复杂度是 O(1)
单 Reactor 多线程
1. Reactor 通过 select 监听客户端请求事件,收到事件后,通过 dispatch 分发处理 2. 如果收到连接请求的事件,由 Acceptor 通过 accpet 处理(创建一个 Handler 对象处理连接后的事务) 3. 如果收到非连接请求的事件,则由对应 Handler 响应事件(Read -> 提交任务到 Worker 线程池 -> 收到 Worker 返回 -> Send) 4. Worker 线程池分配独立线程处理业务,并将结果返回 Handler ---优点--- 充分利用多核 CPU 的处理能力 ---缺点--- 1. 多线程数据共享和访问比较复杂 2. Reactor 通过单线程处理所有事件的监听和响应,高并发场景容易出现性能瓶颈
主从 Reactor 多线程
1. Reactor 主线程 MainReactor 对象通过 select 监听连接事件,收到事件后,通过 Acceptor 的 accept 处理 2. MainReactor 将连接分配给 SubReactor(MainReactor 可以关联多个 SubReactor),SubReactor 监听连接的其他事件,并创建 Handler 与监听事件绑定 3. SubReactor 监听到事件时,相应 Handler 处理(read -> 提交业务任务给 Worker 线程池 -> 接收返回结果 -> send) 4. Worker 线程池分配独立线程处理业务,并返回结果 ---优点--- 1. MainReactor 只需要接收新的连接,由 SubReactor 完成后续业务处理 2. MainReactor 与 SubReactor 间的交互简单,MainReactor 只需将新连接传给 SubReactor,SubReactor 会直接将结果回写 Client ---缺点--- 编程复杂度较高 ---场景--- 1. Nginx 主从 Reactor 多线程 2. Memcached 主从多线程 3. Netty 主从多线程
异步模型
Netty
基于主从 Reactor 多线程模型改进而来,支持多 Reactor ---简单原理--- 1. BossGroup 线程维护一个 Selector,只关注 Accept 事件 2. 当 Accept 事件发生时,获得一个 SocketChannel,封装成 NioSocketChannel 并注册到 WorkerGroup 线程 3. WorkerGroup 通过 Selector 监听 NioSocketChannel 中的事件,事件发生时,交由 pipeline(通道)中的 Handlers 链处理业务 ---优点--- 1. 设计优雅:适用性强且统一的 API,阻塞和非阻塞的 Socket,灵活且可扩展的事件模型,可扩展的线程模型(单线程,线程池,多线程池) 2. 方便:Javadoc,用户指南,示例,无第三方依赖 3. 高性能,高吞吐,低延迟,低消耗,最小化不必要的内存复制 4. 安全:完整 SSL/TLS 及 StartTLS 支持 5. 社区活跃,版本迭代块,Bug 修复及时,更多新功能加入
Netty 模型
---详解--- 1. Netty 抽象出两个 NioEventLoopGroup 线程池,BossGroup 负责接收连接,WorkerGroup 负责网络读写 2. NioEventLoopGroup 事件循环组,包含多个事件循环 NioEventLoop 3. NioEventLoop 即不断循环执行任务的线程,对应一个 Selector,用于监听多个绑定的 socket 网络通信 4. BossGroup 下 NioEventLoop 循环执行步骤: 1. 轮询 accept 2. 处理 accept 事件,与 client 连接,生成 NioSocketChannel,并将其注册到 WorkerGroup 的 NioEventLoop 上的 Selector 3. runAllTasks 处理任务队列的任务 5. WorkerGroup 下 NioEventLoop 循环执行步骤: 1. 轮询 read, write 事件 2. 在对应 NioSocketChannel 处理 I/O 事件 3. runAllTasks 处理任务队列的任务 6. WorkerGroup 的 NioEventLoop 通过 pipeline(管道) 维护的 Handlers 链处理任务,pipeline 通过其包含的 channel 完成 I/O 操作
异步模型
异步与同步是针对调用者来说的,异步是指当调用者发送一个调用时,不需要等待调用结果的返回,可以先去做其他任务 异步通常通过状态、通知、回调来处理结果
Future-Listener
异步调用会直接返回一个 Future 对象,通过 Future 对象可以监听到任务的执行状态,从而可以触发 Callback ---常用操作--- isDone - 判断当前操作是否完成 isSuccess - 判断已完成的当前操作是否成功 getCause - 获取已完成的当前任务失败的原因(异常) isCanceled - 判断已完成的当前操作是否已被取消 addListener - 注册监听器,当操作完成,将通知指定注册的监听器执行回调逻辑
核心模块
Bootstrap
Netty 程序的引导类,用于配置整个 Netty 程序,串联各个组件 ---Netty 引导类--- Bootstrap - 客户端程序的启动引导类 ServerBootstrap - 服务端的启动引导类 ---常用方法--- 1. public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup),该方法用于服务器端,用来设置两个 EventLoop 2. public B extends AbstractBootstrap group(EventLoopGroup group),该方法用于客户端,用来设置一个 EventLoop 3. public B extends AbstractBootstrap channel(Class<? extends C> channelClass),该方法用来设置一个通道实现 4. public <T> B option(ChannelOption<T> option, T value),用来给 ServerChannel 添加配置 5. public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value),用来给接收到的通道添加配置 6. public ServerBootstrap childHandler(ChannelHandler childHandler),该方法用来设置业务处理类(自定义的handler) 7. public ChannelFuture bind(int inetPort),该方法用于服务器端,用来设置占用的端口号 8. public ChannelFuture connect(String inetHost, int inetPort),该方法用于客户端,用来连接服务器端
NioEventLoopGroup
EventLoopGroup 接口的实现类,一组 NioEventLoop 的集合,Netty 通过多个 EventLoop 同时工作来利用多核 CPU 资源 EventLoopGroup 的 next 方法实现从 Group 中获得一个 EventLoop 来处理任务 服务器编程中一般需要分为 BossEventLoopGroup 和 WorkerEventLoopGroup,一个 ServerSocketChannel 对应一个 Selector 和一个 EventLoop 线程,Boss 负责接收客户端连接并将 SocketChannel 交给 Worker 进行 I/O 处理 ---常用方法--- public NioEventLoopGroup() - 构造方法 public Futrue<?> shutdownGracefully() - 断开连接,关闭线程
NioEventLoop
异步 IO 事件循环,采用串行化设计,负责消息的读取 -> 解码 -> 处理 -> 编码 -> 发送 每个 NioEventLoop 包含一个 Selector,一个 taskQueue, Selector 可以注册监听多个 NioChannel(如SockectChannel),NioChannel 会绑定一个 pipeline,而 NioChannel 的处理交 pipeline 上的 Handlers 链实现
Feature
Netty 中所有的 IO 操作都是异步的,具体的实现就是通过 Future
ChannelFuture
Netty 中所有异步 Channel I/O 操作的返回结果
Channel
Netty 的网络通信组件,用于执行网络 I/O 操作 1. 获得当前网络通道的连接状态 2. 获得当前网络连接的配置参数(接收缓冲区大小) 3. 提供异步网络 I/O 操作(连接、读写、绑定端口),操作会立即返回一个 ChannelFutrue 4. 通过在 ChannelFuture 注册监听器,可以实现 Channel I/O 操作结果事件的回调 ---Channel 类型--- NioSocketChannel - 异步客户端 TCP Socket 连接 NioServerSocketChannel - 异步服务器端 TCP Socket 连接 NioDatagramChannel - 异步 UDP 连接 NioSctpChannel - 异步的客户端 Sctp 连接 NioSctpServerChannel - 异步的服务器端 Sctp 连接 ---SCTP--- Stream Control Transmission Protocol,兼有 TCP/UDP 两者特性
ChannelOption
Channel 的配置 SO_BACKLOG - 设置服务器可连接队列大小,对应 TCP/IP 协议 listen 函数中的 backlog 参数。服务端处理客户端连接是顺序的,同一时间点只能处理一个,不能处理的连接会放在队列等待处理,backlog 参数指定了队列大小 SO_KEEPALIVE - 设置保持活动连接状态
Selector
Netty 通过 Selector 监听多个连接的 Channel 事件从而实现 I/O 的多路复用 Selector 内部会不断查询注册上来的 Channel 是否有已就绪的 I/O 事件(可读、可写、完成连接等),从而可以简单使用一个线程管理多个 Channel 的处理
ChannelHandler
处理或拦截 I/O 操作事件的接口 通过实现 ChannelHandler 相应事件的相应方法,并将实现类注册到业务处理链 ChannelPipeline,即可实现 ChannelHandler 对 Channel 事件的响应和链式处理 ---类型--- ChannelInboundHandler - 用于处理入站 I/O 事件 ChannelOutboundHandler - 用于处理出站 I/O 事件 ChannelHandlerAdapter - 适配器 ChannelInboundHandlerAdapter - 处理入站 I/O 事件 ChannelOutboundHandlerAdapter - 处理出站 I/O 事件 ChannelDuplexHander - 处理出站和入站 I/O 事件 ---方法--- channelActive - 通道就绪事件 channelRead - 通道读取数据事件 channelReadComplete - 通道读取完毕事件 exceptionCaught - 通道发生异常事件
ChannelPipeline
ChannelHandler 的集合,用于拦截 Inbound/Outbound 事件,并交由其中 ChannelHandler 的相应事件响应做处理 ChannelPipeline 基于过滤器模式,可以完全控制事件的处理方式,以及各个 ChannelHandler 之间的交互 每个 Channel 有且仅有一个 ChannelPipeline 与之对应 ---常用方法--- ChannelPipeline addFirst(ChannelHandler ... handlers) - 把 ChannelHandler 添加到链的首位 ChannelPipeline addLast(ChannelHandler ... handlers) - 把 ChannelHandler 添加到链的末尾
ChannelHandlerContext
保存 Channel 的所有上下文信息(pipeline、channel),以及一个与之关联的 ChannelHandler 对象 ---常用方法--- ChannelFutrue close() - 关闭 Channel ChannelOutboundInvoker flush() - 刷新 ChannelFutrue writeAndFlush(Object msg) - 数据写入 ChannelPipeline 中下一个 ChannelHandler(出站)
Unpooled
Netty 提供用来操作缓冲区(Netty 的数据容器)的工具类 ---常用方法--- Unpooled::copiedBuffer(CharSequence string, Charset charset) - 通过给定数据和编码,返回一个 ByteBuf 对象 Unpooled::buffer - 创建一个指定长度的 ByteBuf 对象
ByteBuf
类似原生 NIO 中的 ByteBuffer,不需要 flip 进行反转 维护了 readerindex/writerindex, capacity 将 buffer 分成三个区间 已读区间 - 0 ~ readerindex 可读区间 - readerindex ~ writerindex 可写区间 - writerindex ~ capacity
常用 Handler
StringDecoder
字符串解码器
StringEncoder
字符串编码器
IdleStateHandler
空闲状态处理器,可以分别监听定长时间 无读、无写、无读写 的时间,并交给下一个处理做相应处理
HttpServerCodec
http 协议的编解码器
ChunkedWriteHandler
http 以块的方式写数据
HttpObjectAggregator
聚合 http 分段传输的数据
WebSocketServerProtocolHandler
将 http 协议升级为 ws 协议,保持长连接,ws 的数据以帧(Frame)形式传输
ByteToMessageDecoder
ByteBuf 解码成指定类型对象
ReplayingDecoder
扩展了 ByteToMessageDecoder 类,不需要调用 readableBytes() 判断是否有足够数据读取,内部会处理判断 ---缺点--- 1. 不是所有 ByteBuf 都支持,可能抛出 UnsupportedOperationException 2. 由于消息可能会被拆分,稍慢与 ByteToMessageDecoder
LineBasedFrameDecoder
行尾控制字符(\n或者\r\n)作为分隔符来解析数据
DelimiterBasedFrameDecoder
自定义的特殊字符作为消息的分隔符
HttpObjectDecoder
HTTP 数据的解码器
LengthFieldBasedFrameDecoder
指定长度来标识整包消息,可以借此处理粘包和拆包消息
Google Protobuf
Google 开源的轻便高效结构化数据存储协议即工具,适用于数据存储或 RPC 数据交换格式,以 message 管理数据 1. 跨语言 2. 高效、高性能、高可靠 3. 通过 .proto 描述文件自动生成指定语言的代码
Netty 原生编解码器
StringEncoder/StringDecoder - 对字符串数据编解码 ObjectEncoder/ObjectDecoder - 对 Java 对象编解码 ---缺点--- 底层使用 Java 序列化技术,效率不高,无法跨语言,体积大(二进制编码的 5 倍),性能低
案例
---依赖--- <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.6.1</version> </dependency> ---.proto 描述--- syntax = "proto3"; //版本 option java_outer_classname = "StudentPOJO"; //生成的外部类名,同时也是文件名 //protobuf 使用message 管理数据 message Student { //会在 StudentPOJO 外部类生成一个内部类 Student, 他是真正发送的POJO对象 int32 id = 1; // Student 类中有 一个属性 名字为 id 类型为int32(protobuf类型) 1表示属性序号,不是值 string name = 2; } ---编译--- protoc.exe --java_out=.Student.proto (引入 StudentPOJO.class 文件至项目)
Handler & Encode
1. Encoder 与 Decoder 接收的消息类型必须与待处理的消息类型一直,否则该 handler 不会被执行 2. Decoder 执行时,需判断缓冲区数据是否足够,否则接收到的结果与期望可能不一致
出入站
在客户端,客户端到服务端是出站,服务端到客户端是入站 在服务端,客户端到服务端是入站,服务端到客户端时出站 出入站将分别按从头至尾和从尾至头顺序调用 ChannelPipeline 上的 ChannelHandler 链,以处理业务
TCP 粘包和拆包
---Nagle 算法--- 1. 将多次间隔较小且数据量小的数据,合并成一个大的数据块进行封包 - 粘包 2. 将一个数据量大的数据拆分成多个小数据块 - 拆包 ---问题--- 接收端难以分辨完整数据包,因为面向流通信的消息是没有保护边界的 ---解决方案--- 自定义协议(关键在于知道保护边界,即消息长度) + 编解码器
RPC
异步非阻塞 RPC 框架:动态代理(客户端)+ Callable(客户端) 1. 客户端的动态代理中通过调用实现了 Callable 接口的 ClientHandler 异步向与服务端连接的 SocketChannel 写入请求参数,并直接返回一个 Futrue 2. 服务端读取到参数后,通过协议解码参数并调用相应服务后,将结果写回 SocketChannel 3. 客户端读取 SocketChannel 中的结果数据后,唤醒 ClientHandler 的 call 线程,并通过 Futrue 将结果返回给调用者