天天看点

Muduo 网络编程示例之零:前言TCP 网络编程本质论Muduo 简介

陈硕 (giantchen_AT_gmail)

Blog.csdn.net/Solstice

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

用于测试两台机器的往返延迟的 roundtrip

用于测试两台机器的带宽的 pingpong

文件传输

一个基于 TCP 的应用层广播 hub

socks4a 代理服务器,包括简单的 TCP 中继(relay)。

一个 Sudoku 服务器的演变,从单线程到多线程,从阻塞到 event-based。

一个提供短址服务的 httpd 服务器

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

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

连接的建立,包括服务端接受 (accept) 新连接和客户端成功发起 (connect) 连接。

连接的断开,包括主动断开 (close 或 shutdown) 和被动断开 (read 返回 0)。

消息到达,文件描述符可读。这是最为重要的一个事件,对它的处理方式决定了网络编程的风格(阻塞还是非阻塞,如何处理分包,应用层的缓冲如何设计等等)。

消息发送完毕,这算半个。对于低流量的服务,可以不必关心这个事件;另外,这里“发送完毕”是指将数据写入操作系统的缓冲区,将由 TCP 协议栈负责数据的发送与重传,不代表对方已经收到数据。

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

如果要主动关闭连接,如何保证对方已经收到全部数据?如果应用层有缓冲(这在非阻塞网络编程中是必须的,见下文),那么如何保证先发送完缓冲区中的数据,然后再断开连接。直接调用 close(2) 恐怕是不行的。

如果主动发起连接,但是对方主动拒绝,如何定期 (带 back-off) 重试?

非阻塞网络编程该用边沿触发(edge trigger)还是电平触发(level trigger)?(这两个中文术语有其他译法,我选择了一个电子工程师熟悉的说法。)如果是电平触发,那么什么时候关注 EPOLLOUT 事件?会不会造成 busy-loop?如果是边沿触发,如何防止漏读造成的饥饿?epoll 一定比 poll 快吗?

在非阻塞网络编程中,如何设计并使用缓冲区?一方面我们希望减少系统调用,一次读的数据越多越划算,那么似乎应该准备一个大的缓冲区。另一方面,我们系统减少内存占用。如果有 10k 个连接,每个连接一建立就分配 64k 的读缓冲的话,将占用 640M 内存,而大多数时候这些缓冲区的使用率很低。muduo 用 readv 结合栈上空间巧妙地解决了这个问题。

如果使用发送缓冲区,万一接收方处理缓慢,数据会不会一直堆积在发送方,造成内存暴涨?如何做应用层的流量控制?

如何设计并实现定时器?并使之与网络 IO 共用一个线程,以避免锁。

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

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

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

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

<a></a>

1 #ifndef MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H

2  #define MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H

3 #include &lt;muduo/net/TcpServer.h&gt;

4  // RFC 862

5  class EchoServer

6 {

7  public:

8 EchoServer(muduo::net::EventLoop* loop,

9 const muduo::net::InetAddress&amp; listenAddr);

10 void start();

11  private:

12 void onConnection(const muduo::net::TcpConnectionPtr&amp; conn);

13 void onMessage(const muduo::net::TcpConnectionPtr&amp; 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&amp; listenAddr)

3 : loop_(loop),

4 server_(loop, listenAddr, "EchoServer")

5 {

6 server_.setConnectionCallback(

7 boost::bind(&amp;EchoServer::onConnection, this, _1));

8 server_.setMessageCallback(

9 boost::bind(&amp;EchoServer::onMessage, this, _1, _2, _3));

10 }

11

12  void EchoServer::start()

13 {

14 server_.start();

15 }

1 void EchoServer::onConnection(const TcpConnectionPtr&amp; conn)

2 {

3 LOG_INFO &lt;&lt; "EchoServer - " &lt;&lt; conn-&gt;peerAddress().toHostPort() &lt;&lt; " -&gt; "

4 &lt;&lt; conn-&gt;localAddress().toHostPort() &lt;&lt; " is "

5 &lt;&lt; (conn-&gt;connected() ? "UP" : "DOWN");

6 }

7

8 void EchoServer::onMessage(const TcpConnectionPtr&amp; conn,

9 Buffer* buf,

10 Timestamp time)

11 {

12 string msg(buf-&gt;retrieveAsString());

13 LOG_INFO &lt;&lt; conn-&gt;name() &lt;&lt; " echo " &lt;&lt; msg.size() &lt;&lt; " bytes at " &lt;&lt; time.toString();

14 conn-&gt;send(msg);

1 #include "echo.h"

2 #include &lt;muduo/base/Logging.h&gt;

3 #include &lt;muduo/net/EventLoop.h&gt;

4 using namespace muduo;

5  using namespace muduo::net;

6  int main()

7 {

8 LOG_INFO &lt;&lt; "pid = " &lt;&lt; getpid();

9 EventLoop loop;

10 InetAddress listenAddr(2007);

11 EchoServer server(&amp;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,如需转载请自行联系原作者

继续阅读