我正在使用一种协议,它基本上是基于 TCP 的请求和响应协议,类似于其他基于行的协议(SMTP、HTTP 等)。
该协议有大约 130 种不同的请求方法(例如登录、用户添加、用户更新、日志获取、文件信息、文件信息……)。所有这些方法都不能很好地映射到 HTTP 中使用的广泛方法(GET、POST、PUT、...)。如此宽泛的方法会引入一些对实际含义的不相关的曲解。
但是协议方法可以按类型分组(例如,用户管理、文件管理、会话管理……)。
当前的服务器端实现使用class Worker
with 方法ReadRequest()
(读取请求,由方法和参数列表组成),HandleRequest()
(见下文)和WriteResponse()
(写入响应代码和实际响应数据)。
HandleRequest()
将调用实际请求方法的函数 - 使用方法名称的哈希映射到指向实际处理程序的成员函数指针。
实际的处理程序是一个普通的成员函数,每个协议方法都有一个:每个方法验证其输入参数,执行它必须做的任何事情并设置响应代码(成功是/否)和响应数据。
示例代码:
class Worker {
typedef bool (Worker::*CommandHandler)();
typedef std::map<UTF8String,CommandHandler> CommandHandlerMap;
// handlers will be initialized once
// e.g. m_CommandHandlers["login"] = &Worker::Handle_LOGIN;
static CommandHandlerMap m_CommandHandlers;
bool HandleRequest() {
CommandHandlerMap::const_iterator ihandler;
if( (ihandler=m_CommandHandlers.find(m_CurRequest.instruction)) != m_CommandHandler.end() ) {
// call actual handler
return (this->*(ihandler->second))();
}
// error case:
m_CurResponse.success = false;
m_CurResponse.info = "unknown or invalid instruction";
return true;
}
//...
bool Handle_LOGIN() {
const UTF8String username = m_CurRequest.parameters["username"];
const UTF8String password = m_CurRequest.parameters["password"];
// ....
if( success ) {
// initialize some state...
m_Session.Init(...);
m_LogHandle.Init(...);
m_AuthHandle.Init(...);
// set response data
m_CurResponse.success = true;
m_CurResponse.Write( "last_login", ... );
m_CurResponse.Write( "whatever", ... );
} else {
m_CurResponse.Write( "error", "failed, because ..." );
}
return true;
}
};
所以。问题是:我的工人班现在有大约 130 个“命令处理程序方法”。每个人都需要访问:
- 请求参数
- 响应对象(写入响应数据)
- 不同的其他会话本地对象(如数据库句柄、授权/权限查询句柄、日志记录、服务器各种子系统的句柄等)
什么是更好地构造这些命令处理程序方法的好策略?
一个想法是每个命令处理程序有一个类,并使用对请求、响应对象等的引用对其进行初始化-但恕我直言,开销是不可接受的(实际上,它会为对处理程序所需的所有内容的任何单一访问添加间接性:请求,响应,会话对象,...)。如果它能够提供实际优势,它是可以接受的。但是,这听起来不太合理:
class HandlerBase {
protected:
Request &request;
Response &response;
Session &session;
DBHandle &db;
FooHandle &foo;
// ...
public:
HandlerBase( Request &req, Response &rsp, Session &s, ... )
: request(req), response(rsp), session(s), ...
{}
//...
virtual bool Handle() = 0;
};
class LoginHandler : public HandlerBase {
public:
LoginHandler( Request &req, Response &rsp, Session &s, ... )
: HandlerBase(req,rsp,s,..)
{}
//...
virtual bool Handle() {
// actual code for handling "login" request ...
}
};
好的,HandlerBase 可以只获取对工作对象本身的引用(或指针)(而不是对请求、响应等的引用)。但这也会增加另一个间接性(this->worker->session 而不是 this->session)。这种间接性是可以的,如果它毕竟能买到一些优势的话。
关于整体架构的一些信息
worker 对象代表一个工作线程,用于与某个客户端的实际 TCP 连接。每个线程(因此,每个工作人员)都需要自己的数据库句柄、授权句柄等。这些“句柄”是每个线程的对象,允许访问服务器的某些子系统。
整个架构基于某种依赖注入:例如,要创建会话对象,必须为会话构造函数提供“数据库句柄”。然后会话对象使用这个数据库句柄来访问数据库。它永远不会调用全局代码或使用单例。因此,每个线程都可以不受干扰地独立运行。
但代价是——而不是仅仅调用单例对象——worker 及其命令处理程序必须通过这种特定于线程的句柄访问系统的任何数据或其他代码。这些句柄定义了它的执行上下文。
摘要和澄清:我的实际问题
我正在寻找一个优雅的替代当前(“具有大量处理程序方法列表的工作对象”)解决方案:它应该是可维护的,具有低开销并且不需要编写太多的胶水代码。此外,它必须仍然允许每个方法控制其执行的非常不同的方面(这意味着:如果方法“superflurry foo”想要在满月时失败,那么该实现必须有可能这样做) . 这也意味着,我不希望在我的代码的这个架构层(它存在于我的代码中的不同层)有任何类型的实体抽象(创建/读取/更新/删除 XFoo 类型)。这个架构层是纯协议,没有别的。
最后,肯定会妥协,但我对任何想法都感兴趣!
AAA 奖励:具有可互换协议实现的解决方案(而不仅仅是class Worker
负责解析请求和编写响应的 current )。可能有一个可互换的class ProtocolSyntax
,它处理那些协议语法细节,但仍然使用我们新的闪亮的结构化命令处理程序。