当前位置:编程学习 > C/C++ >>

Muduo网络编程示例之七:“串并转换”连接服务器及其自动化测试

   功能需求
这个连接服务器把多个客户连接汇聚为一个内部 TCP 连接,起到“数据串并转换”的作用,让 backend 的逻辑服务器专心处理业务,而无需顾及多连接的并发性。以下是系统的框图:

这个连接服务器的作用与数字电路中的数据选择器 (multiplexer) 类似,所以我把它命名为 multiplexer。(其实 IO-Multiplexing 也是取的这个意思,让一个 thread-of-control 能有选择地处理多个 IO 文件描述符。)

 

(上图取自 易做图,是 public domain 版权)

实现
Multiplexer 的功能需求不复杂,无非是在 backend connection 和 client connections 之间倒腾数据。具体来说,主要是处理四种事件:

对每个新 client connection 分配一个新的整数 id,如果 id 用完了,则断开新连接(这样通过控制 id 的数目就能控制最大连接数)。另外,为了避免 id 过快地被复用(有可能造成 backend 串话),multiplexer 采用 queue 来管理 free id,每次从队列的头部取 id,用完之后放回 queue 的尾部。
当 client connection 到达或断开时,向 backend 发出通知。onClientConnection() http://code.google.com/p/muduo/source/browse/tags/0.2.0/examples/multiplexer/multiplexer_易做图.cc#54
当从 client connection 收到数据时,把数据连同 connection id 一同发给 backend。onClientMessage() http://code.google.com/p/muduo/source/browse/tags/0.2.0/examples/multiplexer/multiplexer_易做图.cc#117
当从 backend connection 收到数据时,辨别数据是发给哪个 client connection,并执行相应的转发操作。onBackendMessage() http://code.google.com/p/muduo/source/browse/tags/0.2.0/examples/multiplexer/multiplexer_易做图.cc#194
如果 backend connection 断开连接,则断开所有 client connections(假设 client 会自动重试)。 onBackendConnection() http://code.google.com/p/muduo/source/browse/tags/0.2.0/examples/multiplexer/multiplexer_易做图.cc#162
由上可见,multiplexer 的功能与 proxy 颇为类似。multiplexer_易做图.cc 是一个线程版的实现,借助 muduo 的 io-multiplexing 特性,可以方便地处理多个并发连接。

在实现的时候有两点值得注意:

TcpConnection 的 id 如何存放?当从 backend 收到数据,如何根据 id 找到对应的 client connection?当从 client connection 收到数据,如何得知其 id ?
第一个问题比较好解决,用 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,
                      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 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(conn->getContext());
      sendBackendBuffer(id, buf);
    }
    else
    {
      buf->retrieveAll();
    }
  }
TcpConnection 的生命期如何管理?由于 Client Connection 是动态创建并销毁,其生与灭完全由客户决定,如何保证 backend 想向它发送数据的时候,这个 TcpConnection 对象还活着?解决思路是用 reference counting,当然,不用自己写,用 boost::shared_ptr 即可。TcpConnection 是 muduo 中唯一默认采用 shared_ptr 来管理生命期的对象,盖由其动态生命期的本质决定。更多内容请参考陈硕《当析构函数遇到多线程──C++ 中线程安全的对象回调》
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 会把各种 event 汇聚到一个 blocking queue 里边,方便编写 test case。Test case 则操纵 test harness,发起连接、发送数据、检查收到的数据,例如以下是其中一个 test case

http://code.google.com/p/muduo/source/browse/trunk/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientSend.java

这里的几个 test cases 都以用 java 直接写的,如果有必要,也可以采用 Groovy 来编写,这样可以在不重启 test harness 的情况下随时修改添加 test cases。具体做法见陈硕《“过家家”版的移动离线计费系统实现》。

将来的改进
有了这个自动化的 test harness,我们可以比较方便且安全地修改(甚至重新设计)multiplexer。例如

增加“backend 发送指令断开 client connection”的功能。有了自动化测试,这个新功能可以被单独测试(指开发者测试),而不需要真正的 backend 参与进来。
将 Multiplexer 改用多线程重写。有了自动化回归测试,我们不用担心破坏原有的功能,可以放心大胆地重写。而且由于 test harness 是从外部测试,不是单元测试,重写 multiplexer 的时候不用动 test cases,这样保证了测试的稳定性。另外,这个 test harness 稍作改进还可以进行 stress test

补充:软件开发 , C语言 ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,