Muduo 网络编程示例

125 views
Skip to first unread message

Shuo Chen

unread,
Feb 2, 2011, 10:45:05 PM2/2/11
to TopLanguage
前言

http://blog.csdn.net/Solstice/archive/2011/02/02/6171831.aspx

陈硕 (giantchen_AT_gmail)

Blog.csdn.net/Solstice

Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category/779646.aspx

我将会写一系列文章,介绍用 muduo 网络库完成常见的 TCP 网络编程任务。目前计划如下:

1. UNP 中的简单协议,包括 echo、daytime、time、discard 等。
2. Boost.Asio 中的示例,包括 timer2~6、chat 等。
3. Java Netty 中的示例,包括 discard、echo、uptime 等,其中的 discard 和 echo 带流量统计
功能。
4. Python twisted 中的示例,包括 finger01~07
5. 用于测试两台机器的往返延迟的 roundtrip
6. 用于测试两台机器的带宽的 pingpong
7. 云风的串并转换连接服务器 multiplexer,包括单线程和多线程两个版本。
8. 文件传输
9. 一个基于 TCP 的应用层广播 hub
10. socks4a 代理服务器,包括简单的 TCP 中继(relay)。
11. 一个 Sudoku 服务器的演变,从单线程到多线程,从阻塞到 event-based。
12. 一个提供短址服务的 httpd 服务器

其中前面 7 个已经放到了 muduo 代码的 examples 目录中,下载地址是:
http://muduo.googlecode.com/files/muduo-0.1.6-alpha.tar.gz
在线阅读:
http://code.google.com/p/muduo/source/browse/#svn%2Ftrunk

Shuo Chen

unread,
Feb 2, 2011, 10:47:03 PM2/2/11
to TopLanguage
TCP 网络编程本质论

我认为,TCP 网络编程最本质的是处理三个半事件:

1. 连接的建立,包括服务端接受 (accept) 新连接和客户端成功发起 (connect) 连接。
2. 连接的断开,包括主动断开 (close 或 shutdown) 和被动断开 (read 返回 0)。
3. 消息到达,文件描述符可读。这是最为重要的一个事件,对它的处理方式决定了网络编程的风格(阻塞还是非阻塞,如何处理分包,应用层的缓冲如
何设计等等)。
4. 消息发送完毕,这算半个。对于低流量的服务,可以不必关心这个事件;另外,这里"发送完毕"是指将数据写入操作系统的缓冲区,将由
TCP 协议栈负责数据的发送与重传,不代表对方已经收到数据。

这其中有很多难点,也有很多细节需要注意,比方说:

1. 如果要主动关闭连接,如何保证对方已经收到全部数据?如果应用层有缓冲(这在非阻塞网络编程中是必须的,见下文),那么如何保证先发送完缓
冲区中的数据,然后再断开连接。直接调用 close(2) 恐怕是不行的。
2. 如果主动发起连接,但是对方主动拒绝,如何定期 (带 back-off) 重试?
3. 非阻塞网络编程该用边沿触发(edge trigger)还是电平触发(level trigger)?(这两个中文术语有其他译法,我选
择了一个电子工程师熟悉的说法。)如果是电平触发,那么什么时候关注 EPOLLOUT 事件?会不会造成 busy-loop?如果是边沿触发,如何
防止漏读造成的饥饿?epoll 一定比 poll 快吗?
4. 在非阻塞网络编程中,为什么要使用应用层缓冲区?假如一次读到的数据不够一个完整的数据包,那么这些已经读到的数据是不是应该先暂存在某个
地方,等剩余的数据收到之后再一并处理?见 lighttpd 关于 \r\n\r\n 分包的 bug。假如数据是一个字节一个字节地到达,间隔
10ms,每个字节触发一次文件描述符可读 (readable) 事件,程序是否还能正常工作?lighttpd 在这个问题上出过安全漏洞。
5. 在非阻塞网络编程中,如何设计并使用缓冲区?一方面我们希望减少系统调用,一次读的数据越多越划算,那么似乎应该准备一个大的缓冲区。另一
方面,我们系统减少内存占用。如果有 10k 个连接,每个连接一建立就分配 64k 的读缓冲的话,将占用 640M 内存,而大多数时候这些缓冲区
的使用率很低。muduo 用 readv 结合栈上空间巧妙地解决了这个问题。
6. 如果使用发送缓冲区,万一接收方处理缓慢,数据会不会一直堆积在发送方,造成内存暴涨?如何做应用层的流量控制?
7. 如何设计并实现定时器?并使之与网络 IO 共用一个线程,以避免锁。

这些问题在 muduo 的代码中可以找到答案。

On Feb 3, 11:45 am, Shuo Chen <giantc...@gmail.com> wrote:
> 前言
>

Shuo Chen

unread,
Feb 2, 2011, 10:48:53 PM2/2/11
to TopLanguage
Muduo 简介

我编写 Muduo 网络库的目的之一就是简化日常的 TCP 网络编程,让程序员能把精力集中在业务逻辑的实现上,而不要天天和 Sockets
API 较劲。借用 Brooks 的话说,我希望 Muduo 能减少网络编程中的偶发复杂性 (accidental complexity)。

Muduo 只支持 Linux 2.6.x 下的并发非阻塞 TCP 网络编程,它的安装方法见陈硕的 blog 文章。
http://blog.csdn.net/Solstice/archive/2010/08/29/5848547.aspx

Muduo 的使用非常简单,不需要从指定的类派生,也不用覆写虚函数,只需要注册几个回调函数去处理前面提到的三个半事件就行了。

以经典的 echo 回显服务为例:
http://code.google.com/p/muduo/source/browse/#svn%2Ftrunk%2Fexamples%2Fsimple%2Fecho

yuan zhu

unread,
Feb 3, 2011, 12:41:06 AM2/3/11
to pon...@googlegroups.com
一直在关注你博客的人飘过.....

陈大家的这个玩意好像已有小成了?libevent2在1月也终于出stable版了,陈大家好像有比一比的想法?

io并发俺觉得还是蛮重要的,reactor终究只能适合业务瓶颈非io的时候。可是业务多迭代几次的话.......

当然原生的proator在unix及其后代中的实现好像都比较难,是因为没有一个比较好的内核线程与用户线程通信的方法?

鄙人陋见,还请诸位大家多多指点。本命年寡居深圳的我,恭祝大家,新春快乐。

2011/2/3 Shuo Chen <gian...@gmail.com>

Shuo Chen

unread,
Feb 3, 2011, 8:27:13 AM2/3/11
to TopLanguage
一个是跨平台的 C 语言的库,一个是专为 Linux/pthreads/TCP 准备 C++ 的库,功能方面没什么好比的。
性能方面我已经比过了:
http://blog.csdn.net/Solstice/archive/2010/09/05/5864889.aspx
http://blog.csdn.net/Solstice/archive/2010/09/08/5869801.aspx

"业务多迭代几次"是什么意思?有什么问题?

On Feb 3, 1:41 pm, yuan zhu <zy498...@gmail.com> wrote:
> 一直在关注你博客的人飘过.....
>
> 陈大家的这个玩意好像已有小成了?libevent2在1月也终于出stable版了,陈大家好像有比一比的想法?
>
> io并发俺觉得还是蛮重要的,reactor终究只能适合业务瓶颈非io的时候。可是业务多迭代几次的话.......
>
> 当然原生的proator在unix及其后代中的实现好像都比较难,是因为没有一个比较好的内核线程与用户线程通信的方法?
>
> 鄙人陋见,还请诸位大家多多指点。本命年寡居深圳的我,恭祝大家,新春快乐。
>

> 2011/2/3 Shuo Chen <giantc...@gmail.com>


>
> > Muduo 简介
>
> > 我编写 Muduo 网络库的目的之一就是简化日常的 TCP 网络编程,让程序员能把精力集中在业务逻辑的实现上,而不要天天和 Sockets
> > API 较劲。借用 Brooks 的话说,我希望 Muduo 能减少网络编程中的偶发复杂性 (accidental complexity)。
>
> > Muduo 只支持 Linux 2.6.x 下的并发非阻塞 TCP 网络编程,它的安装方法见陈硕的 blog 文章。
> >http://blog.csdn.net/Solstice/archive/2010/08/29/5848547.aspx
>
> > Muduo 的使用非常简单,不需要从指定的类派生,也不用覆写虚函数,只需要注册几个回调函数去处理前面提到的三个半事件就行了。
>
> > 以经典的 echo 回显服务为例:
>

> >http://code.google.com/p/muduo/source/browse/#svn%2Ftrunk%2Fexamples%...

yuan zhu

unread,
Feb 3, 2011, 9:20:11 AM2/3/11
to pon...@googlegroups.com
有时候业务的瓶颈在带宽上,后来加了几个应用,结果cpu又成瓶颈了,很难把2者吞吐量都在保证服务质量的前提下提升到极限。
总体来说,用reactor模式始终感觉有些浪费多任务操作系统的并发能力,就像你说的:“...由于 libevent2 每次最多从网络读取 4096 字节,大大限制了它的吞吐量。

2011/2/3 Shuo Chen <gian...@gmail.com>

Shuo Chen

unread,
Feb 3, 2011, 9:29:27 AM2/3/11
to TopLanguage
要想充分利用多核,那就每个线程一个 Reactor 呗,把已建立的连接分散到多个线程去处理,就像 Muduo 的 EventLoop/
EventLoopThread/EventLoopThreadPool 一样。

On Feb 3, 10:20 pm, yuan zhu <zy498...@gmail.com> wrote:
> 有时候业务的瓶颈在带宽上,后来加了几个应用,结果cpu又成瓶颈了,很难把2者吞吐量都在保证服务质量的前提下提升到极限。
> 总体来说,用reactor模式始终感觉有些浪费多任务操作系统的并发能力,就像你说的:"...由于 libevent2 每次最多从网络读取 4096
> 字节,大大限制了它的吞吐量。"
>

> 2011/2/3 Shuo Chen <giantc...@gmail.com>

yuan zhu

unread,
Feb 3, 2011, 9:39:12 AM2/3/11
to pon...@googlegroups.com
并发策略如果和线程策略耦合总是感觉不太好。

关键是想利用好内核的io并发能力,而不是利用2.6内核的内核可抢占特性来在用户进程中模拟并发。

2011/2/3 Shuo Chen <gian...@gmail.com>

Shuo Chen

unread,
Feb 3, 2011, 10:01:13 AM2/3/11
to TopLanguage
"利用内核可抢占特性来在用户进程中模拟并发" 是什么意思?
跟"利用好内核的io并发能力"有什么冲突或矛盾?

On Feb 3, 10:39 pm, yuan zhu <zy498...@gmail.com> wrote:
> 并发策略如果和线程策略耦合总是感觉不太好。
>
> 关键是想利用好内核的io并发能力,而不是利用2.6内核的内核可抢占特性来在用户进程中模拟并发。
>

> 2011/2/3 Shuo Chen <giantc...@gmail.com>

jinhu wang

unread,
Feb 4, 2011, 9:50:02 AM2/4/11
to pon...@googlegroups.com
我觉得封装socket的库之所以难以普及,主要原因有这么几个:
1。socket作为网络通信的facade,socket的设计者初衷就是觉得socket系列接口已经是他们认为的网络通信层里最简洁的接口了。但是事实上socket引入的概念复杂度远远大于创始人的初衷,这也是网络程序员面临的一个尴尬,如shuo所言:天天和socket api较劲。socket真正的复杂90%集中在了tcp网络编程上了,tcp协议栈的设计决定了他会提供一个严谨的通信通道,而他的用户要想很好的用它,就得很严谨的设计要实现的业务逻辑。在这样的大前提下封装一个依然暴露socket概念的库,如果用户对socket的机制原理理解不深的话,往往会引入更深一层的复杂度,一个是加高了用户入门的门槛,再一个是增加了用户维护的成本
2.针对tcp协议栈的复杂性的,网络层提供了一个sctp协议标准对tcp进行了进一步的简化,在可靠性等方面也有一些增强,有些操作系统对这一协议栈提供支持。还有个开源的tipc(应该也是E的杰作)有针对性的对集群分布式机器的通信函数库,但是通信层每增加一个概念,socket的宝典就要增加几十页。大师们在这一层的优化往往也很难普及。
3.常见的(可能也是更高明更成功的)是一些公司干脆将tcp或udp等概念藏起来,封装一层自己的概念,如“消息队列”,“signal”之类的叫法,使得开发者真正把精力集中在业务上,使得socket在业务层面上真正消失了。

