陈硕 (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.5-alpha.tar.gz

这些例子都比较简单,逻辑不复杂,代码也很短,适合摘取关键部分放到博客上。其中一些有一定的代表性与针对性,比如“如何传输完整的文件”估计是网络编程的初学者经常遇到的问题。请注意,muduo 是设计来开发内网的网络程序,它没有做任何安全方面的加强措施,如果用在公网上可能会受到攻击,在后面的例子中我会谈到这一点。

本系列文章适用于 Linux 2.6.x (x > 28),主要测试发行版为 Ubuntu 10.04 LTS 和 Debian 6.0 Squeeze,64-bit x86 硬件。

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 的代码中可以找到答案。

Muduo 简介

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

Muduo 只支持 Linux 2.6.x 下的并发非阻塞 TCP 网络编程,它的安装方法见陈硕的 blog 文章

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

以经典的 echo 回显服务为例:

1. 定义 EchoServer class,不需要派生自任何基类:

1 #ifndef MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H
2  #define MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H
3 #include <muduo/net/TcpServer.h>
4  // RFC 862
5  class EchoServer
6 {
7  public:
8 EchoServer(muduo::net::EventLoop* loop,
9 const muduo::net::InetAddress& listenAddr);
10 void start();
11  private:
12 void onConnection(const muduo::net::TcpConnectionPtr& conn);
13 void onMessage(const muduo::net::TcpConnectionPtr& conn,
14 muduo::net::Buffer* buf,
15 muduo::Timestamp time);
16 muduo::net::EventLoop* loop_;
17 muduo::net::TcpServer server_;
18 };
19  #endif // MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H

在构造函数里注册回调函数:

1 EchoServer::EchoServer(EventLoop* loop,
2 const InetAddress& listenAddr)
3 : loop_(loop),
4 server_(loop, listenAddr, "EchoServer")
5 {
6 server_.setConnectionCallback(
7 boost::bind(&EchoServer::onConnection, this, _1));
8 server_.setMessageCallback(
9 boost::bind(&EchoServer::onMessage, this, _1, _2, _3));
10 }
11
12  void EchoServer::start()
13 {
14 server_.start();
15 }

2. 实现 EchoServer::onConnection() 和 EchoServer::onMessage():

1 void EchoServer::onConnection(const TcpConnectionPtr& conn)
2 {
3 LOG_INFO << "EchoServer - " << conn->peerAddress().toHostPort() << " -> "
4 << conn->localAddress().toHostPort() << " is "
5 << (conn->connected() ? "UP" : "DOWN");
6 }
7
8 void EchoServer::onMessage(const TcpConnectionPtr& conn,
9 Buffer* buf,
10 Timestamp time)
11 {
12 string msg(buf->retrieveAsString());
13 LOG_INFO << conn->name() << " echo " << msg.size() << " bytes at " << time.toString();
14 conn->send(msg);
15 }

3. 在 main() 里用 EventLoop 让整个程序跑起来:

1 #include "echo.h"
2 #include <muduo/base/Logging.h>
3 #include <muduo/net/EventLoop.h>
4 using namespace muduo;
5  using namespace muduo::net;
6  int main()
7 {
8 LOG_INFO << "pid = " << getpid();
9 EventLoop loop;
10 InetAddress listenAddr(2007);
11 EchoServer server(&loop, listenAddr);
12 server.start();
13 loop.loop();
14 }

完整的代码见 muduo/examples/simple/echo。

这个几十行的小程序实现了一个并发的 echo 服务程序,可以同时处理多个连接。

对这个程序的详细分析见下一篇博客《Muduo 网络编程示例之一:五个简单 TCP 协议》

(待续)

http://www.cnblogs.com/Solstice/archive/2011/02/02/1948814.html

Muduo 网络编程示例之零:前言的更多相关文章

  1. Java后端进阶-网络编程(Netty零拷贝机制)

    package com.study.hc.net.netty.demo; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled ...

  2. go网络编程示例,客户端,服务器端

    http://blog.csdn.net/wangningyu/article/details/22859245 http://blog.csdn.net/wangningyu/article/det ...

  3. 浅谈TCP/IP网络编程中socket的行为

    我认为,想要熟练掌握Linux下的TCP/IP网络编程,至少有三个层面的知识需要熟悉: 1. TCP/IP协议(如连接的建立和终止.重传和确认.滑动窗口和拥塞控制等等) 2. Socket I/O系统 ...

  4. TCP/UDP网络编程的基础知识与基本示例(windows和Linux)

    一.TCP编程的一般步骤 服务器端: 1.创建一个socket,用函数socket() 2.绑定IP地址.端口等信息到socket上,用函数bind() 3.开启监听,用函数listen() 4.接收 ...

  5. JAVA的网络编程

    网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编 ...

  6. JAVA网络编程【转】出处不详

    网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编 ...

  7. 【转】JAVA之网络编程

    转自:火之光 网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者 ...

  8. JAVA的网络编程【转】

    JAVA的网络编程[转] Posted on 2009-12-03 18:04 火之光 阅读(93441) 评论(20) 编辑 收藏 网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能, ...

  9. Java学习之网络编程实例

    转自:http://www.cnblogs.com/springcsc/archive/2009/12/03/1616413.html 多谢分享 网络编程 网络编程对于很多的初学者来说,都是很向往的一 ...

随机推荐

  1. 嵌入式系统 Boot Loader

    基于嵌入式系统中的 OS 启动加载程序 ―― Boot Loader 的概念.软件设计的主要任务以及结构框架等内容.

  2. Android数据的四种存储方式SharedPreferences、SQLite、Content Provider和File (二) —— SQLite

    SQLite是一种转为嵌入式设备设计的轻型数据库,其只有五种数据类型,分别是: NULL: 空值 INTEGER: 整数 REAL: 浮点数 TEXT: 字符串 BLOB: 大数据 在SQLite中, ...

  3. iOS中UIWebView使用JS交互 - 机智的新手

    iOS中偶尔也会用到webview来显示一些内容,比如新闻,或者一段介绍.但是用的不多,现在来教大家怎么使用js跟webview进行交互. 这里就拿点击图片获取图片路径为例: 1.测试页面html & ...

  4. ExtJS003单击按钮弹出window

    html部分 <input type="button" id="btn" name="name" value="点击&quo ...

  5. 6.PHP 教程_PHP数据类型

    PHP数据类型 String(字符串),Integer(整型),Float(浮点型),Boolean(布尔型),Array(数组),Object(对象), NULL(空值). PHP字符串 一个字符串 ...

  6. tomcat远程debug端口开启

    declare -x CATALINA_OPTS="-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt ...

  7. Protel99SE制作拼板的方法

    制作步骤: 1.在PCB编辑里按快捷键 S/A全选复制源PCB全部内容,再按Ctrl+C看到十字光标.点击左键. 2.打开目标PCB文件,点击Edit菜单,在下拉菜单中点击Paste special( ...

  8. ThinkPHP 3.1.2 模板中的变量

    一.变量输出 (重点) 1.标量输出 2.数组输出 {$name[1]} {$name['k2']} {$name.k1} 3.对象输出 {$name:k} {$name->k} 二.系统变量 ...

  9. cocos2d-x中的导演、场景、层和精灵

    场景(Scenes) 场景在cocos2d-x中是CCScene类实现的,是应用程序流中独立的一部分.一个cocos2dx应用程序可以有许多场景,但是在某一时刻,只有一个场景在运行. 比如,你有一个游 ...

  10. 1043 - Triangle Partitioning(数学)

    1043 - Triangle Partitioning   PDF (English) Statistics Forum Time Limit: 0.5 second(s) Memory Limit ...