做后台服务经常有这样的流程:
如何优雅的完成这个过程呢?
首先要解决的是如何在netty+protobuf
中传输多个protobuf协议,这里采取的方案是使用一个类来做为描述协议的方案,也就是需要二次解码的方案,IDL文件如下:
syntax = "proto3";
option java_package = "com.nonpool.proto";
// 生成的时候设置java_multiple_files = true可以让类分开生成,
//更清晰些,也更方便后面利用反射来获取这些类
option java_multiple_files = true;
//Frame为描述协议
message Frame {
// messageName约定为要发送的message的类全名
string messageName = 1;
// 所有消息在发送的时候都序列化成byte数组写入Frame的payload
bytes payload = 15;
}
message TextMessage {
string text = 1;
}
message SpeakMessage {
string text = 1;
// ID
int32 id = 2;
}
Frame为描述协议,所有消息在发送的时候都序列化成byte数组写入Frame的payload,messageName
约定为要发送的message的类名
,生成的时候设置java_multiple_files = true
可以让类分开生成,更清晰些,也更方便后面利用反射来获取这些类.
生成好了protobuf,我们解包的过程就应该是这样的:
其中protobuf的序列化和反序列化netty已经为我们编写好了对应的解码/编码器,直接调用即可,我们只需要编写二次解码/编码器即可:
二次编码/解码器代码如下:
package com.tcp.manyproto;
import com.google.protobuf.MessageLite;
import com.nonpool.proto.Frame;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import java.util.List;
/**
* @Description 二次Protobuf解码器 in:把playload里面的字节数组转换成对象
* out:把对需要发送的对象包装成Frame
* *
* @Date 2019/10/22 13:23
* @Author zsj
* 二次解码/编码器
*/
public class SecondProtobufCodec extends MessageToMessageCodec<Frame, MessageLite> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, MessageLite messageLite, List<Object> list) throws Exception {
//messageName 为全类名 方便从jvm中加载类
list.add(Frame.newBuilder()
.setMessageName(messageLite.getClass().getName())
.setPayload(messageLite.toByteString())
.build());
}
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, Frame frame, List<Object> list) throws Exception {
// 根据Frame类解析出其中的body
list.add(ParseFromUtil.parse(frame));
}
}
解析工具类代码如下
package com.tcp.manyproto;
import com.google.protobuf.ByteString;
import com.google.protobuf.MessageLite;
import com.nonpool.proto.Frame;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author nonpool
* @version 1.0
* @since 2018/1/29
*/
public class ParseFromUtil {
/**
* 根据Frame类解析出其中的body
*根据全类名加载类
* @param msg
* @return
*/
public static MessageLite parse(Frame msg) throws InvocationTargetException, IllegalAccessException {
String className = msg.getMessageName();
ByteString body = msg.getPayload();
Class clazz = null;
try {
// 根据全类名加载类
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("unknown Message type :" + className);
}
Method method = null;
try {
//执行解析方法
method = clazz.getMethod("parseFrom", ByteString.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException("unknown Message type :" + className);
}
return (MessageLite) method.invoke(null, body);
}
}
服务器接收到消息的业务处理逻辑
package com.tcp.manyproto;
import com.google.protobuf.MessageLite;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHa