06 muduo 网络库简介

最后修改:

安装

muduo 地址

安装 cmake

1
sudo apt-get install cmake g++ make

安装依赖 boost

1
sudo apt-get install libboost-dev libboost-test-dev

三个非必要依赖库:curl、c-ares DNS、 Google Protobuf。

1
sudo apt-get install libcurl4-openssl-dev libc-ares-dev protobuf-compiler libprotobuf-dev

muduo 编译

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
git clone https://github.com/chenshuo/muduo
cd muduo

./build.sh -j2 #编译 muduo 库和自带的用例,生成可执行文件和静态库分别位于 ../build/debug/{bin,lib}

./build.sh install #将 muduo 头文件和库文件安装到 ../build/debug-install/{include,lib}

## 编译 release 包(以 -O2 优化)
BUILD_TYPE=release ./build.sh -j2 #编译 muduo 库和自带的用例,生成可执行文件和静态库分别位于 ../build/release/{bin,lib}

BUILD_TYPE=release ./build.sh install #将 muduo 头文件和库文件安装到 ../build/release-install/{include,lib},以便 muduo-protorpc 和 muduo-udns 等库使用

使用 muduo 库只需要设置好头文件路径(../build/release-install/include)和库文件路径(../build/release-install/lib)并链接相应的静态库文件(-lmuduo_net -lmuduo_base)。

如何 CMake 和 makefile 编译基于 muduo 的程序:https://github.com/chenshuo/muduo-tutorial

目录结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# tree -L 1 muduo
muduo
├── BUILD.bazel     # Bazel 构建工具的配置文件
├── CMakeLists.txt  # cmake 编译文件
├── ChangeLog       # 项目变更日志
├── ChangeLog2      # 项目变更日志
├── License         # 项目许可证文件
├── README          # 项目说明文档
├── WORKSPACE       # Bazel 构建系统的工作区配置文件,用于声明项目的外部依赖(如第三方库),与 BUILD.bazel 配合使用
├── build.sh        # 编译脚本
├── contrib         # 第三方贡献的扩展模块或工具
├── examples        # 示例代码
├── muduo           # muduo 主体
└── patches         # 补丁文件

5 directories, 8 files

基础库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# tree -L 1 muduo/muduo/base/
muduo/muduo/base/
├── AsyncLogging.cc
├── AsyncLogging.h          # 异步日志 backend
├── Atomic.h                # 原子操作
├── BUILD.bazel
├── BlockingQueue.h         # 无界阻塞队列(生产者消费者队列)
├── BoundedBlockingQueue.h  # 有界阻塞队列
├── CMakeLists.txt
├── Condition.cc
├── Condition.h             # 条件变量,与 Mutex 一起使用
├── CountDownLatch.cc
├── CountDownLatch.h        # “倒计时门闩” 同步
├── CurrentThread.cc
├── CurrentThread.h
├── Date.cc
├── Date.h                  # julian 日期库(公历)
├── Exception.cc
├── Exception.h             # 带 stack trace 的异常基类
├── FileUtil.cc
├── FileUtil.h
├── GzipFile.h
├── LogFile.cc
├── LogFile.h
├── LogStream.cc
├── LogStream.h
├── Logging.cc
├── Logging.h               # 简单日志
├── Mutex.h                 # 互斥锁
├── ProcessInfo.cc
├── ProcessInfo.h           # 进程信息
├── Singleton.h             # 线程安全的 Singleton
├── StringPiece.            # 字符串传递类型
├── Thread.cc
├── Thread.h                # 线程对象
├── ThreadLocal.h           # 线程局部数据
├── ThreadLocalSingleton.h  # 每个线程的 Singleton
├── ThreadPool.cc
├── ThreadPool.h            # 简单的固定大小线程池
├── TimeZone.cc
├── TimeZone.h              # 时区与夏令时
├── Timestamp.cc
├── Timestamp.h             # UTC 时间戳
├── Types.h                 # 基本类型声明
├── WeakCallback.h
├── copyable.h              # 空基类,用于标识值类型
├── noncopyable.h
└── tests                   # 测试用例

2 directories, 45 files

