好的,首先是问题: 我需要知道在我的远程“对象分派(dispatch)”(穷人的 RPC)MethodCall 方法中,我有哪些替代方法可以替代 Incredibly Evil (TM) 开关。
virtual void methodCall(unsigned int method) {
bvnet::scoped_lock lock(ctx.getMutex());
bvnet::value_queue &vqueue=ctx.getSendQueue();
switch(method) {
case 0: /* GetType */
/* emit object type to output queue as string */
vqueue.push(string(getType()));
break;
}
当然有人会问我想用这种方法完成什么。
我已经建立了一个实现轻量级分布式对象系统的协议(protocol)(网络),该系统允许一端或另一端使用对象引用和方法索引在一端或另一端进行方法调用,每个对象引用和方法索引都是通过网络传递的整数(所以不能使用指针、ptr-to-method 等)。此外,我在每个位置都有一个注册表映射,用于跟踪哪些对象引用处于事件状态(这意味着来自另一端的传入方法调用的对象 ID 整数是有效的)。
参数是用第四式堆栈处理的,所以你会有一个传入流的东西,比如:
3 5 math.plus() 7 math.multiply()
达到相当于(3+5)*7
vqueue<>.push() 和 getarg<>() 处理通过 vqueue 将 vlaue 放入传出流并通过 getarg<>() 读取参数,<> 模板化为通过线路传输的支持类型
这个动物是基类,它的目的是提供注册到特定连接 session 的对象(每个端点都有自己的注册中心,每当创建新对象以引用远程时,该注册中心就会插入。
/**
** @brief ABC for remotable objects.
**
** Base used for objects exchangeable via object references.
**
** Since secure referencing requires a way to
** track object lifetime a registry reference
** is required for construction.
*/
class object {
protected:
session &ctx; /**< @brief for objects to attach to the session's registry */
public:
/** @brief construction of an object @param sess reference to session to attach */
object(session &sess) :
ctx(sess) {
LOCK_COUT
cout << "object [" << this << "] ctor" << endl;
UNLOCK_COUT
ctx.register_object(this);
}
/** @brief base dtor to automatically unregister the object */
virtual ~object() {
LOCK_COUT
cout << "object [" << this << "] dtor" << endl;
UNLOCK_COUT
ctx.unregister(this);
}
/**
* @brief Get object's identity.
* @return Object identity string
*
* Overriden by superclass to announce it's identity.
*/
virtual const char *getType() {return "baseObject";}
/**
* @brief Method call switchboard.
*
* Overidden by superclass to implement methods callable
* by the remote. Currently the superclasses are using
* big switchbanks which looks plain evil but at this
* point I'm not sure of what to refactor with.
*
* @todo
* Base class to implement some sort of glue to take out the switch boilerplate?
* @todo
* some sort of static enum to get rid of the magic number
* method call #s from remote POV?
* @todo
* automatically declare the methods for method calling
* via some sort of macro or metacode?
*
*/
virtual void methodCall(unsigned int idx)=0;
};
丑陋之处在于实现实际接口(interface)的派生对象。就是 MethodCall() 中的那个开关:
class Account : public bvnet::object {
private:
s64 userId;
public:
Account(bvnet::session &sess,s64 who)
: bvnet::object(sess),userId(who) {}
virtual ~Account() {}
virtual const char *getType() {return "userAccount";}
virtual void methodCall(unsigned int method) {
bvnet::scoped_lock lock(ctx.getMutex());
bvnet::value_queue &vqueue=ctx.getSendQueue();
switch(method) {
case 0: /* GetType */
/* emit object type to output queue as string */
vqueue.push(string(getType()));
break;
}
}
};
所以问题又来了,是否有另一种方法可以在对 C++ 更友好的东西上实现编号的方法分派(dispatch),以及一些 future 对象决定他想做的恶作剧
MyFutureAccount : public Account {...}
(并且害怕我会从愤怒的维护者那里收到一封愤怒的 nulcear-fission-angry 电子邮件,他们想要这样做以最大化附近的盖格计数器......)
看来我可能需要在 bvnet::Object 的构造函数中做一些工作并设置某种形式的 C++ 内部操作(vtable)可能使用 intMethodId-to-ptrToMember 的 STL 映射(为下游提供一种简单的方法来覆盖东西)。但是,bvnet::Object 基类成员指针是否仍会在下游派生类中按预期工作?还是那么多问号。不确定我是在正确的轨道上还是在这个潜在的解决方案上咆哮错误的树......
与其粘贴更多内容(我无法预测其他人还想看到什么,我可以将您指向 github,因为它是 LGPL3 开源的:https://github.com/gau-veldt/Minetest-Blockiverse/tree/master/blockiverse
最相关的文件是 https://github.com/gau-veldt/Minetest-Blockiverse/blob/master/blockiverse/protocol.hpp但在 https://github.com/gau-veldt/Minetest-Blockiverse/blob/master/blockiverse/server.hpp 的 serverRoot 中抢劫清楚地看到 switch 方法已经变得多么邪恶......
最佳答案
至少有人对该问题投了赞成票,我推断至少有一个人可能对此问题的解决方案/答案感兴趣。
我确实设法移除了邪恶的开关,并将其替换为我在代码中标记为“调度方法调用”或 dmc 的内容。它们在从网络进入时被调度以转换为方法调用,因此命名为“调度方法调用”
现在层次结构使用封装在 std::function 适配器中的指向成员函数的指针,它是 std::map 的值类型:
typedef std::function<void(value_queue&)> dmcMethod;
typedef std::map<unsigned int,dmcMethod> call_map;
乐趣始于 bvnet::object(基类),其中初始化调用映射以引用基类 dmc_GetType(dmc 仿函数调用可覆盖的 GetType 并将值发布到传出值队列):
/**
** @brief ABC for remotable objects.
**
** Base used for objects exchangeable via object references.
**
** Since secure referencing requires a way to
** track object lifetime a registry reference
** is required for construction.
*/
typedef void(bvnet::object::*dmc)(value_queue&);
class object {
private:
/**
* @brief Displateched Method Call
*
* Implements dispatched method call (dmc)
*
* Superclass-installed dmc methods in dmcTable are
* callable by the remote. A superclass sets up his
* dmc methods in his ctor by accessing object's dmcTable
*
* As a plus the lock and value queue boilerplate has been
* been moved to the base class dispatcher and the dmc methods
* will be in locked context and given the value queue as a
* parameter. He also has an exception now to trap calls to
* an unimplemented method index.
*
* @todo
* some sort of static enum to get rid of the magic number
* method call #s from remote POV? I'll have the ctors store
* method labels in the base class dmcName map while it is
* setting up the dmc methods.
*
* @todo
* automatically declare the methods for method calling
* via some sort of macro or metacode?
*
*/
friend class session;
void methodCall(unsigned int idx) {
bvnet::scoped_lock lock(ctx.getMutex());
bvnet::value_queue &vqueue=ctx.getSendQueue();
auto dmcFunc=dmcTable.find(idx);
if (dmcFunc!=dmcTable.end()) {
(dmcFunc->second)(this,vqueue);
} else {
// called a method that doesn't exist
throw method_notimpl(getType(),idx);
}
}
protected:
session &ctx; /**< @brief for objects to attach to the session's registry */
call_map dmcTable; /**< @brief method mapper for method call and OO mechanism */
name_map dmcLabel; /**< @brief name labels of dmc methods */
void dmc_GetType(value_queue&); /**< @brief the GetType dispatched method call (dmc) */
private:
const string methodLabel(const unsigned int idx) {
const auto &s=dmcLabel.find(idx);
if (s!=dmcLabel.end()) {
return s->second;
}
return std::to_string(idx);
}
public:
/** @brief construction of an object @param sess reference to session to attach */
object(session &sess) :
ctx(sess) {
LOCK_COUT
cout << "object [" << this << "] ctor" << endl;
UNLOCK_COUT
ctx.register_object(this);
// dmtTable[0] is the GetType method
dmcTable[0]=&object::dmc_GetType;
dmcLabel[0]="GetType";
}
/** @brief base dtor to automatically unregister the object */
virtual ~object() {
LOCK_COUT
cout << "object [" << this << "] dtor" << endl;
UNLOCK_COUT
ctx.unregister(this);
}
/**
* @brief Get object's identity.
* @return Object identity string
*
* Overriden by superclass to announce it's identity.
*/
virtual const char *getType() {return "baseObject";}
};
这是修改为使用 dmc 机制后的新 serverRoot 对象的示例(单一的 MethodCall 和 switch 疯狂消失了):
class serverRoot : public bvnet::object {
private:
BigInt cli_pub_mod;
BigInt cli_pub_exp;
Key *clientKey;
bool clientValid;
string challenge;
SQLiteDB db;
unsigned int randbits[8];
protected:
void dmc_LoginClient(value_queue &vqueue);
void dmc_AnswerChallenge(value_queue &vqueue);
void dmc_GetAccount(value_queue &vqueue);
public:
serverRoot(bvnet::session &sess)
: bvnet::object(sess) {
dmcLabel[1]="LoginClient";
dmcTable[1]=(dmc)&serverRoot::dmc_LoginClient;
dmcLabel[2]="AnswerChallenge";
dmcTable[2]=(dmc)&serverRoot::dmc_AnswerChallenge;
dmcLabel[3]="GetAccount";
dmcTable[3]=(dmc)&serverRoot::dmc_GetAccount;
clientValid=false;
challenge="";
clientKey=NULL;
}
virtual ~serverRoot() {
if (clientKey!=NULL)
delete clientKey;
}
virtual const char *getType() {return "serverRoot";}
};
这里需要注意的重要一点是,在 serverRoot(或 Account 或 clientRoot 以及后续的 friend )中不再有 MethodCall,它也不必与 dmcLabel[0]/dmcTabel[0] 做任何事情——它继承了设置来自 bvnet::Object 的构造函数。出于同样的原因,一些 futureServerRoot 是 serverRoot 的子类,它将从 bvnet::Object 和 serverRoot 的 ctors 继承 dmc 表。因此,我现在在 dmc 层次结构中有一个适当的继承机制,并且没有来自 future 维护者的核弹电子邮件(无论如何不是因为那个原因)试图做 class futureServerRoot : public serverRoot ;)
总而言之,这是一个有用的重构。它消除了一堆样板(后续对象也不需要)。 dmc 样板在类中的体积更小(实现可以从内联类或其他类中移出)并且不再陷入来自 hell 的邪恶整体开关。
虽然不完美,但与我之前的相比有了很大的改进,它提供了我最初问题的答案。
关于c++ - 如何实现对 C++ 更友好/支持 OO 的网络方法调度机制?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27296631/