Netty编解码案例

wen java案例 14

本文目录导读:

Netty编解码案例

  1. 基础项目结构
  2. 协议定义
  3. 编码器实现
  4. 解码器实现
  5. 组合编解码器
  6. 服务器端实现
  7. 客户端实现
  8. 优化版本(使用JSON编解码)
  9. 测试类
  10. 使用Protobuf的版本(高级)

我将为您提供一个完整的Netty编解码案例,涵盖常见的编解码器实现方式。

基础项目结构

// 项目结构
src/main/java/com/example/netty/
├── protocol/
│   ├── Message.java          // 消息对象
│   ├── MessageType.java      // 消息类型枚举
│   └── ProtocolConstants.java // 协议常量
├── codec/
│   ├── MessageEncoder.java   // 消息编码器
│   ├── MessageDecoder.java   // 消息解码器
│   └── MessageCodec.java     // 组合编解码器
├── server/
│   └── NettyServer.java      // 服务器端
└── client/
    └── NettyClient.java      // 客户端

协议定义

package com.example.netty.protocol;
// 消息类型枚举
public enum MessageType {
    LOGIN_REQUEST(1, "登录请求"),
    LOGIN_RESPONSE(2, "登录响应"),
    CHAT_REQUEST(3, "聊天请求"),
    CHAT_RESPONSE(4, "聊天响应"),
    HEARTBEAT(5, "心跳");
    private final int code;
    private final String description;
    MessageType(int code, String description) {
        this.code = code;
        this.description = description;
    }
    public int getCode() {
        return code;
    }
    public String getDescription() {
        return description;
    }
    public static MessageType getByCode(int code) {
        for (MessageType type : MessageType.values()) {
            if (type.code == code) {
                return type;
            }
        }
        return null;
    }
}
package com.example.netty.protocol;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
// 消息对象
public class Message implements Serializable {
    private static final long serialVersionUID = 1L;
    // 消息头
    private int magicNumber;      // 魔数
    private byte version;         // 版本号
    private int messageType;      // 消息类型
    private int sequenceId;       // 序列号
    private int length;           // 消息体长度
    // 消息体
    private Map<String, Object> body = new HashMap<>();
    // 构造函数
    public Message() {}
    public Message(int magicNumber, byte version, int messageType, int sequenceId) {
        this.magicNumber = magicNumber;
        this.version = version;
        this.messageType = messageType;
        this.sequenceId = sequenceId;
    }
    // getter和setter方法
    public int getMagicNumber() { return magicNumber; }
    public void setMagicNumber(int magicNumber) { this.magicNumber = magicNumber; }
    public byte getVersion() { return version; }
    public void setVersion(byte version) { this.version = version; }
    public int getMessageType() { return messageType; }
    public void setMessageType(int messageType) { this.messageType = messageType; }
    public int getSequenceId() { return sequenceId; }
    public void setSequenceId(int sequenceId) { this.sequenceId = sequenceId; }
    public int getLength() { return length; }
    public void setLength(int length) { this.length = length; }
    public Map<String, Object> getBody() { return body; }
    public void setBody(Map<String, Object> body) { this.body = body; }
    // 便利方法
    public void put(String key, Object value) {
        body.put(key, value);
    }
    public Object get(String key) {
        return body.get(key);
    }
}
package com.example.netty.protocol;
// 协议常量
public class ProtocolConstants {
    public static final int MAGIC_NUMBER = 0xCAFEBABE;  // 魔数
    public static final byte VERSION = 1;                 // 版本号
    public static final int HEADER_LENGTH = 18;           // 头部长度 (4+1+4+4+4+1)
    // 内存分配相关
    public static final int INITIAL_CAPACITY = 256;
    public static final int MAX_FRAME_LENGTH = 1024 * 1024; // 1MB
}

编码器实现

