Netty——Handler链调用机制及编解码器
文章目录
一、Netty的入站和出站机制
1.1 概述
ChannelHandler是处理入站和出站数据的应用程序逻辑的容器。实现了ChannelInboundHandler接口或ChannelInboundHandlerAdapter后可以接收入站事件和数据,这些数据会被业务逻辑处理。当要给客户端发送响应时,也可以从ChannelInboundHandler冲刷数据。ChannelOutboundHandler原理一样,用来处理出站数据。
ChannelPipeline是提供了ChannelHandler链的容器。以客户端应用程序为例,如果事件的运动方向是从客户端到服务端的,则这些事件为出站的,即客户端发送给服务端的数据会通过pipeline中的一系列ChannelOutboundHandler,并被这些Handler处理,反之则称为入站的。
对于服务器端和客户端而言,接收数据的过程就是入站,发送数据的过程就是出站。
接收数据是入站,因此数据解码是InboundHandler;发送数据是出站,需要将数据编码,因此为OutboundHandler。
- Handler关系图
1.2 Handler 编解码器
当Netty发送或者接收一个消息时,就会发生一次数据转换。入站消息会被解码,从字节转换为另一种格式(如java对象);如果是出站消息,它会被编码成字节。
Netty的编解码器都实现了ChannelInboundHandler或者ChannelOutboundHandler接口,实现了接口的这些类都重写了channelRead方法。
对于每个从入站Channel读取的消息,channelRead会被调用,它会调用由解码器提供的decode()方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。
1.2.1 ByteToMessageDecoder
由于不知道远程节点是否会一次性发送一个完整的信息,tcp有可能出现粘包、拆包的问题,这个类会对入站数据进行缓冲,直到它准备好被处理。
- decode()方法会根据接收到的数据被调用多次,直到确定没有新的元素被添加到 list,或者是 ByteBuf 没有更多的可读字节为止。
- 此时,如果 List out不为空,就会将 List 的内容传递给下一个 Handler 进行处理,该处理器的方法同样会被调用多次。
- 需要注意的是,编解码过程中的数据类型必须一致,否则将跳过Handler,直接将字节码内容读出。
public class ToIntegerDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() >= 4) {
out.add(in.readInt());
}
}
}
- 每次入站从ByteBuf中读取4字节,将其解码为一个int,然后将它添加到下一个List中。
- 当没有更多元素可以被添加到该List中时,它的内容将会被发送给下一个ChannelInboundHandler。
- int在被添加到List中时,会被自动装箱为Integer。
- 在调用readInt()方法前必须验证所输入的ByteBuf是否具有足够的数据。
1.2.2 ReplayingDecoder
ReplayingDecoder扩展了ByteToMessageDecoder类,使用这个类,不必调用readableBytes()方法,参数S指定了用户状态管理的类型,其中Void代表不需要状态管理。
1.2.3 HttpObjectDecoder
一个HTTP数据的解码器。
二、Handler链调用机制
要求:
2.1 服务端
- MyServerInitializer
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 自定义入站解码器
pipeline.addLast(new ByteToLongDecoder());
// 自定义出站编码器
pipeline.addLast(new LongToByteEncoder());
pipeline.addLast(new MyServerHandler());
}
}
- ByteToLongDecoder
public class ByteToLongDecoder extends ByteToMessageDecoder {
/**
* decode 方法会根据接收到的数据,被调用多次,直到确定没有新的元素被添加到 list,或者是 ByteBuf 没有更多的可读字节为止
* 如果 List out不为空,就会将 List 的内容传递给下一个 Handler 进行处理,该处理器的方法也会被调用多次
* @param ctx 上下文对象
* @param in 入站的 ByteBuf
* @param out list 集合,将解码后的数据传给下一个 Handler
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//因为 long 为 8 个字节,所以需要 8 个字节才能读取成一个 long 类型的数据
System.out.println("ByteToLongDecoder:入栈数据被解码");
if (in.readableBytes() >= 8){
out.add(in.readLong());
}
}
}
- MyServerHandler
public class MyServerHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println(ctx.channel().remoteAddress() + " " + msg);
ctx.writeAndFlush(98765L);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
2.2 客户端
- MyClientInitializer
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 自定义出站编码器
pipeline.addLast(new LongToByteEncoder());
// 自定义入站解码器
pipeline.addLast(new ByteToLongDecoder());
pipeline.addLast(new MyClientHandler());
}
}
- LongToByteEncoder
public class LongToByteEncoder extends MessageToByteEncoder<Long> {
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
System.out.println("LongToByteEncoder: 出站数据 msg = " + msg);
out.writeLong(msg);
}
}
- MyClientHandler
public class MyClientHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("服务器的ip=" + ctx.channel().remoteAddress() + " 收到消息:" + msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("发送数据");
ctx.writeAndFlush(123456L);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
2.3 结果
- 客户端调用顺序
- 服务端调用顺序