1 muduo简介

1.1 编译安装

  1. git clone https://github.com/chenshuo/muduo.git
  2. cd muduo
  3. sudo apt install libboost-dev # 安装boost库
  4. # 修改build.sh中的INSTALL_DIR,默认是在~/build目录下,我们可以改为/usr,直接安装到系统目录
  5. sudo ./build.sh install

之后就可以看到/usr/include/muduo中相关的头文件,/usr/lib中有相应的静态链接文件。
使用如下命令可以进行正常编译:

  1. # 以编译muduo的echo例子为例,指定静态库和include路径
  2. g++ main.cc echo.cc -lmuduo_net -lmuduo_base -lpthread -I/home/barret/muduo -o echo

1.2 目录结构

  • base:基础库,多是多线程相关的类
  • net和net/poller:基于reactor模式的网络库,核心是EventLoop,用于处理IO事件和定时器
  • net附属模块
    • http:简易的http服务器
    • inspect:基于http的窥探器,用于报告进程状态
    • protorpc:模仿protobuf RPC的实现
  • example:示例代码

    2 使用示例

    可以查看附带的Finger了解如何一步步实现一个简单的echo server:代码地址:examples/twisted/finger

    2.1 simple目录示例

    examples/simple目录下实现了几个简单的TCP网络服务程序,功能列举如下:

  • discard:丢弃所有收到的数据

  • daytime:服务端accept连接之后,以字符串形式发送当前时间,然后主动断开连接
  • time:服务端accept连接之后,以二进制形式发送当前时间(从Epoch到现在的秒数),然后主动断开连接;我们需要一个客户程序来把收到的时间转换为字符串
  • echo:回显服务,把收到的数据发回客户端。
  • chargen:服务端accept连接之后,不停地发送测试数据。

2.2 文件传输示例

examples/filetransfer/
三种实现方式:

  1. 一次读取全文件内容,一次发送
  2. 每次读取64K内容,多次发送
  3. 使用智能指针管理文件描述符,自动释放

    3 最大并发连接数问题与解决

    当文件描述符用尽时,会存在这样的问题:
    本进程的文件描述符已经达到上限,无法为新连接创建socket文件描述符。但是,既然没有socket文件描述符来表示这个连接,我们就无法close(2)它。程序继续运行,再一次调用epoll_wait。这时候epoll_wait会立刻返回,因为新连接还等待处理,listening fd还是可读的。这样程序立刻就陷入了busy loop,CPU占用率接近100%。这既影响同一event loop上的连接,也影响同一机器上的其他服务。

所以设置一个最大并发连接数的限制是必须的。有两个基本的方案:

  1. 准备一个空闲的文件描述符。遇到这种情况,先关闭这个空闲文件,获得一个文件描述符的名额;再accept(2)拿到新socket连接的描述符;随后立刻close(2)它,这样就优雅地断开了客户端连接;最后重新打开一个空闲文件,把“坑”占住,以备再次出现这种情况时使用。muduo的acceptor类内部实现了这种方法这种方法在多线程下有竞争问题,多个线程可能同时需要空闲文件描述符
  2. 可以自己设一个稍低一点的soft limit,如果超过soft limit就主动关闭新连接,这样就可避免触及“文件描述符耗尽”这种边界条件。比方说当前进程的max file descriptor是1024,那么我们可以在连接数达到1000的时候进入“拒绝新连接”状态,这样就可留给我们足够的腾挪空间。

在muduo中可以很简单的在App层实现方案2(尽管库里已经实现方法1),为Server类添加一个最大限制的成员变量,当新连接到来时,判断是否超过最大限制:

  1. class XxxServer
  2. {
  3. //...
  4. atomic_int numConnected_; //当前活动连接数,原子类型
  5. const int kMaxConnections_;//最大连接限制
  6. };
  7. void XxxServer::onConnection(const TcpConnectionPtr &conn)
  8. {
  9. if (conn->connected())
  10. {
  11. ++numConnected_;
  12. if (numConnected_ > kMaxConnections_) //判断是否超过最大的并发连接数限制,
  13. { //超过,立刻关闭新连接
  14. conn->shutdown();
  15. conn->forceCloseWithDelay(3.0); // > round trip of the whole Internet.
  16. }
  17. }
  18. else
  19. {
  20. --numConnected_;
  21. }
  22. }