package com.example.netty.codec;
import com.example.netty.protocol.Message;
import com.example.netty.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
// 消息编码器
public class MessageEncoder extends MessageToByteEncoder<Message> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        // 写入魔数
        out.writeInt(ProtocolConstants.MAGIC_NUMBER);
        // 写入版本号
        out.writeByte(ProtocolConstants.VERSION);
        // 写入消息类型
        out.writeInt(msg.getMessageType());
        // 写入序列号
        out.writeInt(msg.getSequenceId());
        // 序列化消息体
        byte[] bodyBytes = serializeBody(msg.getBody());
        // 写入消息体长度
        out.writeInt(bodyBytes.length);
        // 写入校验位(简单示例,实际可用CRC32等)
        out.writeByte(calculateChecksum(bodyBytes));
        // 写入消息体
        out.writeBytes(bodyBytes);
    }
    // 序列化消息体(使用Java序列化)
    private byte[] serializeBody(Map<String, Object> body) throws Exception {
        if (body == null || body.isEmpty()) {
            return new byte[0];
        }
        // 简单实现:将Map转为JSON字符串
        // 实际项目中可以使用FastJSON、Jackson等库
        StringBuilder sb = new StringBuilder();
        sb.append('{');
        for (Map.Entry<String, Object> entry : body.entrySet()) {
            sb.append('"').append(entry.getKey()).append("\":\"")
              .append(entry.getValue()).append("\",");
        }
        if (sb.length() > 1) {
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.append('}');
        return sb.toString().getBytes(StandardCharsets.UTF_8);
    }
    // 计算校验和(简单实现)
    private byte calculateChecksum(byte[] data) {
        byte checksum = 0;
        for (byte b : data) {
            checksum ^= b;
        }
        return checksum;
    }
}

解码器实现

package com.example.netty.codec;
import com.example.netty.protocol.Message;
import com.example.netty.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// 消息解码器
public class MessageDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 检查是否有足够的数据读取头部
        if (in.readableBytes() < ProtocolConstants.HEADER_LENGTH) {
            return; // 等待更多数据
        }
        // 标记当前读取位置
        in.markReaderIndex();
        // 读取魔数
        int magicNumber = in.readInt();
        if (magicNumber != ProtocolConstants.MAGIC_NUMBER) {
            // 魔数不匹配,可能是垃圾数据
            in.resetReaderIndex();
            throw new Exception("Invalid magic number: " + magicNumber);
        }
        // 读取版本号
        byte version = in.readByte();
        // 读取消息类型
        int messageType = in.readInt();
        // 读取序列号
        int sequenceId = in.readInt();
        // 读取消息体长度
        int length = in.readInt();
        // 检查消息体长度是否合理
        if (length < 0 || length > ProtocolConstants.MAX_FRAME_LENGTH) {
            throw new Exception("Invalid message length: " + length);
        }
        // 读取校验位
        byte checksum = in.readByte();
        // 检查是否有足够的数据读取消息体
        if (in.readableBytes() < length) {
            // 数据不完整,重置读取位置
            in.resetReaderIndex();
            return;
        }
        // 读取消息体
        byte[] bodyBytes = new byte[length];
        in.readBytes(bodyBytes);
        // 验证校验和
        if (checksum != calculateChecksum(bodyBytes)) {
            throw new Exception("Checksum verification failed");
        }
        // 构建消息对象
        Message message = new Message(magicNumber, version, messageType, sequenceId);
        message.setLength(length);
        // 反序列化消息体
        message.setBody(deserializeBody(bodyBytes));
        // 将解码后的消息加入输出列表
        out.add(message);
    }
    // 反序列化消息体
    private Map<String, Object> deserializeBody(byte[] data) {
        Map<String, Object> body = new HashMap<>();
        if (data.length == 0) {
            return body;
        }
        // 简单解析JSON格式(实际使用JSON库)
        String json = new String(data, StandardCharsets.UTF_8);
        json = json.substring(1, json.length() - 1); // 去掉花括号
        if (!json.isEmpty()) {
            String[] pairs = json.split(",");
            for (String pair : pairs) {
                String[] keyValue = pair.split(":");
                if (keyValue.length == 2) {
                    String key = keyValue[0].replace("\"", "");
                    String value = keyValue[1].replace("\"", "");
                    body.put(key, value);
                }
            }
        }
        return body;
    }
    // 计算校验和
    private byte calculateChecksum(byte[] data) {
        byte checksum = 0;
        for (byte b : data) {
            checksum ^= b;
        }
        return checksum;
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 捕获解码异常
        System.err.println("Decode error: " + cause.getMessage());
        cause.printStackTrace();
    }
}

组合编解码器

package com.example.netty.codec;
import io.netty.channel.CombinedChannelDuplexHandler;
// 组合编解码器
public class MessageCodec extends CombinedChannelDuplexHandler<MessageDecoder, MessageEncoder> {
    public MessageCodec() {
        super(new MessageDecoder(), new MessageEncoder());
    }
}

服务器端实现

