计算机系统应用教程网站

网站首页 > 技术文章 正文

netty4+protobuf3多类型传输实现

btikc 2024-09-04 03:24:39 技术文章 9 ℃ 0 评论

做后台服务经常有这样的流程:

.

如何优雅的完成这个过程呢?下面分享下基于netty+protobuf的方案:

首先要解决的是如何在netty+protobuf中传输多个protobuf协议,这里采取的方案是使用一个类来做为描述协议的方案,也就是需要二次解码的方案,IDL文件如下:

.

Frame为描述协议,所有消息在发送的时候都序列化成byte数组写入Frame的payload,messageName约定为要发送的message的类名,生成的时候设置java_multiple_files = true可以让类分开生成,更清晰些,也更方便后面利用反射来获取这些类.

生成好了protobuf,我们解包的过程就应该是这样的:

.

其中protobuf的序列化和反序列化netty已经为我们编写好了对应的解码/编码器,直接调用即可,我们只需要编写二次解码/编码器即可:

.

.

.

至此,我们收发数据的解码/编码已经做完配合自带的解码/编码器,此时pipeline的处理链是这样的:

:

public void initChannel(SocketChannel ch) throws Exception { 
 ch.pipeline() 
 .addLast(new ProtobufVarint32FrameDecoder()) 
 .addLast(new ProtobufDecoder(Frame.getDefaultInstance())) 
 .addLast(new ProtobufVarint32LengthFieldPrepender()) 
 .addLast(new ProtobufEncoder()) 
 .addLast(new SecondProtobufCodec()) 
 ;

数据收发完成,接下来就是把消息分发到对应的处理方法。处理方法也利用多态特性+泛型+注解优雅的实现分发。首先定义一个泛型接口:interface DataHandler<T extends MessageLite>,该接口上定义一个方法void handler(T t, ChannelHandlerContext ctx),然后每一个类型的处理类都使用自己要处理的类型实现该接口,并使用自定义注解映射处理类型。在项目启动的扫描实现该接口的所有类,使用跟上述解析Message类型相同的方法来缓存这些处理类(有一点不同的是这里只需要缓存一个处理类的实例而不是方法,因为parseFrom是static的无法统一调用),做完这些我们就可以编写我们的pipeline上的最后一个处理器:

public class DispatchHandler extends ChannelInboundHandlerAdapter { 


 @Override 
 @SuppressWarnings("unchecked") 
 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 
 HandlerUtil.getHandlerInstance(msg.getClass().getSimpleName()).handler((MessageLite) msg,ctx); 
 } 
 
 @Override 
 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 
 cause.printStackTrace(); 
 } 
}
public abstract class HandlerUtil { 
 
 
 private final static ConcurrentMap<String,DataHandler> instanceCache = new ConcurrentHashMap<>(); 
 
 
 static { 
 try { 
 List<Class> classes = ClassUtil.getAllClassBySubClass(DataHandler.class, true,"com.onescorpion"); 
 for (Class claz : classes) { 
 HandlerMapping annotation = (HandlerMapping) claz.getAnnotation(HandlerMapping.class); 
 instanceCache.put(annotation.value(), (DataHandler) claz.newInstance()); 
 } 
 System.out.println("handler init success handler Map: " + instanceCache); 
 } catch (Exception e) { 
 e.printStackTrace(); 
 
 } 
 
 
 } 
 
 
 public static DataHandler getHandlerInstance(String name) { 
 return instanceCache.get(name); 
 } 
}

这样一个优雅的处理分发就完成了。 由于代码规律性极强,所以所有handler类均可以使用模版来生成,完整代码请看这里

ps:其实本例中由于使用了protobuf还有比较强约定性,所以理论上来说每个消息处理器上的自定义注解是不需要的,通过获取泛型的真实类型即可,但是注解可以大大增加handler的灵活性,如果采用其他方案(例如json)也更有借鉴的价值。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表