网络核心库

基于 Reactor 模式的网络库,核心是个事件循环 EventLoop,用于响应计时器,和 IO 事件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
tree -L 1 muduo/muduo/net
muduo/muduo/net
├── Acceptor.cc
├── Acceptor.h              # 接收器,用于服务端网络连接
├── BUILD.bazel
├── Buffer.cc
├── Buffer.h                # 缓冲区,非阻塞 IO 必备
├── CMakeLists.txt
├── Callbacks.h
├── Channel.cc
├── Channel.h               # 用于每个 socket 连接的事件分发
├── Connector.cc
├── Connector.h             # 连接器,用于客户端发起连接
├── Endian.h                # 网络字节序与本机字节序转换
├── EventLoop.cc
├── EventLoop.h             # 事件分发器
├── EventLoopThread.cc
├── EventLoopThread.h       # 专门用于 EventLoop 的线程
├── EventLoopThreadPool.cc
├── EventLoopThreadPool.h   # muduo 默认 多线程 IO 模型
├── InetAddress.cc
├── InetAddress.h           # Ip 地址的简单封装
├── Poller.cc
├── Poller.h                # io multiplexing的基类
├── Socket.cc
├── Socket.h                # 封装 socket 描述符,用于关闭连接
├── SocketsOps.cc
├── SocketsOps.h            # 封装底层 socket api
├── TcpClient.cc
├── TcpClient.h             # Tcp 客户端
├── TcpConnection.cc
├── TcpConnection.h
├── TcpServer.cc
├── TcpServer.h             # Tcp 服务端
├── Timer.cc
├── Timer.h
├── TimerId.h
├── TimerQueue.cc
├── TimerQueue.h
├── ZlibStream.h
├── boilerplate.cc
├── boilerplate.h
├── http                    # 网络附属库 http,需要 -lmuduo_http链接
├── inspect                 # 网络附属库 窥探进程内部信息,需要 -lmuduo_inspect链接
├── poller                  # Io multiplexing 实现
├── protobuf
├── protorpc
└── tests                   # 测试用例

7 directories, 40 files

muduo 头文件使用前置声明,减少头文件引入的依赖关系。

https://github.com/chenshuo/muduo-udns :基于 UDNS 的异步 DNS 解析

https://github.com/chenshuo/muduo-protorpc:基于 muduo 的 RPC 框架,自动管理对象生命周期。

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

  1. 连接建立。(客户端发起连接,服务端被动接受连接)
  2. 消息到达,文件描述符可读。(阻塞和非阻塞,如何处理分包,应用层的缓冲如何设计等)
  3. 连接断开。(主动断开和被动断开) 3.5. 消息发送完毕(数据写入操作系统的缓冲区,将TCP协议栈负责数据的发送和重传,不代表对方已经收到数据)。

echo 服务实现

muduo 使用不需要指定类派生,不用复写虚函数,只需要注册回调即可。(与 muduo 设计哲学一致)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H
#define MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H

#include "muduo/net/TcpServer.h"

// RFC 862
class EchoServer
{
 public:
  EchoServer(muduo::net::EventLoop* loop,
             const muduo::net::InetAddress& listenAddr);

  void start();  // calls server_.start();

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

  void onMessage(const muduo::net::TcpConnectionPtr& conn,
                 muduo::net::Buffer* buf,
                 muduo::Timestamp time);

  muduo::net::TcpServer server_;
};

#endif  // MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include "examples/simple/echo/echo.h"

#include "muduo/base/Logging.h"

using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;

// using namespace muduo;
// using namespace muduo::net;

EchoServer::EchoServer(muduo::net::EventLoop* loop,
                       const muduo::net::InetAddress& listenAddr)
  : server_(loop, listenAddr, "EchoServer")
{
  server_.setConnectionCallback(
      std::bind(&EchoServer::onConnection, this, _1));
  server_.setMessageCallback(
      std::bind(&EchoServer::onMessage, this, _1, _2, _3));
}

void EchoServer::start()
{
  server_.start();
}

