陈硕的Blog

发布一个基于 Reactor 模式的 C++ 网络库

发布一个基于 Reactor 模式的 C++ 网络库

陈硕 (giantchen_AT_gmail)

Blog.csdn.net/Solstice

2010 Aug 30

本文主要介绍 muduo 网络库的使用。其设计与实现将有另文讲解。

目录

由来 1

下载与编译 2

例子 2

基本结构 3

公开接口 4

内部实现 4

线程模型 5

结语 5

由来

半年前我写了一篇《学之者生,用之者死——ACE历史与简评》,其中提到“我心目中理想的网络库”的样子:

  • 线程安全,支持多核多线程
  • 不考虑可移植性,不跨平台,只支持 Linux,不支持 Windows。
  • 在不增加复杂度的前提下可以支持 FreeBSD/Darwin,方便将来用 Mac 作为开发用机,但不为它做性能优化。也就是说 IO multiplexing 使用 poll 和 epoll。
  • 主要支持 x86-64,兼顾 IA32
  • 不支持 UDP,只支持 TCP
  • 不支持 IPv6,只支持 IPv4
  • 不考虑广域网应用,只考虑局域网
  • 只支持一种使用模式:non-blocking IO + one event loop per thread,不考虑阻塞 IO
  • API 简单易用,只暴露具体类和标准库里的类,不使用 non-trivial templates,也不使用虚函数
  • 只满足常用需求的 90%,不面面俱到,必要的时候以 app 来适应 lib
  • 只做 library,不做成 framework
  • 争取全部代码在 5000 行以内(不含测试)
  • 以上条件都满足时,可以考虑搭配 Google Protocol Buffers RPC

在想清楚这些目标之后,我开始第三次尝试编写自己的 C++ 网络库。与前两次不同,这次我一开始就想好了库的名字,叫 muduo (木铎),并在 Google code 上创建了项目: http://code.google.com/p/muduo/ 。muduo 的主体内容在 5 月底已经基本完成,现在我把它开源。

本文主要介绍 muduo 网络库的使用,其设计与实现将有另文讲解。

下载与编译

下载地址: http://muduo.googlecode.com/files/muduo-0.1.0-alpha.tar.gz

SHA1 Checksum: 5d3642e311177ded89ed0d15c10921738f8c984c

Muduo 使用了 Linux 较新的系统调用,要求 Linux 的内核版本大于 2.6.28 (我自己用的是 2.6.32 )。在 Debian Squeeze / Ubuntu 10.04 LTS 上编译测试通过,32 位和 64 位系统都能使用。

Muduo 采用 CMake 为 build system,安装方法:

$ sudo apt-get install cmake

Muduo 依赖 Boost,很容易安装:

$ sudo apt-get install libboost1.40-dev # 或 libboost1.42-dev

编译方法很简单:

$ tar zxf muduo-0.1.0-alpha.tar.gz

$ cd muduo/

$ ./build.sh

# 编译生成的可执行文件和静态库文件分别位于 ../build/debug/{bin,lib}

如果要编译 release 版,可执行

$ BUILD_TYPE=release ./build.sh

# 编译生成的可执行文件和静态库文件分别位于 ../build/release/{bin,lib}

编译完成之后请试运行其中的例子。比如 bin/inspector_test ,然后通过浏览器访问 http://10.0.0.10:12345/ 或 http://10.0.0.10:12345/proc/status,其中 10.0.0.10 替换为你的 Linux box 的 IP。

例子

Muduo 附带了几十个小例子,位于 examples 目录。其中包括从 Boost.Asio、JBoss Netty、Python Twisted 等处移植过来的例子。

examples

|-- simple # 简单网络协议的实现

|   |-- allinone  # 在一个程序里同时实现下面 5 个协议

|   |-- chargen   # RFC 864,可测试带宽

|   |-- daytime # RFC 867

|   |-- discard # RFC 863