package com.example.netty.server;
import com.example.netty.codec.MessageCodec;
import com.example.netty.protocol.Message;
import com.example.netty.protocol.MessageType;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
public class NettyServer {
    private int port;
    public NettyServer(int port) {
        this.port = port;
    }
    public void start() throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        // 添加空闲检测
                        pipeline.addLast(new IdleStateHandler(60, 0, 0));
                        // 添加编解码器
                        pipeline.addLast(new MessageCodec());
                        // 添加业务处理器
                        pipeline.addLast(new ServerHandler());
                    }
                });
            // 绑定端口,同步等待成功
            ChannelFuture future = bootstrap.bind(port).sync();
            System.out.println("Server started on port " + port);
            // 等待服务器关闭
            future.channel().closeFuture().sync();
        } finally {
            // 优雅关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    // 服务器处理器
    @ChannelHandler.Sharable
    public static class ServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("Client connected: " + ctx.channel().remoteAddress());
        }
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            if (msg instanceof Message) {
                Message message = (Message) msg;
                System.out.println("Received message: " + message);
                // 处理消息
                Message response = processMessage(ctx.channel(), message);
                // 发送响应
                if (response != null) {
                    ctx.writeAndFlush(response);
                }
            }
        }
        private Message processMessage(Channel channel, Message message) {
            MessageType type = MessageType.getByCode(message.getMessageType());
            if (type == null) {
                return null;
            }
            switch (type) {
                case LOGIN_REQUEST:
                    // 处理登录请求
                    return handleLogin(message);
                case CHAT_REQUEST:
                    // 处理聊天请求
                    return handleChat(channel, message);
                case HEARTBEAT:
                    // 处理心跳
                    return handleHeartbeat(message);
                default:
                    System.out.println("Unsupported message type: " + message.getMessageType());
                    return null;
            }
        }
        private Message handleLogin(Message message) {
            Message response = createMessage(MessageType.LOGIN_RESPONSE, message.getSequenceId());
            String username = (String) message.get("username");
            String password = (String) message.get("password");
            // 简单验证
            if ("admin".equals(username) && "123456".equals(password)) {
                response.put("code", 200);
                response.put("message", "登录成功");
                System.out.println("User login success: " + username);
            } else {
                response.put("code", 401);
                response.put("message", "登录失败");
                System.out.println("User login failed: " + username);
            }
            return response;
        }
        private Message handleChat(Channel channel, Message message) {
            Message response = createMessage(MessageType.CHAT_RESPONSE, message.getSequenceId());
            String content = (String) message.get("content");
            System.out.println("Received chat message: " + content);
            response.put("code", 200);
            response.put("message", "消息已收到");
            response.put("echo", content);
            return response;
        }
        private Message handleHeartbeat(Message message) {
            Message response = createMessage(MessageType.HEARTBEAT, message.getSequenceId());
            response.put("status", 1);
            return response;
        }
        private Message createMessage(MessageType type, int sequenceId) {
            return new Message(
                0xCAFEBABE,
                (byte) 1,
                type.getCode(),
                sequenceId
            );
        }
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();
        }
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.err.println("Server exception: " + cause.getMessage());
            cause.printStackTrace();
            ctx.close();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new NettyServer(8080).start();
    }
}

客户端实现

package com.example.netty.client;
import com.example.netty.codec.MessageCodec;
import com.example.netty.protocol.Message;
import com.example.netty.protocol.MessageType;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
public class NettyClient {
    private String host;
    private int port;
    private Channel channel;
    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }
    public void connect() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new IdleStateHandler(0, 30, 0));
                        pipeline.addLast(new MessageCodec());
                        pipeline.addLast(new ClientHandler());
                    }
                });
            // 连接服务器
            ChannelFuture future = bootstrap.connect(host, port).sync();
            channel = future.channel();
            System.out.println("Connected to server: " + host + ":" + port);
        } finally {
            // 注意:这里不关闭group,保持连接
        }
    }
    // 发送登录请求
    public void sendLogin(String username, String password) throws InterruptedException {
        Message message = createMessage(MessageType.LOGIN_REQUEST);
        message.put("username", username);
        message.put("password", password);
        if (channel != null && channel.isActive()) {
            channel.writeAndFlush(message).sync();
        }
    }
    // 发送聊天消息
    public void sendChat(String content) throws InterruptedException {
        Message message = createMessage(MessageType.CHAT_REQUEST);
        message.put("content", content);
        if (channel != null && channel.isActive()) {
            channel.writeAndFlush(message).sync();
        }
    }
    private Message createMessage(MessageType type) {
        // 生成一个简单的序列号
        int sequenceId = (int) (System.nanoTime() % 1000000);
        return new Message(0xCAFEBABE, (byte) 1, type.getCode(), sequenceId);
    }
    // 客户端处理器
    public static class ClientHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("Connected to server");
        }
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            if (msg instanceof Message) {
                Message message = (Message) msg;
                System.out.println("Received response: " + message);
                // 处理响应
                MessageType type = MessageType.getByCode(message.getMessageType());
                if (type != null) {
                    switch (type) {
                        case LOGIN_RESPONSE:
                            handleLoginResponse(message);
                            break;
                        case CHAT_RESPONSE:
                            handleChatResponse(message);
                            break;
                        case HEARTBEAT:
                            handleHeartbeatResponse(message);
                            break;
                        default:
                            System.out.println("Unknown response type");
                    }
                }
            }
        }
        private void handleLoginResponse(Message message) {
            Integer code = (Integer) message.get("code");
            String msg = (String) message.get("message");
            System.out.println("Login response - Code: " + code + ", Message: " + msg);
        }
        private void handleChatResponse(Message message) {
            Integer code = (Integer) message.get("code");
            String msg = (String) message.get("message");
            String echo = (String) message.get("echo");
            System.out.println("Chat response - Code: " + code + ", Message: " + msg + ", Echo: " + echo);
        }
        private void handleHeartbeatResponse(Message message) {
            System.out.println("Heartbeat response received");
        }
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.err.println("Client exception: " + cause.getMessage());
            cause.printStackTrace();
            ctx.close();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        NettyClient client = new NettyClient("localhost", 8080);
        client.connect();
        // 测试登录
        client.sendLogin("admin", "123456");
        Thread.sleep(2000);
        // 测试聊天
        client.sendChat("Hello Netty!");
        Thread.sleep(2000);
        // 保持连接一段时间
        TimeUnit.SECONDS.sleep(10);
    }
}

优化版本(使用JSON编解码)

package com.example.netty.codec;
import com.alibaba.fastjson.JSON;
import com.example.netty.protocol.Message;
import com.example.netty.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.LengthFieldPrepender;
// 使用LengthFieldBasedFrameDecoder和LengthFieldPrepender的版本
public class OptimizedMessageEncoder extends MessageToByteEncoder<Message> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        // 使用JSON序列化整个消息对象
        byte[] data = JSON.toJSONBytes(msg);
        out.writeBytes(data);
    }
}
// 优化后的解码器(使用LengthFieldBasedFrameDecoder处理粘包/拆包)
public class OptimizedMessageDecoder extends LengthFieldBasedFrameDecoder {
    private static final int MAX_FRAME_LENGTH = 1024 * 1024; // 1MB
    private static final int LENGTH_FIELD_OFFSET = 0;
    private static final int LENGTH_FIELD_LENGTH = 4;
    private static final int LENGTH_ADJUSTMENT = 0;
    private static final int INITIAL_BYTES_TO_STRIP = 4;
    public OptimizedMessageDecoder() {
        super(MAX_FRAME_LENGTH, 
              LENGTH_FIELD_OFFSET, 
              LENGTH_FIELD_LENGTH, 
              LENGTH_ADJUSTMENT,
              INITIAL_BYTES_TO_STRIP);
    }
    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf frame = (ByteBuf) super.decode(ctx, in);
        if (frame == null) {
            return null;
        }
        try {
            byte[] data = new byte[frame.readableBytes()];
            frame.readBytes(data);
            return JSON.parseObject(data, Message.class);
        } finally {
            frame.release();
        }
    }
}
// 对应的编码器(使用LengthFieldPrepender)
public class OptimizedMessageEncoder2 extends MessageToByteEncoder<Message> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        byte[] data = JSON.toJSONBytes(msg);
        out.writeInt(data.length);
        out.writeBytes(data);
    }
}

测试类

