1.Channel、 EventLoop 和 ChannelFuture的关系
- EventLoop 定义了 Netty 的核心抽象, 用于处理连接的生命周期中所发生的事件
- 一个 EventLoopGroup 包含一个或者多个 EventLoop
- 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定
- 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理
- 一个 Channel 在它的生命周期内只注册于一个 EventLoop
- 一个 EventLoop 可能会被分配给一个或多个 Channel
- 一个给定 Channel 的 I/O 操作都是由相同的 Thread 执行的, 实际
上消除了对于同步的需要 - Netty 中所有的 I/O 操作都是异步的。因为一个操作可能不会
立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此, Netty 提供了
ChannelFuture 接口,其 addListener()方法注册了一个 ChannelFutureListener,以
便在某个操作完成时(无论是否成功)得到通知
2. ChannelHandler的执行顺序
- 由ChannelPipeline控的添加顺序控制
bootstrap.handler(new ChannelInitializer<SocketChannel>() {//ChannelInitializer是用于配置通道
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new SDataEncoder());//出站
ch.pipeline().addLast(new SDataDecoder());//入站
ch.pipeline().addLast(new SIdleStateHandler(0, 0, SIdleTime));//in
ch.pipeline().addLast(clientDataHandler);//in
}
});
如这里代码,入站的顺序是SDataDecoder(解密)–》clientDataHandler(客户端数据处理)
public class SDataDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//在入栈解码器中,需要将入的in数据,转换成我们想要的数据对象outObject
//然后把这个数据添加到out数组里面
out.add(outObject)
//完成后,调用,让流程走向下个channelhandler
ctx.fireChannelReadComplete();
}
}
//SDataDecoder 的下个channelhandler,如下面这个,就需要指定为解码后的数据类型,
public class ClientDataHandlerextends SimpleChannelInboundHandler<outObject> {
//在这个方法中,就能处理我们想要服务器返回的入站的数据了
@Override
protected void channelRead0(ChannelHandlerContext ctx, SMessage s) {
}
3. 粘包、拼包原因
由服务器发送的消息可能会被分块接收,也可能分块发送。 也就是说,如果服务器发送了 5 字节, 那么不
能保证这 5 字节会被一次性接收。 即使是对于这么少量的数据, channelRead0()方法也可能
会被调用两次,第一次使用一个持有 3 字节的 ByteBuf(Netty 的字节容器),第二次使用一个
持有 2 字节的 ByteBuf。作为一个面向流的协议, TCP 保证了字节数组将会按照服务器发送它们的顺序被接收。所以只要按照特定的长度或者分隔符处理,就能拼接到完整的数据包
设计时,不同的消息除了划分不同的消息类型,还需要针对每个消息一个唯一的消息id,不然同类型消息,只按照消息类型接收,可能会出现发送一次请求,同时受到多条回复(其他线程也发送了类似的消息)
4. ChannelHandler 的典型用途包括
- 将数据从一种格式转换为另一种格式;
- 提供异常的通知;
- 提供 Channel 变为活动的或者非活动的通知;
- 提供当 Channel 注册到 EventLoop 或者从 EventLoop 注销时的通知;
- 提供有关用户自定义事件的通知。
拦截过滤器 ChannelPipeline 实现了一种常见的设计模式—拦截过滤器(Intercepting Filter)。 UNIX 管道是另外一个熟悉的例子: 多个命令被链接在一起,其中一个命令的输出端将连
接到命令行中下一个命令的输入端。
5. chanel 的是线程安全的
Netty 的 Channel 实现是线程安全的,因此你可以存储一个到 Channel 的引用,并且每当
你需要向远程节点写数据时,多个线程都使用这个 Channel写数据,都是没问题的
6. EventLoop的是线程管理
Netty线程模型的卓越性能取决于对于当前执行的Thread的身份的确定 ,确定它是否是分配给当前Channel以及它的EventLoop的那一个线程(因为当前不同的线程会抢用cpu)。如果(当前)调用线程正是支撑 EventLoop 的线程, 那么所提交的代码块将会被(直接)执行。否则,EventLoop 将调度该任务以便稍后执行,并将它放入到内部队列中。当 EventLoop下次处理它的事件时, 它会执行队列中的那些任务/事件。这也就解释了任何的 Thread 是如何与 Channel 直接交互而无需在 ChannelHandler 中进行额外同步的
7. EventLoop/线程的分配
1. 异步传输
一旦一个 Channel 被分配给一个 EventLoop, 它将在它的整个生命周期中都使用这个EventLoop(以及相关联的 Thread)。请牢记这一点,因为它可以使你从担忧你的 ChannelHandler 实现中的线程安全和同步问题中解脱出来。
2. 阻塞传输
但是, 正如同之前一样, 得到的保证是每个 Channel 的 I/O 事件都将只会被一个 Thread(用于支撑该 Channel 的 EventLoop 的那个 Thread) 处理。
8. 引导
引导客户端:
代码示例:
引导服务器:
代码示例:
9. 使用 Netty 的 ChannelOption 和属性
10. 关闭
需要关闭 EventLoopGroup, 它将处理任何挂起的事件和任务,并且随后
释放所有活动的线程。这就是调用 EventLoopGroup.shutdownGracefully()方法的作用。这个方法调用将会返回一个 Future,这个 Future 将在关闭完成时接收到通知。需要注意的是,shutdownGracefully()方法也是一个异步的操作,所以你需要阻塞等待直到它完成,或者向所返回的 Future 注册一个监听器以在关闭完成时获得通知
11. 解码器
- 抽象类 ByteToMessageDecoder
虽然 ByteToMessageDecoder 使得可以很简单地实现这种模式,但是你可能会发现,在调用 readInt()方法前不得不验证所输入的 ByteBuf 是否具有足够的数据有点繁琐。ReplayingDecoder,它是一个特殊的解码器,以少量的开销消除了这个步骤。
- 抽象类 ReplayingDecoder
和之前一样,从ByteBuf中提取的int将会被添加到List中。如果没有足够的字节可用,这个readInt()方法的实现将会抛出一个Error,其将在基类中被捕获并处理。当有更多的数据可供读取时,该decode()方法将会被再次调用 - 抽象类 MessageToMessageDecoder
12. 编码器
抽象类 MessageToByteEncoder
抽象类 MessageToMessageEncoder
13.空闲的连接和超时
14.解码基于分隔符的协议和基于长度的协议
基于分隔符的(delimited) 消息协议使用定义的字符来标记的消息或者消息段(通常被称为帧)的开头或者结尾。
如果你正在使用除了行尾符之外的分隔符分隔的帧,那么你可以以类似的方式使用 DelimiterBasedFrameDecoder,只需要将特定的分隔符序列指定到其构造函数即可
基于长度的协议通过将它的长度编码到帧的头部来定义帧,而不是使用特殊的分隔符来标记
它的结束。
你将经常会遇到被编码到消息头部的帧大小不是固定值的协议。为了处理这种变长帧,你可以使用 LengthFieldBasedFrameDecoder, 它将从头部字段确定帧长,然后从数据流中提取指定的字节数