在使用 Netty 构建通信系统时,很多情况下我们不会直接传输原始的字符串或 JSON,而是使用自定义二进制协议,以提高性能、节省带宽或满足系统对结构化通信的需求。
本篇带你实现一个完整的自定义协议 + 编解码器(Encoder & Decoder)方案。
一、自定义协议的设计
我们定义一个简单的消息协议结构如下:
字段名 | 类型 | 描述 |
---|---|---|
魔数 magic | 4 字节 | 协议识别码 |
版本 version | 1 字节 | 协议版本 |
消息类型 type | 1 字节 | 请求、响应、心跳等 |
数据长度 len | 4 字节 | 消息体长度 |
数据体 body | N 字节 | 具体数据(JSON、文本等) |
| magic(0xCAFE_BABE) | version(1) | type(1) | len(4) | body(N) |
二、自定义消息类 MyMessage
public class MyMessage {
private byte version;
private byte type;
private String body;
// 构造方法和 Getter/Setter 省略
}
三、编码器:MyMessageEncoder
public class MyMessageEncoder extends MessageToByteEncoder<MyMessage> {
@Override
protected void encode(ChannelHandlerContext ctx, MyMessage msg, ByteBuf out) {
byte[] bodyBytes = msg.getBody().getBytes(StandardCharsets.UTF_8);
out.writeInt(0xCAFEBABE); // 魔数
out.writeByte(msg.getVersion());
out.writeByte(msg.getType());
out.writeInt(bodyBytes.length);
out.writeBytes(bodyBytes);
}
}
四、解码器:MyMessageDecoder
public class MyMessageDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
// 保证可读长度足够一个完整的消息头
if (in.readableBytes() < 10) return;
in.markReaderIndex();
int magic = in.readInt();
if (magic != 0xCAFEBABE) {
ctx.close(); // 非法协议
return;
}
byte version = in.readByte();
byte type = in.readByte();
int len = in.readInt();
// 如果 body 未完全收到,重置读取位置等待下一次
if (in.readableBytes() < len) {
in.resetReaderIndex();
return;
}
byte[] bodyBytes = new byte[len];
in.readBytes(bodyBytes);
MyMessage message = new MyMessage();
message.setVersion(version);
message.setType(type);
message.setBody(new String(bodyBytes, StandardCharsets.UTF_8));
out.add(message); // 传给后续 handler
}
}
五、服务端启动代码(集成编解码器)
public class CustomServer {
public static void main(String[] args) throws Exception {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyMessageDecoder());
pipeline.addLast(new MyMessageEncoder());
pipeline.addLast(new SimpleChannelInboundHandler<MyMessage>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyMessage msg) {
System.out.println("接收到消息:" + msg.getBody());
// 业务逻辑处理
}
});
}
});
bootstrap.bind(9000).sync().channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
六、实际应用场景
这种自定义协议结构适用于:
-
物联网设备通信:数据结构简单、传输高效
-
RPC 框架:便于服务端快速识别消息类型、方法调用
-
游戏通信协议:可压缩字段、节省带宽
-
MQTT、CoAP 替代协议:用于私有协议标准的系统
七、总结
通过实现自定义协议的编解码器,你可以:
-
精确控制数据结构和传输方式
-
避免 JSON/XML 带来的性能瓶颈
-
实现高效、安全、可扩展的通信协议