导航:[首页]->[cpp]->[Google Protobuf 自动反射消息]

最近翻了下同事《Linux多线程服务端编程:使用muduo C++网络库》一书,发现Google Protobuf支持自动反射,这在一定程度上简化了消息(反)序列化的处理。

文中作者在自己博客也同步更新了文章,见这里http://blog.csdn.net/solstice/article/details/6300108

在网络程序中,通常需要对报文进行序列化和反序列化,以适用网络传输。不过这其中还有一个细节,你需要告诉对方,你传输的是什么包,对端才知道用什么结构来反序列化。

大多数情况下,我们用一个双方都统一的数字或者字符串来标识报文类型,收包时做一个映射。

现在的问题是,哪天新增了一个新的报文类型,这个映射就必须新增一项,发包的地方也一样。

不过Protobuf内建了这样的映射。

bool init_send_buf_ex(
		const google::protobuf::Message& msg,
		char* buf,
		size_t* bufsize)
{
	assert(buf && bufsize && *bufsize);
	google::protobuf::io::ArrayOutputStream aos(
			buf,
			*bufsize);
	google::protobuf::io::CodedOutputStream coded_output(&aos);

	// 写名字长度
	std::string msgname_ = msg.GetTypeName();
	if(msgname_.empty() || msgname_.length() >= ProtobufMessageMaxNamelen)
	{
		LogError("Invalid message name '%s' when init_send_buf_ex",msgname_.c_str());
		return false;
	}
	coded_output.WriteVarint32(uint32_t(msgname_.length()));
	// 写名字
	coded_output.WriteString(msgname_);
	
	// buffer是否够大
	if(msg.ByteSize() + coded_output.ByteCount() > *bufsize)
	{
		LogError("buf size '%ld' is too small,need '%ld'",
				*bufsize,
				msg.ByteSize() + coded_output.ByteCount());
		return false;
	}
	// 序列化
	msg.SerializeToCodedStream(&coded_output);
	*bufsize = coded_output.ByteCount();
	return true;
}

/**
 * @brief 根据名字获取实际的Message对象
 * @param typeName
 * @return
 */
static google::protobuf::Message* Name2ProtobufMessage(
		const char* name)
{
	assert(name);
	google::protobuf::Message* message = NULL;
	const google::protobuf::Descriptor* descriptor =
			google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(name);
	if (descriptor)
	{
		const google::protobuf::Message* prototype =
				google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
		if (prototype)
		{
			message = prototype->New();
		}
	}
	return message;
}

void LanServerBase::read_imp(int fd)
{
	char buf_[1024];
	struct sockaddr_in addr_;
	socklen_t addrlen_ = sizeof(addr_);

	int len_ = recvfrom_ex(fd,buf_,sizeof(buf_),0,
			(struct sockaddr*)&addr_,&addrlen_);
	if(len_ <= 0)
	{
		if(len_ < 0)
		{
			LogError("%s %d %s",strerror(errno),errno,"recvfrom_ex");
		}
		return;
	}
	assert(sizeof(addr_) <= addrlen_);
	google::protobuf::io::ArrayInputStream ais(buf_,len_);
	google::protobuf::io::CodedInputStream coded_input(&ais);
	
	// msg 类型是否合法
	bool result_ = false;

	// 读取名称长度
	uint32_t msgname_len_ = 0;
	result_ = coded_input.ReadVarint32(&msgname_len_);
	if(!result_ ||
		0 == msgname_len_ || 
		ProtobufMessageMaxNamelen <= msgname_len_)
	{
		LogError("ReadVarint32 msgname_len fail with size '%d'",msgname_len_);
		return;
	}

	// 读取名称
	std::string msgname_;
	result_ = coded_input.ReadString(&msgname_,msgname_len_);
	if(!result_)
	{
		LogError("ReadString msgname fail with size '%d'",msgname_len_);
		return;
	}
	
	google::protobuf::Message* msg_ = Name2ProtobufMessage(msgname_.c_str());
	if(!msg_)
	{
		LogError("Name2ProtobufMessage fail with name '%s'",msgname_.c_str());
		return;
	}
	
	if(!msg_->ParseFromCodedStream(&coded_input))
	{
		LogError("ParseFromCodedStream fail with name '%s'",msgname_.c_str());
		delete msg_;
		return;
	}
	delete msg_;
}

##注意

在反序列化时,ParseFromCodedStream不要一次性给太多数据,这个报文需要多少数据,就给多少数据,否则会画蛇添足,导致反序列化失败

##参考

  1. http://blog.csdn.net/solstice/article/details/6300108