1. 基础概念
1.1 阻塞和非阻塞
在访问数据的时候,数据没有准备好时的处理方式;
- 阻塞 : 等待数据处理好后才开始处理其他的事情;
- 非阻塞 : 直接返回,去处理其他事情;
1.2 同步和异步
基于应用程序和操作系统处理IO事件所采用的方式。比如:
- 同步:应用程序直接参与IO读写的操作;
- 异步:所有的IO读写都交给操作系统去处理,应用程序只需要等待通知;
1.3 BIO和NIO
| IO模型 | BIO | NIO |
|---|---|---|
| 通信 | 面向流(乡村公路) | 面向缓冲(高速公路,多路复用技术) |
| 处理 | 阻塞IO(多线程) | 非阻塞IO(反应堆Reactor) |
| 触发 | 无 | 选择器(轮询机制) |
2. IO模型
常见IO模型
- 同步阻塞 I/O(BIO)
- 同步非阻塞 I/O(NIO)
- I/O 多路复用
- 信号驱动 I/O (【不怎么用】)
- 异步 I/O (【不怎么用】)
Reactor线程模型
单线程模型
I/O 操作(包括连接建立、数据读写、事件分发等),都是由一个线程完成的;
缺点:
- 连接数有限,CPU很容易打满;
- 线程在处理I/O事件时,Select 无法建立连接,分发事件等操作;
多线程模型
\
- Reactor 多线程模型将业务逻辑交给多个线程进行处理;
- 多线程模型其他的操作与单线程模型是类似的,例如读取数据依然保留了串行化的设计。当客户端有数据发送至服务端时,Select 会监听到可读事件,数据读取完毕后提交到业务线程池中并发处理
主从模型
- 主从多线程模型由多个 Reactor 线程组成,每个 Reactor 线程都有独立的 Selector 对象。
- MainReactor 仅负责处理客户端连接的 Accept 事件,连接建立成功后将新创建的连接对象注册至 SubReactor。
- SubReactor 分配线程池中的 I/O 线程与其连接绑定,它将负责连接生命周期内所有的 I/O 事件
3. 零拷贝
0拷贝是指,在数据操作的时候,不需要将数据从一个内存位置拷贝到另一个内存位置;
即数据操作,避免不必要的内存拷贝就是0拷贝;
常规拷贝
0拷贝
DMA :DMA是指外部设备不通过CPU而直接与系统内存交换数据的接口技术。
4. TCP 粘包、拆包
4.1 问题由来
TCP传输面向流,数据包没有边界;客户端可能将一个大包拆分成小包进行发送,也可能将多个报文合并成一个大的报文发送。故产生了拆包粘包;
Nagle算法:1984 年被福特航空和通信公司定义为 TCP/IP 拥塞控制方法。它主要用于解决频繁发送小数据包而带来的网络拥塞问题;
Linux 提供的 TCP_NODELAY 参数禁用 Nagle 算法。Netty 中为了使数据传输延迟最小化,就默认禁用了 Nagle 算法,这一点与 Linux 操作系统的默认行为是相反的。
4.2 拆包/粘包的解决方案
消息长度固定
缺点:大小不好固定(server 和client 端大小需一致);
特定分隔符
缺点:内容中有相似的字符,解析会有问题;
消息长度 + 消息内容
最常用协议之一;消息头还可以放置其他东西(协议、版本号、算法等)
4.3 自定义协议
通用协议设计
即通信双方约定好的暗语;市面上已有的通用通信协议:HTTP、HTTPS、FTP等;
通用的协议示例
+---------------------------------------------------------------+
| 魔数 2byte | 协议版本号 1byte | 序列化算法 1byte | 报文类型 1byte |
+---------------------------------------------------------------+
| 状态 1byte | 保留字段 4byte | 数据长度 4byte |
+---------------------------------------------------------------+
| 数据内容 (长度不定) |
+---------------------------------------------------------------+
Netty 如何实现自定义协议
Netty 常用编码器:
- MessageToByteEncoder 对象编码成字节流;
- MessageToMessageEncoder 一种消息类型编码成另外一种消息类型。
Netty常用解码器:
- ByteToMessageDecoder/ReplayingDecoder 将字节流解码为消息对象;
- MessageToMessageDecoder 将一种消息类型解码为另外一种消息类型。
编接码器分为1次编解码器和2次编解码器;
- 一次解码器用于
解决 TCP 拆包/粘包问题,按协议解析后得到的字节数据; - 二次解码器用于解决字节数据到对象模型之间的转化;
- 一次编解码器:MessageToByteEncoder/ByteToMessageDecoder
- 二次编解码器:MessageToMessageEncoder/MessageToMessageDecoder
Netty 支持哪些常用的解码器?
固定长度解码器
FixedLengthFrameDecoder
public class EchoServer {
public void startEchoServer(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new FixedLengthFrameDecoder(10));
ch.pipeline().addLast(new EchoServerHandler());
}
});
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("Receive client : [" + ((ByteBuf) msg).toString(CharsetUtil.UTF_8) + "]");
}
}
特殊分隔符解码器
DelimiterBasedFrameDecoder
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ByteBuf delimiter = Unpooled.copiedBuffer("&".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(10, true, true, delimiter));
ch.pipeline().addLast(new EchoServerHandler());
}
});
长度域解码器
长度域解码器 LengthFieldBasedFrameDecoder 是解决 TCP 拆包/粘包问题最常用的解码器。 它基本上可以覆盖大部分基于长度拆包场景,开源消息中间件 RocketMQ 就是使用 LengthFieldBasedFrameDecoder 进行解码的;
+---------------------------------------------------------------+
| 魔数 2byte | 协议版本号 1byte | 序列化算法 1byte | 报文类型 1byte |
+---------------------------------------------------------------+
| 状态 1byte | 保留字段 4byte | 数据长度 4byte |
+---------------------------------------------------------------+
| 数据内容 (长度不定) |
+---------------------------------------------------------------+