当前位置: 首页 > 工具软件 > SFrame > 使用案例 >

分布式游戏服务器框架sframe(四)—— 消息映射

唐修诚
2023-12-01

        对于服务器业务开发来说,主要的任务便是处理各种消息——服务间消息、客户端的消息等等。一般的开发模式都是将每一类消息都映射到与其对应的一个处理函数。服务器收到该消息后,找到与其对应的处理函数,再将消息进行解码,最后调用处理函数。这些步骤都是通用的,所以大多数服务器框架都有一套消息映射机制。实际开发中,一套系统的业务往往是非常复杂的,这也就意味着我们要处理的消息是非常多的,几百、几千种消息往往是很正常的。所以一套好的消息映射机制是很重要的。在本篇文章中,我将主要介绍sframe的消息映射机制。

         看过前面的文章的同学应该已经知道了如何使用sframe来处理服务间消息。在开发逻辑服务时,我们要注册消息处理函数,一般的做法是在服务初始化的时候,调用以下三个函数:

       RegistInsideServiceMessageHandler()

       RegistNetServiceMessageHandler()

       RegistServiceMessageHandler()

       在前文中,我们已经知道了它们三个分别是干什么的。那这3个函数实际上做了什么呢?

       实际上,sframe的消息映射功能是通过模板类DelegateManager<T_Decoder>来实现的。类在Service类中,有两个此类的对象:

       DelegateManager<InsideServiceMessageDecoder>_inside_delegate_mgr;负责进程内部服务消息的映射。

       DelegateManager<NetServiceMessageDecoder>_net_delegate_mgr;负责网络服务消息的映射。

       RegistInsideServiceMessageHandler()函数就是调用_inside_delegate_mgr.Regist()。

       RegistNetServiceMessageHandler()函数就是调用_net_delegate_mgr.Regist()。

       RegistServiceMessageHandler()同时调用_inside_delegate_mgr.Regist()和_net_delegate_mgr.Regist()。

       所以真正的消息映射功能是DelegateManager<T_Decoder>类实现的。这是一个通用辅助类,在处理客户端消息时,也可用此来完成消息映射功能。相关实现也只有一个头文件sframe/sfram/util/Serialization.h。

1.      消息处理函数的注册

       DelegateManager支持注册任意参数的静态函数、任意类的任意参数的非静态成员函数。当然,消息处理函数的返回类型都必须是void。对于注册非静态成员函数,可以注册时绑定对象,也可以调用时指定对象。本文以最简单的非静态函数的注册为例来讲解,至于非静态成员函数,感兴趣的同学请自己看源码(其实原理都是一样的,理解了基本原理,任何变化都会很快明白)。这里的讲解我会配合一些示例代码,但是这里的示例代码只包含最基础部分,和源代码会有不同,但原理都一样。

       首先,我们要实现将很多函数注册到一起,同时以一个数字ID(消息ID)作为key,那就必须有一个容器来保存。毋庸置疑,map是最好的选择,我们定义一个map<int, 函数>的对象来保存这些函数。

       那么,问题来了。map<Key,Val>,Key和Val都是固定的类型,Key当然可以是固定类型,因为任何消息的消息ID的类型肯定都实现同的,但是Val若是采用固定类型的话,我们欲注册的函数必须要求都是一样参数、一样的返回值。这样的话,我们如何实现任意参数类型的函数的注册呢?

         这个问题看上去很棘手,实际上却很简单。这个问题需要解决的关键点有两个——任意类型、任意类型放在一个容器中。

         解决任意类型的问题,在c++中,毋庸置疑模板是最不错的选择。

         解决将任意放在同一个容器中,其实我们在最初学习c++的时候便已经学习过了,利用类的继承便可解决。

         所以,解决以上问题的方法已经有了,那便是模板+继承。

         我们将每一种函数类型都封装在一个模板类里面,这些模板类都继承同一个基类,map容器中保存这个基类指针即可。

        下面给出代码:

// Delegate接口
class IDelegate
{
public:
virtual ~IDelegate() {}
};

// 静态函数委托
template<typename... Args_Type>
class StaticFuncDelegate : public IDelegate
{
public:
typedef void(*FuncT)(Args_Type...);

public:
StaticFuncDelegate(FuncT func) : _func(func) {}
~StaticFuncDelegate() {}

private:
FuncT _func;
};

