接上篇:netty protobuf 序列化协议之 多个message
本篇讲讲自定义协议头、协议体时候,怎么使用protobuf作为序列化和反序列化。
部分代码思路来源黑马netty教程。
gitee:https://gitee.com/hsjjsh123/itcast_netty/tree/master/code/my_netty_study/Netty-protobuf-selfDef
先看一下自定义协议编解码器:
4 字节的魔数
1 字节的版本
1 字节的序列化方式 protobuf = 0
4 字节的消息类型
4 字节的消息体内容长度
N 字节的消息体内容部分
我们的.proto(protobuf3)文件如下,每个消息message都是会对应java类,包含消息唯一id,还有一些业务字段:
syntax = "proto3";
// 生成的包名
option java_package="com.pancm.protobuf";
//生成的java名
option java_outer_classname = "UserInfo";
message UserMsgLogin{
// 消息唯一ID
int32 id = 1;
// 内容
string content = 2;
// userID
int32 user_id = 3;
// userNME
string user_name = 4;
// 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
int32 type = 5;
}
message UserMsgSingle{
// 消息唯一ID
int32 id = 1;
// 内容
string content = 2;
// 来源
int32 from_id = 3;
// 来源
string from_name = 4;
// 去源
int32 to_id = 5;
// 去源
string to_name = 6;
// 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
int32 type = 7;
}
message UserMsgMutiple{
// 消息唯一ID
int32 id = 1;
// 内容
string content = 2;
// 来源
int32 from_id = 3;
// 来源
string from_name = 4;
// 去源group
int32 to_group_id = 5;
// 去源group
string to_group_name = 6;
// 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
int32 type = 7;
}
message UserMsgQuit{
// 消息唯一ID
int32 id = 1;
// bool
bool quit = 2;
// 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
int32 type = 7;
}
message UserMsgServerAck{
// 消息唯一ID
int32 id = 1;
// 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
int32 type = 2;
}
注意:编解码器接受的是com.google.protobuf.GeneratedMessageV3对象,所有的.proto文件message转java类之后的父类是GeneratedMessageV3。
package com.pancm.protocol;
import com.google.protobuf.BytesValue;
import com.google.protobuf.GeneratedMessageV3;
import com.google.protobuf.InvalidProtocolBufferException;
import com.pancm.protobuf.UserInfo;
import com.pancm.utils.MessageUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@ChannelHandler.Sharable
/**
* 必须和 LengthFieldBasedFrameDecoder 一起使用,确保接到的 ByteBuf 消息是完整的
*/
public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, GeneratedMessageV3> {
/**
* @Description:protobuf编码
* @author HeShengjin 2356899074@qq.com
* @date 2021/7/29 9:51
*/
@Override
public void encode(ChannelHandlerContext ctx, GeneratedMessageV3 msg, List<Object> outList) throws Exception {
ByteBuf out = ctx.alloc().buffer();
// 1. 4 字节的魔数
out.writeBytes(new byte[]{1, 2, 3, 4});
// 2. 1 字节的版本,
out.writeByte(1);
// 3. 1 字节的序列化方式 protobuf 0
out.writeByte(SerializeType.PROTOBUF.getSerializeType());
// 4. 4 字节的消息类型
out.writeInt(MessageUtil.parseMessageFeildTypeFromProtobufMessage(msg).intValue());
// 5. 获取protobuf的字节数组
byte[] bytes = msg.toByteArray();
// 6. 长度
out.writeInt(bytes.length);
// 7. 写入protobuf
out.writeBytes(bytes);
outList.add(out);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 1. 4 字节的魔数
int magicNum = in.readInt();
// 2. 1 字节的版本,
byte version = in.readByte();
// 3. 1 字节的序列化方式 protobuf 0
byte serializerAlgorithm = in.readByte(); // 0
// 4. 4 字节的消息类型
int messageType = in.readInt();
// 5. 长度
int length = in.readInt();
byte[] bytes = new byte[length];
// 6. 读取protobuf字节数组
in.readBytes(bytes, 0, length);
//反序列化成java对象
out.add(MessageUtil.parseMessageType2Message(messageType, bytes));
}
}
4 字节的消息类型字节的消息类型我们编码时候是从message转换的java对象读取的,解码时候反序列化成对应的protobuf的java类。 .proto 文件定义的type 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
package com.pancm.utils;/**
* @author HeShengjin 2356899074@qq.com
* @Description TODO
* @createTime 2021年07月29日 15:47:00
*/
import com.google.protobuf.Descriptors;
import com.google.protobuf.GeneratedMessageV3;
import com.google.protobuf.InvalidProtocolBufferException;
import com.pancm.protobuf.UserInfo;
import com.pancm.protocol.MsgTypeFeilds;
import com.pancm.protocol.MsgTypes;
import java.util.Iterator;
import java.util.Map;
/**
* @author hsj
* @description:message工具
* @date 2021/7/29 15:47
*/
public final class MessageUtil {
/**
* @Description:protobuf获取消息的类型type
* @author HeShengjin 2356899074@qq.com
* @date 2021/7/29 15:36
*/
public static Integer parseMessageFeildTypeFromProtobufMessage(GeneratedMessageV3 userMsgSingle) {
Map<Descriptors.FieldDescriptor, Object> map = userMsgSingle.getAllFields();
Iterator<Map.Entry<Descriptors.FieldDescriptor, Object>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Descriptors.FieldDescriptor, Object> next = it.next();
if (next.getKey().getJsonName().equals(MsgTypeFeilds.TYPE.getName())) {
return (Integer) next.getValue();
}
}
return null;
}
/**
* @Description:根据消息类型反序列化
* @author HeShengjin 2356899074@qq.com
* @date 2021/7/29 15:57
*/
public static GeneratedMessageV3 parseMessageType2Message(int messageType, byte[] bytes) throws InvalidProtocolBufferException {
if (MsgTypes.TYPE_LOGIN.getMsgType() == messageType) {
return UserInfo.UserMsgLogin.parseFrom(bytes);
}
if (MsgTypes.TYPE_SINGLE.getMsgType() == messageType) {
return UserInfo.UserMsgSingle.parseFrom(bytes);
}
if (MsgTypes.TYPE_MUTIPLE.getMsgType() == messageType) {
return UserInfo.UserMsgMutiple.parseFrom(bytes);
}
if (MsgTypes.TYPE_QUIT.getMsgType() == messageType) {
return UserInfo.UserMsgQuit.parseFrom(bytes);
}
if (MsgTypes.TYPE_SERVER_ACK.getMsgType() == messageType) {
return UserInfo.UserMsgServerAck.parseFrom(bytes);
}
return null;
}
}
package com.pancm.protocol;
/**
* @author HeShengjin 2356899074@qq.com
* @Description 消息type指令类型等, 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
* @createTime 2021年07月29日 09:14:00
*/
public enum MsgTypes {
TYPE_LOGIN(1,"login"),
TYPE_SINGLE(2,"single"),
TYPE_MUTIPLE(3,"mutiple"),
TYPE_QUIT(4,"quit"),
TYPE_SERVER_ACK(5,"ack");
//指令
//类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
private int msgType;
private String msgTypeName;
MsgTypes(int msgType, String msgTypeName) {
this.msgType = msgType;
this.msgTypeName = msgTypeName;
}
public String getMsgTypeName() {
return msgTypeName;
}
public void setMsgTypeName(String msgTypeName) {
this.msgTypeName = msgTypeName;
}
public int getMsgType() {
return msgType;
}
public void setMsgType(int msgType) {
this.msgType = msgType;
}
}
package com.pancm.protocol;
/**
* @author HeShengjin 2356899074@qq.com
* @Description 消息的字段域
* @createTime 2021年07月29日 15:43:00
*/
public enum MsgTypeFeilds {
//字段域type
TYPE("type");
private String name;
MsgTypeFeilds(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
以上就是消息定义.proto文件和编解码相关代码。
这里使用netty解码器:LengthFieldBasedFrameDecoder
最大帧设置1024
长度域字段偏移字节10 = 4 + 1 + 1 + 4
长度域字段长度4字节
无需调整,使用0
无需去除字节,使用0
LengthFieldBasedFrameDecoder (1024,10,4,0,0)
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
public class ProcotolFrameDecoder extends LengthFieldBasedFrameDecoder {
public ProcotolFrameDecoder() {
this(1024, 10, 4, 0, 0);
}
public ProcotolFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
}
}
服务端handler接受一个单聊类型的消息:
package com.pancm.handler;
import com.pancm.protobuf.UserInfo;
import com.pancm.protocol.Constant;
import com.pancm.protocol.MsgTypes;
import com.pancm.protocol.SequenceIdGenerator;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
/**
* @author hsj
* @description:single聊天handler
* @date 2021/7/29 10:13
*/
@ChannelHandler.Sharable
@Slf4j
public class UserMsgSingleServerHandler extends SimpleChannelInboundHandler<UserInfo.UserMsgSingle> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, UserInfo.UserMsgSingle msg) throws Exception {
//自动释放ByteBuf
//ACK时候,client不再发起消息
log.info("接受到client的single聊天protobuf消息,{}",msg.getAllFields());
//server回复client消息ACK
UserInfo.UserMsgServerAck userMsgServerAck = UserInfo.UserMsgServerAck.newBuilder()
//消息id
.setId(SequenceIdGenerator.nextId())
//ACK
.setType(MsgTypes.TYPE_SERVER_ACK.getMsgType())
.build();
ctx.channel().writeAndFlush(userMsgServerAck);
}
}
public class NettyServerFilter extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
// 解码和编码,应和客户端一致
//传输的协议 Protobuf
ph.addLast("LengthFieldBasedFrameDecoder", new ProcotolFrameDecoder());
ph.addLast("decoc-protobuf",new MessageCodecSharable());
//业务逻辑实现类
ph.addLast("UserMsgSingleServerHandler", new UserMsgSingleServerHandler());
}
}
客户端接受服务器的一个回应handler:
package com.pancm.handler;
import com.pancm.protobuf.UserInfo;
import com.pancm.protocol.Constant;
import com.pancm.protocol.MsgTypes;
import com.pancm.protocol.SequenceIdGenerator;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
/**
* @author hsj
* @description:ACK聊天handler
* @date 2021/7/29 10:13
*/
@ChannelHandler.Sharable
@Slf4j
public class UserMsgServerAckClientHandler extends SimpleChannelInboundHandler<UserInfo.UserMsgServerAck> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, UserInfo.UserMsgServerAck msg) throws Exception {
//自动释放ByteBuf
log.info("接受到服务器ACK的protobuf消息,{}",msg.getAllFields());
}
}
public class NettyClientFilter extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
// 解码和编码,应和客户端一致
//传输的协议 Protobuf
ph.addLast("LengthFieldBasedFrameDecoder", new ProcotolFrameDecoder());
ph.addLast("decoc-protobuf",new MessageCodecSharable());
//业务逻辑实现类
ph.addLast("UserMsgServerAckClientHandler", new UserMsgServerAckClientHandler());
}
}