jinhu wang

unread,
Feb 4, 2011, 10:14:29 AM2/4/11
to pon...@googlegroups.com
另外这个库对boost的依赖,也是一个门槛,boost的普及度决定着这一门槛的高低。
另一方面多线程单线程的思想并存也可能会加高门槛,例如:定时器使用主线程,是一个很好的做法,但是又为开发者提供了用线程池里的新线程处理新会话的弹性,这样也不错,但是两个机制合在一起感觉就会很怪,这样设计的理由可以再充分一下。例如针对短会话的tcp是不是可以考虑只用一个主线程干所有的活。
还有些涉及到界面的应用,例如iphone程序的开发,还要兼顾跟前台界面的交互,如果这个库能提供一个前后台的交互通道,那就更好了。

Shuo Chen

unread,
Feb 4, 2011, 11:49:17 PM2/4/11
to TopLanguage
Muduo 网络编程示例之二:Boost.Asio 的聊天服务器

这是《Muduo 网络编程示例》系列的第二篇文章。

Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category/779646.aspx

本文讲介绍一个与 Boost.Asio 的示例代码中的聊天服务器功能类似的网络服务程序,
包括客户端与服务端的 muduo 实现。这个例子的主要目的是介绍如何处理 TCP 分包,并
初步涉及 Muduo 的多线程功能。Muduo 的下载地址:
http://muduo.googlecode.com/files/muduo-0.1.7-alpha.tar.gz ,本文的完整代码可
在线阅读 http://code.google.com/p/muduo/source/browse/trunk/examples/asio/chat/

*** TCP 分包 ***

前面一篇《五个简单 TCP 协议》中处理的协议没有涉及分包,在 TCP 这种字节流协议
上做应用层分包是网络编程的基本需求。分包指的是在发生一个消息(message)或一
帧(frame)数据时,通过一定的处理,让接收方能从字节流中识别并截取(还原)出一个个
消息。"粘包问题"是个伪问题。

对于短连接的 TCP 服务,分包不是一个问题,只要发送方主动关闭连接,就表示一条
消息发送完毕,接收方 read() 返回 0,从而知道消息的结尾。例如前一篇文章里的 daytime
和 time 协议。

对于长连接的 TCP 服务,分包有四种方法:

1. 消息长度固定,比如 muduo 的 roundtrip 示例就采用了固定的 16 字节消息;
2. 使用特殊的字符或字符串作为消息的边界,例如 HTTP 协议的 headers 以 "\r\n" 为字段的分隔符;
3. 在每条消息的头部加一个长度字段,这恐怕是最常见的做法,本文的聊天协议也采用这一办法;
4. 利用消息本身的格式来分包,例如 XML 格式的消息中 <root>...</root> 的配对,或者 JSON 格式中的
{ ... } 的配对。解析这种消息格式通常会用到状态机。

在后文的代码讲解中还会仔细讨论用长度字段分包的常见陷阱。

*** 聊天服务 ***

本文实现的聊天服务非常简单,由服务端程序和客户端程序组成,协议如下:

* 服务端程序中某个端口侦听 (listen) 新的连接;
* 客户端向服务端发起连接;
* 连接建立之后,客户端随时准备接收服务端的消息并在屏幕上显示出来;
* 客户端接受键盘输入,以回车为界,把消息发送给服务端;
* 服务端接收到消息之后,依次发送给每个连接到它的客户端;原来发送消息的客户端进程也会收到这条消息;
* 一个服务端进程可以同时服务多个客户端进程,当有消息到达服务端后,每个客户端进程都会收到同一条消息,服务端广播发送消息的顺序是任意
的,不一定哪个客户端会先收到这条消息。
* (可选)如果消息 A 先于消息 B 到达服务端,那么每个客户端都会先收到 A 再收到 B。

这实际上是一个简单的基于 TCP 的应用层广播协议,由服务端负责把消息
发送给每个连接到它的客户端。参与"聊天"的既可以是人,也可以是程序。
在以后的文章中,我将介绍一个稍微复杂的一点的例子 hub,它有"聊天室"
的功能,客户端可以注册特定的 topic(s),并往某个 topic 发送消息,这样
代码更有意思。
消息格式

本聊天服务的消息格式非常简单,"消息"本身是一个字符串,每条消息有
一个 4 字节的头部,以网络序存放字符串的长度。消息之间没有间隙,字
符串也不一定以 '\0' 结尾。比方说有两条消息 "hello" 和 "chenshuo",那么
打包后的字节流是:

0x00, 0x00, 0x00, 0x05, 'h', 'e', 'l', 'l', 'o', 0x00, 0x00, 0x00,
0x08, 'c', 'h', 'e', 'n', 's', 'h', 'u', 'o'

共 21 字节。

*** 打包的代码 ***

略,请阅读原文,地址见后。

*** 分包的代码 ***

解析数据往往比生成数据复杂,分包打包也不例外。

1: void onMessage(const muduo::net::TcpConnectionPtr& conn,
2: muduo::net::Buffer* buf,
3: muduo::Timestamp receiveTime)
4: {
5: while (buf->readableBytes() >= kHeaderLen)
6: {
7: const void* data = buf->peek();
8: int32_t tmp = *static_cast<const int32_t*>(data);
9: int32_t len = muduo::net::sockets::networkToHost32(tmp);
10: if (len > 65536 || len < 0)
11: {
12: LOG_ERROR << "Invalid length " << len;
13: conn->shutdown();
14: }
15: else if (buf->readableBytes() >= len + kHeaderLen)
16: {
17: buf->retrieve(kHeaderLen);
18: muduo::string message(buf->peek(), len);
19: buf->retrieve(len);
20: messageCallback_(conn, message, receiveTime); // 收到完整的消息,通
知用户
21: }
22: else
23: {
24: break;
25: }
26: }
27: }

上面这段代码第 7 行用了 while 循环来反复读取数据,直到 Buffer 中
的数据不够一条完整的消息。请读者思考,如果换成
if (buf->readableBytes() >= kHeaderLen) 会有什么后果。

以前面提到的两条消息的字节流为例:

0x00, 0x00, 0x00, 0x05, 'h', 'e', 'l', 'l', 'o', 0x00, 0x00, 0x00,
0x08, 'c', 'h', 'e', 'n', 's', 'h', 'u', 'o'

假设数据最终都全部到达,onMessage() 至少要能正确处理以下各种
数据到达的次序,每种情况下 messageCallback_ 都应该被调用两次:

1. 每次收到一个字节的数据,onMessage() 被调用 21 次;
2. 数据分两次到达,第一次收到 2 个字节,不足消息的长度字段;
3. 数据分两次到达,第一次收到 4 个字节,刚好够长度字段,但是没有 body;
4. 数据分两次到达,第一次收到 8 个字节,长度完整,但 body 不完整;
5. 数据分两次到达,第一次收到 9 个字节,长度完整,body 也完整;
6. 数据分两次到达,第一次收到 10 个字节,第一条消息的长度完整、body 也完整,第二条消息长度不完整;
7. 请自行移动分割点,验证各种情况;
8. 数据一次就全部到达,这时必须用 while 循环来读出两条消息,否则消息会堆积。

请读者验证 onMessage() 是否做到了以上几点。这个例子充分说明了
non-blocking read 必须和 input buffer 一起使用。

继续阅读全文: http://blog.csdn.net/Solstice/archive/2011/02/04/6172391.aspx

On Feb 3, 11:48 am, Shuo Chen <giantc...@gmail.com> wrote:
> Muduo 简介
>
> 我编写 Muduo 网络库的目的之一就是简化日常的 TCP 网络编程,让程序员能把精力集中在业务逻辑的实现上,而不要天天和 Sockets
> API 较劲。借用 Brooks 的话说,我希望 Muduo 能减少网络编程中的偶发复杂性 (accidental complexity)。
>
> Muduo 只支持 Linux 2.6.x 下的并发非阻塞 TCP 网络编程,它的安装方法见陈硕的 blog 文章。http://blog.csdn.net/Solstice/archive/2010/08/29/5848547.aspx
>
> Muduo 的使用非常简单,不需要从指定的类派生,也不用覆写虚函数,只需要注册几个回调函数去处理前面提到的三个半事件就行了。
>

> 以经典的 echo 回显服务为例:http://code.google.com/p/muduo/source/browse/#svn%2Ftrunk%2Fexamples%...
>

Shuo Chen

unread,
Feb 6, 2011, 8:27:27 PM2/6/11
to TopLanguage
Muduo 网络编程示例之三:定时器 收藏

陈硕 (giantchen_AT_gmail)

Blog.csdn.net/Solstice

这是《Muduo 网络编程示例》系列的第三篇文章。

Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category/779646.aspx

*** 程序中的时间 ***

程序中对时间的处理是个大问题,我打算单独写一篇文章来全面地讨论这个问题。文章暂定名《〈程序中的日期与时间〉第二章 计时与定时》,跟《〈程序中的
日期与时间〉第一章 日期计算》放到一个系列,这个系列预计会有四篇文章。

在这篇博客里里我先简要谈谈与编程直接相关的内容,把更深入的内容留给上面提到的日期与时间专题文章。

在一般的服务端程序设计中,与时间有关的常见任务有:

1. 获取当前时间,计算时间间隔;
2. 时区转换与日期计算;把纽约当地时间转换为上海当地时间;2011-02-05 之后第 100 天是几月几号星期几?等等
3. 定时操作,比如在预定的时间执行一项任务,或者在一段延时之后执行一项任务。

其中第 2 项看起来复杂,其实最简单。日期计算用 Julian Day Number,时区转换用 tz database;惟一麻烦一点的是夏令
时,但也可以用 tz database 解决。这些操作都是纯函数,很容易用一套单元测试来验证代码的正确性。需要特别注意的是,用 tzset/
localtime_r 来做时区转换在多线程环境下可能会有问题;对此我的解决办法是写一个 TimeZone class,以避免影响全局,将来在
日期与时间专题中会讲到。以下本文不考虑时区,均为 UTC 时间。

真正麻烦的是第 1 项和第 3 项。一方面,Linux 有一大把令人眼花缭乱的与时间相关的函数和结构体,在程序中该如何选用?另一方面,计算机中
的时钟不是理想的计时器,它可能会漂移或跳变;最后,民用的 UTC 时间与闰秒的关系也让定时任务变得复杂和微妙。当然,与系统当前时间有关的操作也
让单元测试变得困难。

** Linux 时间函数 **

Linux 的计时函数,用于获得当前时间:

* time(2) / time_t (秒)
* ftime(3) / struct timeb (毫秒)
* gettimeofday(2) / struct timeval (微秒)
* clock_gettime(2) / struct timespec (纳秒)
* gmtime / localtime / timegm / mktime / strftime / struct tm (这些与当
前时间无关)

定时函数,用于让程序等待一段时间或安排计划任务:

* sleep
* alarm
* usleep
* nanosleep
* clock_nanosleep
* getitimer / setitimer
* timer_create / timer_settime / timer_gettime / timer_delete
* timerfd_create / timerfd_gettime / timerfd_settime

我的取舍如下:

* (计时)只使用 gettimeofday 来获取当前时间。
* (定时)只使用 timerfd_* 系列函数来处理定时。

gettimeofday 入选原因:(这也是 muduo::Timestamp class 的主要设计考虑)

1. time 的精度太低,ftime 已被废弃,clock_gettime 精度最高,但是它系统调用的开销比
gettimeofday 大。
2. 在 x86-64 平台上,gettimeofday 不是系统调用,而是在用户态实现的(搜 vsyscall),没有上下文切换和陷入
内核的开销。
3. gettimeofday 的分辨率 (resolution) 是 1 微秒,足以满足日常计时的需要。
muduo::Timestamp 用一个 int64_t 来表示从 Epoch 到现在的微秒数,其范围可达上下 30 万年。

timerfd_* 入选的原因:

1. sleep / alarm / usleep 在实现时有可能用了信号 SIGALRM,在多线程程序中处理信号是个相当麻烦的事情,应
当尽量避免。(近期我会写一篇博客仔细讲讲"多线程、RAII、fork() 与信号")
2. nanosleep 和 clock_nanosleep 是线程安全的,但是在非阻塞网络编程中,绝对不能用让线程挂起的方式来等待一段
时间,程序会失去响应。正确的做法是注册一个时间回调函数。
3. getitimer 和 timer_create 也是用信号来 deliver 超时,在多线程程序中也会有麻烦。
timer_create 可以指定信号的接收方是进程还是线程,算是一个进步,不过在信号处理函数(signal handler)能做的事情实在很
受限。
4. timerfd_create 把时间变成了一个文件描述符,该"文件"在定时器超时的那一刻变得可读,这样就能很方便地融入到
select/poll 框架中,用统一的方式来处理 IO 事件和超时事件,这也正是 Reactor 模式的长处。我在一年前发表的《Linux
新增系统调用的启示》中也谈到这个想法,现在我把这个想法在 muduo 网络库中实现了。
5. 传统的 Reactor 利用 select/poll/epoll 的 timeout 来实现定时功能,但 poll 和
epoll 的定时精度只有毫秒,远低于 timerfd_settime 的定时精度。

必须要说明,在 Linux 这种非实时多任务操作系统中,在用户态实现完全精确可控的计时和定时是做不到的,因为当前任务可能会被随时切换出去,这
在 CPU 负载大的时候尤为明显。但是,我们的程序可以尽量提高时间精度,必要的时候通过控制 CPU 负载来提高时间操作的可靠性,在程序在
99.99% 的时候都是按预期执行的。这或许比换用实时操作系统并重新编写并测试代码要经济一些。

关于时间的精度(accuracy)问题我留到专题博客文章中讨论,它与分辨率(resolution)不完全是一回事儿。时间跳变和闰秒的影响与应对
也不在此处展开讨论了。

*** Muduo 的定时器接口 ***

Muduo EventLoop 有三个定时器函数:
* runAt 在指定的时间调用 TimerCallback
* runAfter 等一段时间调用 TimerCallback
* runEvery 以固定的间隔反复调用 TimerCallback
* cancel 取消 timer,目前未实现

回调函数在 EventLoop 对象所在的线程发生,与 onMessage() onConnection() 等网络事件函数在同一个线程。

Muduo 的 TimerQueue 采用了最简单的数据结构(链表)来管理定时器,它的效率比不上常见的 binary heap 的做法,如果程
序中大量(10 个以上)使用重复触发的定时器,或许值得考虑改用更高级的实现。我目前还没有在一个程序里用过这么多定时器,暂时也不打算优化
TimerQueue。

*** Boost.Asio Timer 示例 ***

Boost.Asio 教程里以 Timer 和 Daytime 为例介绍 asio 的基本使用,daytime 已经在前文"示例一"中介绍过,
这里着重谈谈 Timer。Asio 有 5 个 Timer 示例,muduo 把其中四个重新实现了一遍,并扩充了第 5 个示例。

1. 阻塞式的定时,muduo 不支持这种用法,无代码。
2. 非阻塞定时,见 examples/asio/tutorial/timer2
3. 在 TimerCallback 里传递参数,见 examples/asio/tutorial/timer3
4. 以成员函数为 TimerCallback,见 examples/asio/tutorial/timer4
5. 在多线程中回调,用 mutex 保护共享变量,见 examples/asio/tutorial/timer5
6. 在多线程中回调,缩小临界区,把不需要互斥执行的代码移出来,见 examples/asio/tutorial/timer6

继续阅读全文: http://blog.csdn.net/Solstice/archive/2011/02/06/6173563.aspx

Linker

unread,
Feb 8, 2011, 12:15:32 AM2/8/11
to pon...@googlegroups.com
说到分包,我想到flash的xmlsocket的分包就是用0x0来定界的。
简单用还是蛮分别的。

--
Sent from my mobile device

*Regards,
Linker Lin
linker...@gmail.com*

Yunfan Jiang

unread,
Feb 8, 2011, 4:06:04 AM2/8/11
to pon...@googlegroups.com
呵呵 那unicode截断怎么办

2011/2/8 Linker <linker...@gmail.com>:

--
welcom to gtalk me
http://hi.baidu.com/jyf1987

Linker

unread,
Feb 8, 2011, 4:34:31 AM2/8/11
to pon...@googlegroups.com
Utf-8 base64都可以解决

jinhu wang

unread,
Feb 8, 2011, 10:01:25 PM2/8/11
to pon...@googlegroups.com
tlv+变长buffer(string, vector<BYTE>)
Reply all
Reply to author
Forward
0 new messages