自定义协议要素
- 魔术:判断是否是无效数据包。
- 版本号:可以支持协议的升级。
- 序列化算法(对消息正文进行序列化以及反序列化):json、protobuf、hessian、jdk。
- 指令类型:如登陆、注册、单聊、群聊跟业务相关的。
- 请求序号:为了双工通信,提供异步能力。
- 正文长度:
- 消息正文:
JDK自带序列化以及反序列化算法
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(msg);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
Message message = (Message) ois.readObject();
编解码器设计
定义抽象父类message
@Data
public abstract class Message implements Serializable {
/**
* 根据消息类型字节,获得对应的消息 class
* @param messageType 消息类型字节
* @return 消息 class
*/
public static Class<? extends Message> getMessageClass(int messageType) {
return messageClasses.get(messageType);
}
private int sequenceId;
private int messageType;
public abstract int getMessageType();
public static final int LoginRequestMessage = 0;
public static final int LoginResponseMessage = 1;
public static final int ChatRequestMessage = 2;
public static final int ChatResponseMessage = 3;
public static final int GroupCreateRequestMessage = 4;
public static final int GroupCreateResponseMessage = 5;
public static final int GroupJoinRequestMessage = 6;
public static final int GroupJoinResponseMessage = 7;
public static final int GroupQuitRequestMessage = 8;
public static final int GroupQuitResponseMessage = 9;
public static final int GroupChatRequestMessage = 10;
public static final int GroupChatResponseMessage = 11;
public static final int GroupMembersRequestMessage = 12;
public static final int GroupMembersResponseMessage = 13;
public static final int PingMessage = 14;
public static final int PongMessage = 15;
/**
* 请求类型 byte 值
*/
public static final int RPC_MESSAGE_TYPE_REQUEST = 101;
/**
* 响应类型 byte 值
*/
public static final int RPC_MESSAGE_TYPE_RESPONSE = 102;
private static final Map<Integer, Class<? extends Message>> messageClasses = new HashMap<>();
static {
messageClasses.put(LoginRequestMessage, LoginRequestMessage.class);
messageClasses.put(LoginResponseMessage, LoginResponseMessage.class);
messageClasses.put(ChatRequestMessage, ChatRequestMessage.class);
messageClasses.put(ChatResponseMessage, ChatResponseMessage.class);
messageClasses.put(GroupCreateRequestMessage, GroupCreateRequestMessage.class);
messageClasses.put(GroupCreateResponseMessage, GroupCreateResponseMessage.class);
messageClasses.put(GroupJoinRequestMessage, GroupJoinRequestMessage.class);
messageClasses.put(GroupJoinResponseMessage, GroupJoinResponseMessage.class);
messageClasses.put(GroupQuitRequestMessage, GroupQuitRequestMessage.class);
messageClasses.put(GroupQuitResponseMessage, GroupQuitResponseMessage.class);
messageClasses.put(GroupChatRequestMessage, GroupChatRequestMessage.class);
messageClasses.put(GroupChatResponseMessage, GroupChatResponseMessage.class);
messageClasses.put(GroupMembersRequestMessage, GroupMembersRequestMessage.class);
messageClasses.put(GroupMembersResponseMessage, GroupMembersResponseMessage.class);
messageClasses.put(RPC_MESSAGE_TYPE_REQUEST, RpcRequestMessage.class);
messageClasses.put(RPC_MESSAGE_TYPE_RESPONSE, RpcResponseMessage.class);
}
}
设计编解码器:
@Slf4j
public class MessageCodec extends ByteToMessageCodec<Message> {
// 编码器
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
// 1. 4字节的魔术
out.writeBytes(new byte[]{1, 2, 3, 4});
// 2. 1字节的版本
out.writeByte(1);
// 3. 1字节的序列化方式 jdk 0, json 1
out.writeByte(0);
// 4. 1字节的指令类型
out.writeByte(msg.getMessageType());
// 5. 4字节的序列号
out.writeInt(msg.getSequenceId());
// 无意义,对其填充,使其前缀保持16字节
out.writeByte(0xff);
// 6. 获取内容的字节数组
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(msg);
byte[] bytes = bos.toByteArray();
// 7.长度
out.writeInt(bytes.length);
// 8. 写入内容
out.writeBytes(bytes);
}
// 解码器
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 1. 获取魔术
int magicNum = in.readInt();
// 2. 1字节的版本
byte version = in.readByte();
// 3. 1字节的序列化方式 jdk 0, json 1
byte serializerType = in.readByte();
// 4. 1字节的指令类型
byte messageType = in.readByte();
// 5. 4字节的序列号
int sequenceId = in.readInt();
// 无意义,对其填充
in.readByte();
// 读取长度
int length = in.readInt();
byte[] bytes = new byte[length];
in.readBytes(bytes, 0, length);
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
Message message = (Message) ois.readObject();
log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);
log.debug("{}", message);
out.add(message);
}
}
测试编解码器
出战编码测试:
入站解码测试:
将上述代码中帧解码器LengthFieldBasedFrameDecoder注释掉,此时报错如下:
@Sharable注解
@Sharable
注解用于标记一个 ChannelHandler
是可共享的,这意味着多个 Channel
或 ChannelPipeline
可以重用同一个 ChannelHandler
实例。@Sharable
主要适用于那些没有维护状态或者只维护线程安全状态的处理器。
当Handler不保存状态时,就可以安全地在多线程下被共享。此时可以将Handler提取为公共方法,从而达到复用的效果:
帧解码器LengthFieldBasedFrameDecoder是否能抽取为公共方法?不可以主要原因如下:
- 状态维护:
LengthFieldBasedFrameDecoder
在其内部维护了一些状态信息(例如,解码过程中的临时数据),这些状态信息是特定于每个Channel
的,因此它不能安全地被多个Channel
共享。 - 配置参数:
LengthFieldBasedFrameDecoder
在初始化时需要一些特定的配置参数(如长度字段的偏移量和长度),这些参数可能依赖于特定的Channel
,因此每个Channel
可能需要一个独立的解码器实例来满足不同的配置需求。
查看LoggingHandler和LengthFieldBasedFrameDecoder源码,可以看到LoggingHandler内部加@Sharable注解,代表次handler内部不维护状态信息,可以被多个channel、channelPipeline重用。LengthFieldBasedFrameDecoder内部没有@Sharable注解,不可以重用。
同时可以看到LengthFieldBasedFrameDecoder继承自ByteToMessageDecoder,查看后者的源码如下:
可以看到Netty作者规定只要继承ByteToMessageDecoder的子类都不能被多个handler共享。
所以我们上面自定义编解码器也不能加@Sharable注解使其被多个handler使用。如何设计可以被多个handler共用的帧解码器,可以将继承关系由ByteToMessageDecoder替换为MessageToMessageCodec。