class DelegateManager
{
public:

static const int kMaxArrLen = 65536;

// 构造函数、析构函数
// ...

// 注册静态函数
template<typename... Args>
void Regist(int id, void(*func)(Args...))
{
	IDelegate * caller = new StaticFuncDelegate<Args...>(func);
	_map_callers[id] = caller;
}

private:
std::unordered_map<int32_t, IDelegate *> _map_callers;
};

2.      消息处理函数的调用与消息解码

        消息处理函数的注册已经完成,但是注册的目的是要调用,并且要根据注册的函数的参数类型来解码消息。这又怎么解决呢?

        首先我们要解决的问题是,如何在调用的时候,取得函数注册时传入的类型信息。看上去很麻烦,实际上很简单。利用多态便可解决,我们给IDelegate接口申明一个Call纯虚函数,StaticFuncDelegate实现这个方法,那么调用Call时,便进入了真正的StaticFuncDelegate类的Call函数中,既然已经进入了StaticFuncDelegate的Call函数中,要取到处理函数的参数类型便很容易了。

        取到类型后,边和根据这些类型来对消息进行解码。解码成功后,便可调用具体的消息处理函数了。那么如何解码呢?

        首先,解码就是从服务消息对象中,取出应该传给消息处理函数的数据。对于内部服务消息而言,将每个对象取出即可;对于网络服务消息而言,要根据这些类型,按照上篇文章讲的序列化和反序列化方法将数据反序列成对象,当然,也有些情况需要用其他的序列化方案。所以,秉着通用化的原则,我们应该将解码部分独立出来。外部提供解码的方法,供StaticFuncDelegate调用,以完成解码。所以,完整的DelegateManager 类应该带有一个模板参数,指明解码器,即DelegateManager<T_Decoder>。解码器提供一个模板函数,完成解码即可。

        那么StaticFuncDelegate又是如何根据消息处理函数的参数类型来调用解码的呢?下面给出修改后的最终代码:

// Delegate接口
template<typename Decoder_Type>
class IDelegate
{
public:
	virtual ~IDelegate() {}
	virtual bool Call(Decoder_Type& decoder) = 0;
};

// 静态函数委托
template<typename Decoder_Type, typename... Args_Type>
class StaticFuncDelegate : public IDelegate<Decoder_Type>
{
public:
	typedef void(*FuncT)(Args_Type...);

public:
	StaticFuncDelegate(FuncT func) : _func(func) {}
	~StaticFuncDelegate() {}

	bool Call(Decoder_Type& decoder) override
	{
		std::tuple<typename std::decay<Args_Type>::type ...> args_tuple;
		std::tuple<typename std::decay<Args_Type>::type ...> * p_args_tuple = nullptr;
		if (!decoder.Decode(&p_args_tuple, args_tuple))
		{
			return false;
		}

		if (p_args_tuple == nullptr)
		{
			p_args_tuple = &args_tuple;
		}

		UnfoldTuple(this, *p_args_tuple);
		return true;
	}

	template<typename... Args>
	void DoUnfoldTuple(Args&&... args)
	{
		this->_func(std::forward<Args>(args)...);
	}

private:
	FuncT _func;
};

// Delegate管理器
template <typename Decoder_Type>
class DelegateManager
{
public:

	// 构造函数、析构函数
	// ...

	bool Call(int id, Decoder_Type & decoder)
	{
		auto it = _map_callers.find(id);
		if (it == _map_callers.end())
		{
			return false;
		}

		return it->seconds->Call(decoder);
	}

	// 注册静态函数
	template<typename... Args>
	void Regist(int id, void(*func)(Args...))
	{
		auto caller = new StaticFuncDelegate<Decoder_Type, Args...>(func);
		_map_callers[id] = caller;
	}

private:
	std::unordered_map<int32_t, IDelegate<Decoder_Type> *> _map_callers;
};
        其中UnfoldTuple函数主要完成了解开tuple对象的功能,具体代码请看源代码sframe/sfram/util/TupleHelper.h。至于解码器,sframe实现了两个解码器InsideServiceMessageDecoder和NetServiceMessageDecoder可供参考。两者分别实现内部服务消息和网络服务消息的解码,实现代码请参考源代码sframe/sfram/serv/MessageDecoder.h。

 类似资料: