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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
pomelo 是由网易开发的基于node.js开发的高性能、分布式游戏服务器框架, 也可作为高实时web应用框架。 Pomelo的应用范围 pomelo最适合的应用领域是网页游戏、社交游戏、移动游戏的服务端,开发者会发现pomelo可以用如此少的代码达到强大的扩展性和伸缩性。当然还不仅仅是游戏,很多人断言未来的web时代是实时web应用的时代, 我们发现用pomelo开发高实时web应用也如此合适, 而且伸缩性比其它框架好。目前不推荐将pomelo用于大型的MMO rpg游戏开发,尤其是3d游戏, 还是需要象bigworld这样的商用引擎来支撑。 Pomelo的理念 pomelo的第一个理念是让游戏(高实时web应用)服务器的开发变得非常简单, 而不是解决某类算法或系统上的难题。这个设计理念跟rails是很类似的;第二个理念是重视性能和可伸缩性,用户用pomelo开发出来的游戏天生具有很强的伸缩性,扩展也很容易。我们在性能优化上也花了很多功夫,并且会持续进行;第三个理念是让第三方很容易扩展,框架用了很多插件式的设计, 组件component、路由规则、甚至管理控制台都可以完全由第三方扩展。 Pomelo的框架组成 pomelo包括三部分: 框架, pomelo的核心, 与以往单进程的游戏框架不同, 它是高性能、分布式游戏服务器框架,并且使用很简单 库, 包括了开发游戏的常用工具库, 如人工智能(ai), 寻路, aoi等 工具包, 包括管理控制台, 命令行工具, 压力测试工具等 pomelo特性 快速、易上手的游戏开发模型和api 高可伸缩的多进程架构, 支持MMO的场景分区和其它各类分区策略 方便的服务器扩展机制,可快速扩展服务器类型和数量 方便的请求、响应、广播、服务器通讯机制, 无需任何配置 注重性能,在性能、可伸缩性上做了大量的测试、优化 提供了较多扩展组件,包括游戏开发常用的库和工具包 提供了完整的MMO demo代码(客户端html5),可以作为很好的开发参考 基于socket.io开发,支持socket.io支持的多种语言客户端 为什么使用pomelo? 高并发、高实时的游戏服务器的开发是很复杂的工作。跟web应用一样, 一个好的开源容器或开发框架可以大大减少游戏开发的复杂性,让开发变得更加容易。遗憾的是目前在游戏服务器开发领域一直没有太好的开源解决方案。 pomelo将填补这个空白, 打造一款完全开源的高性能(并发)游戏服务器框架。 pomelo的优势有以下几点: 架构的可伸缩性好。 采用多进程单线程的运行架构,扩展服务器非常方便, node.js的网络io优势提供了高可伸缩性。 使用非常容易, 开发模型与web应用的开发类似,基于convention over configuration的理念, 几乎零配置, api的设计也很精简, 很容易上手。 框架的松耦合和可扩展性好, 遵循node.js微模块的原则, framework本身只有很少的代码,所有component、库、工具都可以用npm module的形式扩展进来。任何第三方都可以根据自己的需要开发自定义module。 提供完整的开源MMO游戏demo参考(基于HTML 5)。 一个超过1万行代码的游戏demo,使开发者可以随时借鉴demo的设计与开发思路。 在线演示:http://pomelo.netease.com/demo.html 标签:开发框架  游戏框架
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值