本文目录导读:

我将为您提供一个完整的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编解码案例包含了:
- 协议设计:定义了消息格式(魔数、版本、类型、序列号、长度、校验和)
- 编码器实现:将Java对象转换为字节流
- 解码器实现:处理粘包/拆包,将字节流转换为Java对象
- 组合编解码器:使用CombinedChannelDuplexHandler组合
- 服务器和客户端:完整的通信示例
- 测试用例:验证编解码的正确性
- 优化方案:使用JSON和Protobuf的版本
关键点:
- 处理粘包/拆包问题是编解码的核心
- 使用LengthFieldBasedFrameDecoder可以简化长度字段处理
- 选择合适的序列化方式(Java原生、JSON、Protobuf)
- 添加校验和保证数据完整性
- 使用IdleStateHandler处理心跳检测
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播