void EchoServer::onConnection(const muduo::net::TcpConnectionPtr& conn)
{
  LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> "
           << conn->localAddress().toIpPort() << " is "
           << (conn->connected() ? "UP" : "DOWN");
}

void EchoServer::onMessage(const muduo::net::TcpConnectionPtr& conn,
                           muduo::net::Buffer* buf,
                           muduo::Timestamp time)
{
  muduo::string msg(buf->retrieveAllAsString());
  LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, "
           << "data received at " << time.toString();
  conn->send(msg);
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "examples/simple/echo/echo.h"

#include "muduo/base/Logging.h"
#include "muduo/net/EventLoop.h"

#include <unistd.h>

// using namespace muduo;
// using namespace muduo::net;

int main()
{
  LOG_INFO << "pid = " << getpid();
  muduo::net::EventLoop loop;
  muduo::net::InetAddress listenAddr(2007);
  EchoServer server(&loop, listenAddr);
  server.start();
  loop.loop();
}

//nc 127.0.0.1 2007
//telnet 127.0.0.1 2007

finger 服务

python twisted 是一个非常好的网络库,采用 reactor 作为网络库基本模型。

  1. 拒绝连接,空等。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include "muduo/net/EventLoop.h"

using namespace muduo;
using namespace muduo::net;

int main()
{
  EventLoop loop;
  loop.loop();
}
  1. 接受新连接,不处理数据。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"

using namespace muduo;
using namespace muduo::net;

int main()
{
  EventLoop loop;
  TcpServer server(&loop, InetAddress(1079), "Finger");
  server.start();
  loop.loop();
}

//nc 127.0.0.1 1079

//telnet 127.0.0.1 1079
  1. 主动断开连接。通过打印日志发现,使用 nc 客户端链接后,发送数据,服务的接收到第一次发送的数据后直接断开,不在接收数据。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"
#include "muduo/base/Logging.h"

using namespace muduo;
using namespace muduo::net;

void onConnection(const TcpConnectionPtr& conn)
{
  if (conn->connected())
  {
    LOG_INFO << "Finger - " << conn->peerAddress().toIpPort() << " -> "
           << conn->localAddress().toIpPort() << " is "
           << (conn->connected() ? "UP" : "DOWN");
    conn->shutdown();
  }
}

int main()
{
  EventLoop loop;
  TcpServer server(&loop, InetAddress(1079), "Finger");
  server.setConnectionCallback(onConnection);
  server.start();
  loop.loop();
}

//printf "msg\r\n" | nc 127.0.0.1 1079

//telnet 127.0.0.1 1079
  1. 读取用户名,然后断开连接。与 finger03 类似,调用回调不同,一个是连接,一个是数据。

读到 \r\n 结尾的消息就会断开连接。

安全问题:

  • 如果客户端不换行会把服务器内存撑爆。
  • Buffer::findCRLF()是线性查找的,如果客户端每次发送一个字节,服务的的时间复杂的为 O(n^2),消耗 CPU 资源。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"
#include "muduo/base/Logging.h"

using namespace muduo;
using namespace muduo::net;

void onMessage(const TcpConnectionPtr& conn,
               Buffer* buf,
               Timestamp receiveTime)
{
  if (buf->findCRLF())
  {
    LOG_INFO << "Finger - " << conn->peerAddress().toIpPort() << " -> "
           << conn->localAddress().toIpPort() << " is "
           << (conn->connected() ? "UP" : "DOWN");
    conn->shutdown();
  }
}

int main()
{
  EventLoop loop;
  TcpServer server(&loop, InetAddress(1079), "Finger");
  server.setMessageCallback(onMessage);
  server.start();
  loop.loop();
}

//printf "msg\r\n" | nc 127.0.0.1 1079

//telnet 127.0.0.1 1079
  1. 读取用户名,输出错我,然后断开连接。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"

using namespace muduo;
using namespace muduo::net;

void onMessage(const TcpConnectionPtr& conn,
               Buffer* buf,
               Timestamp receiveTime)
{
  if (buf->findCRLF())
  {
    conn->send("No such user\r\n");
    conn->shutdown();
  }
}

int main()
{
  EventLoop loop;
  TcpServer server(&loop, InetAddress(1079), "Finger");
  server.setMessageCallback(onMessage);
  server.start();
  loop.loop();
}

//printf "msg\r\n" | nc 127.0.0.1 1079

//telnet 127.0.0.1 1079
  1. 在空的 UserMap 中查找用户
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"

#include <map>

using namespace muduo;
using namespace muduo::net;

typedef std::map<string, string> UserMap;
UserMap users;

string getUser(const string& user)
{
  string result = "No such user";
  UserMap::iterator it = users.find(user);
  if (it != users.end())
  {
    result = it->second;
  }
  return result;
}

void onMessage(const TcpConnectionPtr& conn,
               Buffer* buf,
               Timestamp receiveTime)
{
  const char* crlf = buf->findCRLF();
  if (crlf)
  {
    string user(buf->peek(), crlf);
    conn->send(getUser(user) + "\r\n");
    buf->retrieveUntil(crlf + 2);
    conn->shutdown();
  }
}

int main()
{
  EventLoop loop;
  TcpServer server(&loop, InetAddress(1079), "Finger");
  server.setMessageCallback(onMessage);
  server.start();
  loop.loop();
}

//printf "msg\r\n" | nc 127.0.0.1 1079

//telnet 127.0.0.1 1079
  1. 往 UserMap 中添加用户
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"

#include <map>

using namespace muduo;
using namespace muduo::net;

typedef std::map<string, string> UserMap;
UserMap users;

string getUser(const string& user)
{
  string result = "No such user";
  UserMap::iterator it = users.find(user);
  if (it != users.end())
  {
    result = it->second;
  }
  return result;
}

void onMessage(const TcpConnectionPtr& conn,
               Buffer* buf,
               Timestamp receiveTime)
{
  const char* crlf = buf->findCRLF();
  if (crlf)
  {
    string user(buf->peek(), crlf);
    conn->send(getUser(user) + "\r\n");
    buf->retrieveUntil(crlf + 2);
    conn->shutdown();
  }
}

int main()
{
  users["schen"] = "Happy and well";
  EventLoop loop;
  TcpServer server(&loop, InetAddress(1079), "Finger");
  server.setMessageCallback(onMessage);
  server.start();
  loop.loop();
}

//printf "schen\r\n" | nc 127.0.0.1 1079

//telnet 127.0.0.1 1079

编译 c-ares 相关问题

https://github.com/cold-rivers-snow/muduo/commit/1a6f10f35a11d36111a041163290b310fb8fbe3f

ARES_VERSION 是 c-ares 库提供的一个预定义宏,在头文件<ares.h>中,它表示当前使用的 c-ares 库的版本号。这个宏的值是一个 24 位的十六进制数

0x010F04 表示 c-ares 1.15.4 版本 0x010500 表示 c-ares 1.5.0 版本

#if ARES_VERSION >= 0x010500 的含义

这行代码是 C/C++ 预处理器的条件编译指令,它的作用是:

“如果当前使用的 c-ares 库版本大于或等于 1.5.0,则编译下面的代码块;否则,跳过该代码块或编译替代代码。”

Reference

http://blog.csdn.net/Solstice/archive/2010/03/10/5364096.aspx

http://www.oschina.net/question/28_61182

http://aur.archlinux.org/packages.php?ID=49251

http://www.cs.nott.ac.uk/~cah/G51ISS/Document/NoSliverBullet.html

http://redmin.lighttpd.net/issues/show/2105

http://download.lighttpd.net/lighttpd/security/lighttpd_sa_2010_01.txt

http://zedshaw.com/essays/programmer_stats.html

http://www.percona.com/files/presentations/VELOCITY2012-Beyond-the-Numbers.pdf

http://gist.github.com/564985

http://think-async.com/Asio/Download

http://monkey.org/~provos/libevent-2.0.6-rc.tar.gz

http://asio.cvs.sourceforge.net/viewvc/asio/asio/src/tests/performance/

http://think-async.com/Asio/LinuxPerformanceImprovements

https://gist.github.com/chenshuo

山重水复疑无路,柳暗花明又一村
使用 Hugo 构建
主题 StackJimmy 设计