猿创征文|[自己做个游戏服务器三]将二进制流转换为具体的 protobuf消息

继续写这个系列,并不是为了真的做个服务器,只是为了展示服务器是怎么从零做起来,只是骨架,如果真的用来开发,还是有很多需要处理的。

废话不多说,今天写一下protobuf的转换和数据的分发

1、设计思路

因为协议使用protobuf传输,具体的内容可以看下上篇文章

[自己做个游戏服务器一]搞清楚游戏通信协议之protobuf的方方面面,评论继续送书_香菜聊游戏的博客-CSDN博客_游戏通信协议

这里没有考虑对字节流的加密,所以只是简单的说下协议的定义:

这是一个最简单的定义,当然后面这个可以使用netty 自带的LengthxxxDecoder(记不清了,我就不去查了)

我想做的就是根据headId 将数据流转换为具体的protobuf消息,然后分发到对应的handler

这样整个消息的流转基本上就形成了,后面的日志,什么监控啥的再说

2、设计代码

为了保持代码的一致性,这里定义一个抽象的handler ,向下约束,留给具体的handler实现,同时使用泛型进行泛化

import com.google.protobuf.AbstractMessage;

public abstract class AbsHandler<Msg_IN extends AbstractMessage,Msg_OUT extends AbstractMessage>  {
    public abstract  Msg_OUT handle(Msg_IN in);
}

为了在项目启动后对所有的handler 进行索引

创建一个注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlerAnno {
    int headerId() default 0;//默认值为0
}

在项目启动之后进行读取所有的handler,并且进行保存。

package com.xin.mgr;

import com.google.protobuf.Parser;
import com.xin.core.HandlerAnno;
import com.xin.handler.AbsHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class MsgMgr implements ApplicationContextAware {
    //  headerId -> Parser
    public static Map<Integer, Parser> msgMap = new ConcurrentHashMap<>();
    //  headerId -> Handler
    public static Map<Integer, AbsHandler> handlerMap = new ConcurrentHashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(HandlerAnno.class);
        for (Object value : beansWithAnnotation.values()) {
            AbsHandler<?,?> handler = (AbsHandler) value;
            Class<? extends AbsHandler> aClass = handler.getClass();
            try {
                //  获取泛型数据
                ParameterizedType superGenericSuperclass = (ParameterizedType) aClass.getGenericSuperclass();
                //  这里只用了第一个泛型,如果严格一些可以做参数的检查,这里就不处理了
                Class protoClass = (Class) superGenericSuperclass.getActualTypeArguments()[0];
                //  反射获取对应的Parser ,类似SimpleMessage.parser()
                Method parser = protoClass.getMethod("parser");
                Parser invoke = (Parser) parser.invoke(null);

                HandlerAnno annotation = aClass.getAnnotation(HandlerAnno.class);
                int headId = annotation.headerId();
                //  这里可以做一个消息重复的检测
                msgMap.put(headId, invoke);
                handlerMap.put(headId, handler);
                System.out.println();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

        }
    }
}

从上面的代码可以看到

MsgMap 主要保存了headerId 和对应输入的Parser,将来用来转换二进制流到protobuf msg

HandlerMap 保存了headerId 和对应的handler 实例,将来用来调用handler handler.handle(msg)

定义一个handler

@HandlerAnno(headerId = 123)
@Component
public class TestHandler extends AbsHandler<SimpleMessage,SimpleMessage> {
    @Override
    public SimpleMessage handle(SimpleMessage in) {
        return null;
    }
}

proto 定义像下面

syntax = "proto3";
option  java_multiple_files = true;
package com.xin.msg.login;

message SimpleMessage {
  int32 id = 1;
  bool is_simple = 2;
  string name = 3;
  repeated int32 sample_list = 4;
}

启动项目可以看下,已经是具体的消息Parser

总结

前面一堆东西,其实重点就是整理出MsgMap 和 HandlerMap,至于什么手段,只要能实现都行,这里提供一个方式

而且这只是我临时写的一些代码,仅供参考,服务器就这么回事

猜你喜欢

转载自blog.csdn.net/perfect2011/article/details/126630537