Muduo 网络编程示例之七:连接服务器及其自动化测试
陈硕 (giantchen_AT_gmail)
Blog.csdn.net/Solstice t.sina.com.cn/giantchen
这是《Muduo 网络编程示例》系列的第七篇文章。
Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category/779646.aspx
本文介绍如何使用 test harness 来测试一个具有内部逻辑的网络服务程序。
本文的代码见 http://code.google.com/p/muduo/source/browse/trunk/examples/multiplexer
下载地址:http://muduo.googlecode.com/files/muduo-0.2.0-alpha.tar.gz SHA1 checksum: 75a09a82f96b583004876e95105c679e64c95715
云风在他的博客中提到了网游连接服务器的功能需求(搜“练手项目”),我用 C++ 初步实现了这些需求,并为之编写了配套的自动化 test harness,作为 muduo 网络库的示例。
注意:本文呈现的代码仅仅实现了基本的功能需求,没有考虑安全性,也没有特别优化性能,不适合用作真正的放在公网上运行的网游连接服务器。
这个连接服务器把多个客户连接汇聚为一个内部 TCP 连接,起到“数据串并转换”的作用,让 backend 的逻辑服务器专心处理业务,而无需顾及多连接的并发性。以下是系统的框图:
这个连接服务器的作用与数字电路中的数据选择器 (multiplexer) 类似,所以我把它命名为 multiplexer。(其实 IO-Multiplexing 也是取的这个意思,让一个 thread-of-control 能有选择地处理多个 IO 文件描述符。)
(上图取自 wikipedia,是 public domain 版权)
Multiplexer 的功能需求不复杂,无非是在 backend connection 和 client connections 之间倒腾数据。具体来说,主要是处理四种事件:
由上可见,multiplexer 的功能与 proxy 颇为类似。multiplexer_simple.cc 是一个单线程版的实现,借助 muduo 的 io-multiplexing 特性,可以方便地处理多个并发连接。
在实现的时候有两点值得注意:
第一个问题比较好解决,用 std::map〈int, TcpConnectionPtr〉 clientConns_; 保存从 id 到 client connection 的映射就行。
第二个问题固然可以用类似的办法解决,但是我想借此介绍一下 muduo::net::TcpConnection 的 context 功能。每个 TcpConnection 都有一个 boost::any 成员,可由客户代码自由支配(get/set),代码如下。这个 boost::any 是 TcpConnection 的 context,可以用于保存与 connection 绑定的任意数据(比方说 connection id、connection 的最后数据到达时间、connection 所代表的用户的名字等等)。这样客户代码不必继承 TcpConnection 就能 attach 自己的状态,而且也用不着 TcpConnectionFactory 了(如果允许继承,那么必然要向 TcpServer 注入此 factory)。
class TcpConnection : public boost::enable_shared_from_this<TcpConnection>, boost::noncopyable { public: void setContext(const boost::any& context) { context_ = context; } boost::any& getContext() { return context_; } const boost::any& getContext() const { return context_; } // ... private: // ... boost::any context_; }; typedef boost::shared_ptr<TcpConnection> TcpConnectionPtr;
对于 Multiplexer,在 onClientConnection() 里调用 conn->setContext(id),把 id 存到 TcpConnection 对象中。onClientMessage() 从 TcpConnection 对象中取得 id,连同数据一起发送给 backend,完整实现如下:
void onClientMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp)
{
if (!conn->getContext().empty())
{
int id = boost::any_cast<int>(conn->getContext());
sendBackendBuffer(id, buf);
}
else
{
buf->retrieveAll();
}
}
multiplexer 是二进制协议,如何测试呢?
Multiplexer 是 muduo 网络编程示例中第一个具有 non-trivial 业务逻辑的网络程序,根据陈硕《分布式程序的自动化回归测试》一文的思想,我为它编写了 test harness。代码见 http://code.google.com/p/muduo/source/browse/trunk/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer
这个 Test harness 采用 Java 编写,用的是 Netty 库。这个 test harness 要扮演 clients 和 backend,也就是既要主动发起连接,也要被动接受连接。而且,test harness 与 multiplexer 的启动顺序是任意的,如何做到这一点请阅读代码。结构如下:
Test harness 会把各种 event 汇聚到一个 blocking queue 里边,方便编写 test case。Test case 则操纵 test harness,发起连接、发送数据、检查收到的数据,例如以下是其中一个 test case
这里的几个 test cases 都以用 java 直接写的,如果有必要,也可以采用 Groovy 来编写,这样可以在不重启 test harness 的情况下随时修改添加 test cases。具体做法见陈硕《“过家家”版的移动离线计费系统实现》。