在看netty的编解码的具体实现,或者自定义编解码协议之前,有必要先知道netty提供的编解码的基础实现
ByteToMessageDecoder、
- 将对方传过来的底层byteBuf二进制数据处理拆包、粘包问题解码出1个完整的ByteBuf消息(一次解码器)
MessageToMessageDecoder<I>、
- 将前方入站处理器传过来的符合指定泛型类型的消息对象解码添加到out中,并将out中的数据交给后续入站处理器处理(二次解码器)
MessageToByteEncoder<I>、
- 将后方出站处理器传过来的符合指定泛型类型的消息对象编码写到创建的byteBuf中,再将此byteBuf使用ctx来write出去(一次编码器)
- 简单的类可以参考下:ObjectEncoder类
MessageToMessageEncoder<I>
- 将后方出站处理器传过来的符合指定泛型类型的消息对象编码添加到out中,并将out中的数据交给后续出站处理器处理(二次编码器)
- 注意下:在MessageToMessageEncoder中,在调用完encode方法后,是会去尝试释放消息的,因此,如果消息是ByteBuf类型,那么需要调用一下retain()方法。这个可以参考下LengthFieldPrepender
NettyServer01
@Slf4j
public class NettyServer01 {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new CustomizeLengthFieldPrepender(
ByteOrder.BIG_ENDIAN,
1, // 长度字段占的字节数
2, // 长度调整值
true // 是否要包括长度字段所占字节数的长度
));
ch.pipeline().addLast(new EchoServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(9084);
log.info("netty server start successfully...");
try {
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
CustomizeLengthFieldPrepender
public class CustomizeLengthFieldPrepender extends LengthFieldPrepender {
public CustomizeLengthFieldPrepender(ByteOrder byteOrder, int lengthFieldLength, int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
super(byteOrder, lengthFieldLength, lengthAdjustment, lengthIncludesLengthFieldLength);
}
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
// 写2个字节长度的标识
out.add(ctx.alloc().buffer(2).writeBytes(new byte[]{(byte) 0xCA, (byte) 0xFE}));
super.encode(ctx, msg, out);
}
}
EchoServerHandler
@Slf4j
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
log.info("client registered...");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("client active...");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
String message = buf.toString(StandardCharsets.UTF_8);
log.info("收到客户端消息: {}", message);
// 在内容前面加上固定的版本号, 版本号长度为2个字节
ByteBuf byteBuf = Unpooled.buffer().writeBytes(new byte[]{(byte) 0xCA, (byte) 0xFE});
byteBuf.writeBytes(message.getBytes());
ctx.writeAndFlush(byteBuf);
} else {
ctx.fireChannelRead(msg);
}
}
}
NettyClient
@Slf4j
public class NettyClient03 {
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
ChannelFuture channelFuture = bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(
ByteOrder.BIG_ENDIAN,
1024, // 最大长度
2, // 长度字段偏移量
1, // 长度字段字节数长度
-3, // 长度调整值
5, // 从长度字段开始剥离的字节数
true // 是否立即失败
));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
String s = ((ByteBuf) msg).toString(StandardCharsets.UTF_8);
log.info("客户端收到消息: {}", s);
}
}
});
}
})
.connect("localhost", 9084);
Channel channel = channelFuture.channel();
try {
Scanner sc = new Scanner(System.in);
while (true) {
String line = sc.nextLine();
channel.writeAndFlush(Unpooled.wrappedBuffer(line.getBytes())).sync();
System.out.println();
}
} catch (Exception e) {
eventLoopGroup.shutdownGracefully();
}
}
}
测试
只处理了服务端发送给客户端的消息的解码(半包、粘包问题);
LengthFieldPrepender编码器与LengthFieldBasedFrameDecoder解码器的用法;
输入aaaa,查看控制台日志输出情况;
在编码时格式为:2字节固定标识 + 1 字节长度值 + 2字节版本标识 + 内容;
aaaa
[DEBUG] [nioEventLoopGroup-2-1] i.n.h.logging.LoggingHandler [145] - [id: 0x35197f11, L:/127.0.0.1:62534 - R:localhost/127.0.0.1:9084] WRITE: 4B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 61 |aaaa |
+--------+-------------------------------------------------+----------------+
[DEBUG] [nioEventLoopGroup-2-1] i.n.h.logging.LoggingHandler [145] - [id: 0x35197f11, L:/127.0.0.1:62534 - R:localhost/127.0.0.1:9084] FLUSH
[DEBUG] [nioEventLoopGroup-2-1] i.n.h.logging.LoggingHandler [145] - [id: 0x35197f11, L:/127.0.0.1:62534 - R:localhost/127.0.0.1:9084] READ: 9B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| ca fe 09 ca fe 61 61 61 61 |.....aaaa |
+--------+-------------------------------------------------+----------------+
[INFO ] [nioEventLoopGroup-2-1] c.zzhua.client03.NettyClient03 [48] - 客户端收到消息: aaaa
[DEBUG] [nioEventLoopGroup-2-1] i.n.h.logging.LoggingHandler [145] - [id: 0x35197f11, L:/127.0.0.1:62534 - R:localhost/127.0.0.1:9084] READ COMPLETE