package com.example.netty;
import com.example.netty.codec.MessageDecoder;
import com.example.netty.codec.MessageEncoder;
import com.example.netty.protocol.Message;
import com.example.netty.protocol.MessageType;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class CodecTest {
    @Test
    public void testEncodeDecode() {
        // 创建EmbeddedChannel
        EmbeddedChannel channel = new EmbeddedChannel(new MessageDecoder());
        // 创建消息
        Message message = new Message(0xCAFEBABE, (byte) 1, MessageType.LOGIN_REQUEST.getCode(), 1001);
        message.put("username", "admin");
        message.put("password", "123456");
        // 测试编码
        ByteBuf buffer = Unpooled.buffer();
        MessageEncoder encoder = new MessageEncoder();
        try {
            // 手动执行编码(简化测试)
            encoder.encode(null, message, buffer);
            // 测试解码
            channel.writeInbound(buffer);
            // 读取解码结果
            Message decoded = channel.readInbound();
            assertNotNull(decoded);
            assertEquals(message.getMagicNumber(), decoded.getMagicNumber());
            assertEquals(message.getMessageType(), decoded.getMessageType());
            assertEquals(message.getSequenceId(), decoded.getSequenceId());
            assertEquals(message.get("username"), decoded.get("username"));
        } catch (Exception e) {
            fail("Codec test failed: " + e.getMessage());
        }
    }
    @Test
    public void testPartialData() {
        EmbeddedChannel channel = new EmbeddedChannel(new MessageDecoder());
        // 模拟分片数据
        byte[] fullData = createMessageData();
        int splitPoint = fullData.length / 2;
        // 第一次写入前半部分
        ByteBuf firstPart = Unpooled.wrappedBuffer(fullData, 0, splitPoint);
        channel.writeInbound(firstPart);
        // 此时不应该有完整的消息
        assertNull(channel.readInbound());
        // 写入剩余部分
        ByteBuf secondPart = Unpooled.wrappedBuffer(fullData, splitPoint, fullData.length - splitPoint);
        channel.writeInbound(secondPart);
        // 应该能读取到完整的消息
        assertNotNull(channel.readInbound());
    }
    private byte[] createMessageData() {
        ByteBuf buffer = Unpooled.buffer();
        MessageEncoder encoder = new MessageEncoder();
        Message message = new Message(0xCAFEBABE, (byte) 1, MessageType.CHAT_REQUEST.getCode(), 1002);
        message.put("content", "Test message");
        try {
            encoder.encode(null, message, buffer);
            byte[] data = new byte[buffer.readableBytes()];
            buffer.readBytes(data);
            return data;
        } catch (Exception e) {
            return new byte[0];
        }
    }
}

使用Protobuf的版本(高级)

// message.proto
syntax = "proto3";
package com.example.netty.protobuf;
message ProtoMessage {
    int32 magicNumber = 1;
    int32 version = 2;
    int32 messageType = 3;
    int32 sequenceId = 4;
    map<string, string> body = 5;
}
package com.example.netty.codec;
import com.example.netty.protobuf.ProtoMessage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.codec.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
public class ProtobufCodec {
    // Protobuf编码器配置
    public static class ProtobufMessageEncoder extends MessageToByteEncoder<ProtoMessage.ProtoMessage> {
        @Override
        protected void encode(ChannelHandlerContext ctx, ProtoMessage.ProtoMessage msg, ByteBuf out) throws Exception {
            byte[] data = msg.toByteArray();
            out.writeBytes(data);
        }
    }
    // Protobuf解码器配置
    public static class ProtobufMessageDecoder extends ByteToMessageDecoder {
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            // 使用ProtobufVarinint32FrameDecoder处理
            // 这里需要配合ProtobufDecoder使用
        }
    }
    // 完整的pipeline配置
    public static void addProtobufCodec(ChannelPipeline pipeline) {
        pipeline.addLast(new ProtobufVarint32FrameDecoder());
        pipeline.addLast(new ProtobufDecoder(ProtoMessage.ProtoMessage.getDefaultInstance()));
        pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
        pipeline.addLast(new ProtobufEncoder());
    }
}

这个完整的Netty编解码案例包含了:

  1. 协议设计:定义了消息格式(魔数、版本、类型、序列号、长度、校验和)
  2. 编码器实现:将Java对象转换为字节流
  3. 解码器实现:处理粘包/拆包,将字节流转换为Java对象
  4. 组合编解码器:使用CombinedChannelDuplexHandler组合
  5. 服务器和客户端:完整的通信示例
  6. 测试用例:验证编解码的正确性
  7. 优化方案:使用JSON和Protobuf的版本

关键点:

  • 处理粘包/拆包问题是编解码的核心
  • 使用LengthFieldBasedFrameDecoder可以简化长度字段处理
  • 选择合适的序列化方式(Java原生、JSON、Protobuf)
  • 添加校验和保证数据完整性
  • 使用IdleStateHandler处理心跳检测

抱歉,评论功能暂时关闭!