最近工作中,需要做一些消息动态解析,因为使用的 protobuf,考虑使用protobuf的反射特性。

1 reflection in C++

在c++中使用protobuf 反射

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package com.sunquan;

message Login 
{
    optional int64 userid = 1;
    optional string username = 2; // name
    optional string password = 3; // passwd
    optional string email = 4;
    optional string nickname = 5;
    // etc ... 
}

  C++和Java 不同的是: c++有一个全局的pool,管理了所有定义在 proto 文件里的消息原型, 我们可以通过消息全称,查找到对应的单例的消息原型,然后通过原型构造可变的消息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/**
 * 通过消息名称, 获取构造该类型的默认(原型)。 然后你可以调用该消息的 New() 方法来构造这种类型的可变消息。
 * @param FullMsgName  eg: com.sunquan.Login
 * @return if fail return NULL
 */
const google::protobuf::Message* ParseUtil::FindMsgProtoType(const std::string& FullMsgName)
{
    google::protobuf::Message* pFactory = NULL;
    auto pDesc = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(FullMsgName);
    if (pDesc)
    {
        // Calling this method twice with the same Descriptor returns the same object.
        pFactory = google::protobuf::MessageFactory::generated_factory()->GetPrototype(pDesc);
    } else
        std::cerr << "can't find new desc:" << FullMsgName << std::endl;
    return pFactory;
}

当我们通过消息原型new一个可变的消息对象,就可以对消息进行动态赋值了,如下:

int ParseUtil::ReflectionTest() {
    std::string fullname = "com.sunquan.Login";
    const google::protobuf::Message* pMsgFactory = FindMsgProtoType(fullname);
    if (pMsgFactory == NULL){
       std::cerr << "CreateMessage() fail!" <<std::endl;
       return 0;
    }
    //new mutable msg
    google::protobuf::Message* pMsg = pMsgFactory->New();

    const google::protobuf::Descriptor *pdesc = pMsgFactory->GetDescriptor();
    const google::protobuf::Reflection *refl = pMsgFactory->GetReflection();

    // 动态查找一个 FieldDesc
    std::string fieldname = "username";
    const google::protobuf::FieldDescriptor* pfield = pdesc->FindFieldByName(fieldname);
    if (pfield) {
        // 暂不支持 repeat 类型
        if (pfield->is_repeated()) {
            std::cerr << "repeat field is not support!" << fieldname << std::endl;
            continue;
        }
        switch (pfield->cpp_type()) {
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_INT32:
            //if(oneRow[i].type() == QVariant::Date)
            refl->SetInt32(pMsg, pfield, 100);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_INT64:
            refl->SetInt64(pMsg, pfield, 100LL);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_UINT32:
            refl->SetUInt32(pMsg, pfield, 100);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_UINT64:
            refl->SetUInt64(pMsg, pfield, 100ULL);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_DOUBLE:
            refl->SetDouble(pMsg, pfield, 100.89);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_FLOAT:
            refl->SetFloat(pMsg, pfield, 100.98);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_BOOL:
            refl->SetBool(pMsg, pfield, true);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_STRING:
            refl->SetString(pMsg, pfield, "name");
            break;
        default:
            //NoSupport CPPTYPE_ENUM/CPPTYPE_MESSAGE
            std::cout << "UnSupport Type=" << pfield->type_name() << std::endl;
            break;
        }
    }
    //if no longer use, please remember delete "pMsg"
    if (pMsg) delete pMsg;
    return 0;
}

2 reflection in Java

在Java中使用的protobuf反射。 1 已知消息名称,查找到该消息类型的 Descriptor,然后根据序列化的字节码,动态构建对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

	public static void Reflect_Parse()
	{
		Login req = Login.newBuilder()
		        .setUserid(100)
		        .setUsername("Alice")
		        .setEmail("Alice123@gmail.com")
		        .setNickname("爱丽丝")
	    		.setPassword(Digest.getStringMD5("A123456"))
	    		.build();
		ByteString data = req.toByteString();
		
		try {
		    //根据消息名称查找 Descriptor, DynamicMessage工厂 根据指定的 Descriptor 产生消息 object
            String magname = "Login";
            Descriptor desc = DemoProtos.getDescriptor().findMessageTypeByName(magname);

            if (desc != null) {
                // com.sunquan.zmqproto.Login
                System.out.println(desc.getFullName());
                DynamicMessage msg = DynamicMessage.parseFrom(desc, data);
                System.out.println(msg.toString());
            } else {
                System.out.println("Can't find msg desc: " + magname);
            }
            //底层的实现: 每个消息自身都实现了 自己的  getDescriptor() 接口
            Descriptor desc_direct = Login.getDescriptor();
		} catch (InvalidProtocolBufferException e) {
			e.printStackTrace();
		}
	}

2 预先准备好所有可能的消息,动态解析 把所有可能的消息的 Descriptor 加入到 Map,然后从Map中查找。

1
2
3
4
   private Map<MsgType, Descriptor> typeMap = new TreeMap<MsgType, Descriptor>();
   typeMap.put(MsgType.LOGINREP, LoginRep.getDescriptor());
   typeMap.put(MsgType.INSERTORDERREP, InsertOrderRep.getDescriptor());
   typeMap.put(MsgType.CANCELORDERREP, CancelOrderRep.getDescriptor());

3 动态修改消息字段值 wait