|   |-- echo # RFC 862

|   |-- time # RFC 868

|   `-- timeclient # time 协议的客户端

|-- hub # 一个简单的 pub/sub/hub 服务,演示应用级的广播

|-- roundtrip # 测试两台机器的网络延时与时间差

|-- asio # 从 Boost.Asio 移植的例子

|   |-- chat # 聊天服务

|   `-- tutorial # 一系列 timers

|-- netty # 从 JBoss Netty 移植的例子

|   |-- discard # 可用于测试带宽,服务器可多线程运行

|   |-- echo # 可用于测试带宽,服务器可多线程运行

|   `-- uptime # TCP 长连接

`-- twisted # 从 Python Twisted 移植的例子

    `-- finger # finger01 ~ 07

基本结构

Muduo 的目录结构如下。

muduo

|-- base # 与网络无关的基础代码,已提前发布

`-- net # 网络库

    |-- http # 一个简单的可嵌入的 web 服务器

    |-- inspect # 基于以上 web 服务器的“窥探器”,用于报告进程的状态

    `-- poller # poll(2) 和 epoll(4) 两种 IO multiplexing 后端

Muduo 是基于 Reactor 模式的网络库,其核心是个事件循环 EventLoop,用于响应计时器和 IO 事件。Muduo 采用基于对象(object based)而非面向对象(object oriented)的设计风格,其接口多以 boost::function + boost::bind 表达

Muduo 的头文件明确分为客户可见和客户不可见两类。客户可见的为白底,客户不可见的为灰底。

inc

这里简单介绍各个头文件及 class 的作用,详细的介绍留给以后的博客。

公开接口
  • Buffer 仿 Netty ChannelBuffer 的 buffer class,数据的读写透过 buffer 进行
  • InetAddress 封装 IPv4 地址 (end point),注意,muduo 目前不能解析域名,只认 IP
  • EventLoop 反应器 Reactor,用户可以注册计时器回调
  • EventLoopThread 启动一个线程,在其中运行 EventLoop::loop()
  • TcpConnection 整个网络库的核心,封装一次 TCP 连接
  • TcpClient 用于编写网络客户端,能发起连接,并且有重试功能
  • TcpServer 用于编写网络服务器,接受客户的连接
  • 在这些类中,TcpConnection 的生命期依靠 shared_ptr 控制(即用户和库共同控制)。Buffer 的生命期由 TcpConnection 控制。其余类的生命期由用户控制。
  • HttpServer 和 Inspector,暴露出一个 http 界面,用于监控进程的状态,类似于 Java JMX。这么做的原因是,《程序员修炼之道》第 6 章第 34 条提到“对于更大、更复杂的服务器代码,提供其操作的内部试图的一种漂亮技术是使用内建的 Web 服务器”,Jeff Dean 也说“(每个 Google 的服务器进程)Export HTML-based status pages for easy diagnosis”。
内部实现
  • Channel 是 selectable IO channel,负责注册与响应 IO 事件,它不拥有 file descriptor。它是 Acceptor、Connector、EventLoop、TimerQueue、TcpConnection 的成员,生命期由后者控制。
  • Socket 封装一个 file descriptor,并在析构时关闭 fd。它是 Acceptor、TcpConnection 的成员,生命期由后者控制。EventLoop、TimerQueue 也拥有 fd,但是不封装为 Socket。
  • SocketsOps 封装各种 sockets 系统调用。
  • EventLoop 封装事件循环,也是事件分派的中心。它用 eventfd(2) 来异步唤醒,这有别于传统的用一对 pipe(2) 的办法。它用 TimerQueue 作为计时器管理,用 Poller 作为 IO Multiplexing。
  • Poller 是 PollPoller 和 EPollPoller 的基类,采用“电平触发”的语意。它是 EventLoop 的成员,生命期由后者控制。
  • PollPoller 和 EPollPoller 封装 poll(2) 和 epoll(4) 两种 IO Multiplexing 后端。Poll 的存在价值是便于调试,因为 poll(2) 调用是上下文无关的,用 strace 很容易知道库的行为是否正确。
  • Connector 用于发起 TCP 连接,它是 TcpClient 的成员,生命期由后者控制。
  • Acceptor 用于接受 TCP 连接,它是 TcpServer 的成员,生命期由后者控制。
  • TimerQueue 用 timerfd 实现定时,这有别于传统的设置 poll/epoll_wait 的等待时长的办法。为了简单起见,目前用链表来管理 Timer,如果有必要可改为优先队列,这样复杂度可从 O(n) 降为 O(ln n) (某些操作甚至是 O(1))。它是 EventLoop 的成员,生命期由后者控制。
  • EventLoopThreadPool 用于创建 IO 线程池,也就是说把 TcpConnection 分派到一组运行 EventLoop 的线程上。它是 TcpServer 的成员,生命期由后者控制。

线程模型

Muduo 的线程模型符合我主张的 one loop per thread + thread pool 模型。每个线程最多有一个 EventLoop。每个 TcpConnection 必须归某个 EventLoop 管理,所有的 IO 会转移到这个线程,换句话说一个 file descriptor 只能由一个线程读写。TcpConnection 所在的线程由其所属的 EventLoop 决定,这样我们可以很方便地把不同的 TCP 连接放到不同的线程去,也可以把一些 TCP 连接放到一个线程里。TcpConnection 和 EventLoop 是线程安全的,可以跨线程调用。TcpServer 直接支持多线程,它有两种模式:

1. 单线程,accept 与 TcpConnection 用同一个线程做 IO。

2. 多线程,accept 与 EventLoop 在同一个线程,另外创建一个 EventLoopThreadPool,新到的连接会按 round-robin 方式分配到线程池中。

结语

Muduo 是我对常见网络编程任务的总结,用它我能很容易地编写多线程的 TCP 服务器和客户端。Muduo 是我业余时间的作品,代码估计还有很多 bug,功能也不完善(例如不支持 signal 处理),待日后慢慢改进吧。

posted on 2010-08-29 23:42 陈硕 阅读(11986) 评论(20)  编辑 收藏 引用 所属分类: muduo

评论

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-08-30 08:25 路青飞

超赞!
有个小问题,为什么不支持Windows呢?  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-08-30 08:56 陈硕

@路青飞
因为我对 Windows 编程不熟。  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-08-30 09:07 expter

很好,下载学习下。。  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-08-30 09:48 dennis-zhuang

不支持udp是基于什么考虑?
timerQueue替换成优先队列也是O(lg(n))的复杂度吧,而不是O(1)
貌似没有实现基于select的Poller。
代码很清晰,感谢。  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-08-30 10:41 陈硕

@dennis-zhuang
> 不支持udp是基于什么考虑?
因为我没搞过 UDP 编程,没有一手的经验。

> timerQueue替换成优先队列也是O(lg(n))的复杂度吧,而不是O(1)
删除的复杂度是 O(ln n)。插入的平均复杂度是 O(1),最坏复杂度是 O(ln n)。已订正原文,多谢。

> 貌似没有实现基于select的Poller。
确实,因为 select 比 poll 限制更多,有了 poll 和 epoll,没必要再实现 select。

> 代码很清晰,感谢。
thanks.  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-02 12:18 梨树阳光

非常不错,下来看看  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-02 23:41 chaogu

不知楼主试过没,Linux开50个线程机器会很卡,windows开50个的时候对机器的运行不影响。老实说我不是很明白。能不能解释一下。
Linux是用的pthread
Windows用的是win32API
会不会是我使用不对啊(不要说开50个线程不对,我只是觉得Linux的线程应该比Windows好,开相同多的线程应该Linux好很多)。
老实说Linux我还是菜鸟。  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-03 08:12 陈硕

@chaogu
我没有遇到过,你的线程函数长什么样?用哪种同步机制?  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库[未登录] 2010-09-03 09:10 cppexplore

@chaogu
linux的线程调度比windows的差, 有资料表明,windows上线程的调度切换是linux上的1/30大概.
另linux可以把进程和cpu绑定,而线程:低内核的linux则没有相关函数, 2.6小版本内核有相关函数,但绑定不成功, 只有最新的linux内核才可以。
  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-03 11:02 chaogu

@陈硕
函数是_beginthreadex
同步用的是WaitForSingleObject。  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-03 11:22 陈硕

@chaogu
Linux 下呢?有没有 busy waiting?  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-03 11:26 chaogu

@陈硕
其他的是一样的。代码上的区别只是线程上的不同。
Linux上用的就是pthread,同步用的是pthread_mutex_unlock(就是加锁)。
Windows用WaitForSingleObject也就为了加锁。  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-03 12:18 陈硕

@chaogu
Linux 上用什么方式等待?  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-03 13:10 chaogu

@陈硕
while(true)
难道有问题?  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-03 13:29 陈硕

@chaogu
循环体内有没有 pthread_cond_wait ? 或者贴一下代码骨架吧。  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-03 16:46 chaogu

1 queue<sometype> shareQueue 2 3 main{ 4 run_server() 5 } 6 run_server{ 7 //do something init 8 .... 9 10 //create thread 11 for(int i = 0;i < pollsize;++i){ 12 pthread_attr_init(attr[i]) 13 pthread_attr_setstacksize(attr[i],1024*120) 14 threads[i] = pthread_create(threadids[i],attr[i],somefunc,args[i]) 15 } 16 17 while(true){ 18 __createShareObject__(shareObject) //Pseudo-code 19 pthread_mutex_lock(&mutex); 20 shareQuueue.push(shareObject); 21 pthread_mutex_unlock(&mutex); 22 } 23 24 //clean up 25 ..... 26 } 27 28 somefunc(){ 29 while(true){ 30 pthread_mutex_lock(&mutex); 31 if(shareQueue.size() < 1){ 32 pthead_mutex_unlock(&mutex); 33 continue; 34 }else{ 35 shareObject = shareQueue.pop(); 36 pthread_mutex_unlock(&mutex); 37 } 38 __useShareObjectDoSomething__ //pseudo-code 39 } 40 } 41 42 43 不知这样是否能看懂,不是我的代码要保密,而是代码有点难看
就是我看也要整半天才能看懂。这个结构也就够清晰了。Windows里
面的结构是一样的,只不过pthread的函数换成win32api
  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-03 22:32 陈硕

@chaogu
这是典型的 busy-waiting,建议改为:

19 pthread_mutex_lock(&mutex);
20 shareQuueue.push(shareObject);
++ pthread_cond_signal(&condvar);
21 pthread_mutex_unlock(&mutex);

30 pthread_mutex_lock(&mutex);
31 while (shareQueue.size() < 1){
++ pthread_cond_wait(&condvar, &mutex);
34 }
35 shareObject = shareQueue.pop();
36 pthread_mutex_unlock(&mutex);

参考:
http://github.com/chenshuo/recipes/blob/master/thread/BlockingQueue.h  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-04 21:19 cpp

std::vector<char> buffer_;
看过mangos里也是采用这个来装buffer。
vector和char [固定长度],请问你认为有什么优势呢?  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2010-09-04 21:27 陈硕

@cpp
vector 能伸缩呗,适合不定长的消息。  回复  更多评论   

# re: 发布一个基于 Reactor 模式的 C++ 网络库 2015-11-06 22:39 孙国栋

Makefile:126: recipe for target 'all' failed
什么原因,谢谢?  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


<2010年9月>
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

导航

统计

常用链接

随笔分类

随笔档案

相册

搜索

最新评论

阅读排行榜

评论排行榜