Consolexin's blog Consolexin's blog
首页
  • 算法基础

    • 图论
    • 字符串
    • 动态规划
    • 二分
    • 滑动窗口
    • 排序
  • Project

    • CppServer
  • 相关书籍

    • 现代C++编程
  • 书籍

    • SQL必知必会
    • MySQL必知必会
分类
标签
归档
GitHub (opens new window)

Consolexinhun

小学生
首页
  • 算法基础

    • 图论
    • 字符串
    • 动态规划
    • 二分
    • 滑动窗口
    • 排序
  • Project

    • CppServer
  • 相关书籍

    • 现代C++编程
  • 书籍

    • SQL必知必会
    • MySQL必知必会
分类
标签
归档
GitHub (opens new window)
  • README
  • day01-从一个最简单的socket开始
  • day02-不要放过任何一个错误
  • day03-高并发还得用epoll
  • day04-来看看我们的第一个类
  • day05-epoll高级用法-Channel登场
  • day06-服务器与事件驱动核心类登场
  • day07-为我们的服务器添加一个Acceptor
  • day08-一切皆是类,连TCP连接也不例外
  • day09-缓冲区-大作用
  • day10-加入线程池到服务器
  • day11-完善线程池,加入一个简单的测试程序
  • day12-将服务器改写为主从Reactor多线程模式
  • day13-支持业务逻辑自定义、完善Connection类
  • day14-重构核心库、使用智能指针
  • day15-重构Connection、修改生命周期
  • day16-使用CMake工程化
  • day17-使用EventLoopThreadPool、移交EventLoop
  • day18-HTTP有限状态转换机
  • day19-创建HTTP响应,实现HTTP服务器
  • day20-定时器的创建使用
  • day21-服务器主动关闭连接
  • day22-初步涉及日志库,定义自己的输出流LogStream
  • day23-定义前端日志库,实现同步输出
  • day24-异步日志库
  • day25-更有效的缓冲区
  • day26-监听写事件
  • day27-处理静态文件,实现POST请求
  • day28-文件服务器的简单实现,文件的展示和下载
  • day29-文件的上传
  • day30-WebBench的测试
  • CppServer
consolexinhun
2025-04-20

day07-为我们的服务器添加一个Acceptor

# day07-为我们的服务器添加一个Acceptor

在上一天,我们分离了服务器类和事件驱动类,将服务器逐渐开发成Reactor模式。至此,所有服务器逻辑(目前只有接受新连接和echo客户端发来的数据)都写在Server类里。但很显然,Server作为一个服务器类,应该更抽象、更通用,我们应该对服务器进行进一步的模块化。

仔细分析可发现,对于每一个事件,不管提供什么样的服务,首先需要做的事都是调用accept()函数接受这个TCP连接,然后将socket文件描述符添加到epoll。当这个IO口有事件发生的时候,再对此TCP连接提供相应的服务。

在这里务必先理解TCP的面向连接这一特性,在谢希仁《计算机网络》里有详细的讨论。

因此我们可以分离接受连接这一模块,添加一个Acceptor类,这个类有以下几个特点:

  • 类存在于事件驱动EventLoop类中,也就是Reactor模式的main-Reactor
  • 类中的socket fd就是服务器监听的socket fd,每一个Acceptor对应一个socket fd
  • 这个类也通过一个独有的Channel负责分发到epoll,该Channel的事件处理函数handleEvent()会调用Acceptor中的接受连接函数来新建一个TCP连接

根据分析,Acceptor类定义如下:

class Acceptor{
private:
    EventLoop *loop;
    Socket *sock;
    InetAddress *addr;
    Channel *acceptChannel;
public:
    Acceptor(EventLoop *_loop);
    ~Acceptor();
    void acceptConnection();
};
1
2
3
4
5
6
7
8
9
10
11

这样一来,新建连接的逻辑就在Acceptor类中。但逻辑上新socket建立后就和之前监听的服务器socket没有任何关系了,TCP连接和Acceptor一样,拥有以上提到的三个特点,这两个类之间应该是平行关系。所以新的TCP连接应该由Server类来创建并管理生命周期,而不是Acceptor。并且将这一部分代码放在Server类里也并没有打破服务器的通用性,因为对于所有的服务,都要使用Acceptor来建立连接。

为了实现这一设计,我们可以用两种方式:

  1. 使用传统的虚类、虚函数来设计一个接口
  2. C++11的特性:std::function、std::bind、右值引用、std::move等实现函数回调

虚函数使用起来比较繁琐,程序的可读性也不够清晰明朗,而std::function、std::bind等新标准的出现可以完全替代虚函数,所以本教程采用第二种方式。

关于虚函数,在《C++ Primer》第十五章第三节有详细讨论,而C++11后的新标准可以参考欧长坤《现代 C++ 教程》

首先我们需要在Acceptor中定义一个新建连接的回调函数:

std::function<void(Socket*)> newConnectionCallback;
1

在新建连接时,只需要调用这个回调函数:

void Acceptor::acceptConnection(){
    newConnectionCallback(sock);
}
1
2
3

而这个回调函数本身的实现在Server类中:

void Server::newConnection(Socket *serv_sock){
    // 接受serv_sock上的客户端连接
}
1
2
3

在今天的代码中,Acceptor的Channel使用了ET模式,事实上使用LT模式更合适,将在之后修复

新建Acceptor时通过std::bind进行绑定:

acceptor = new Acceptor(loop);
std::function<void(Socket*)> cb = std::bind(&Server::newConnection, this, std::placeholders::_1);
acceptor->setNewConnectionCallback(cb);
1
2
3

这样一来,尽管我们抽象分离出了Acceptor,新建连接的工作任然由Server类来完成。

请确保清楚地知道为什么要这么做再进行之后的学习。

至此,今天的教程已经结束了。在今天,我们设计了服务器接受新连接的Acceptor类。测试方法和之前一样,使用make得到服务器和客户端程序并运行。虽然服务器功能已经好几天没有变化了,但每一天我们都在不断抽象、不断完善,从结构化、流程化的程序设计,到面向对象程序设计,再到面向设计模式的程序设计,逐渐学习服务器开发的思想与精髓。

完整源代码:https://github.com/yuesong-feng/30dayMakeCppServer/tree/main/code/day07 (opens new window)

编辑 (opens new window)
上次更新: 2025/05/21, 06:42:57
day06-服务器与事件驱动核心类登场
day08-一切皆是类,连TCP连接也不例外

← day06-服务器与事件驱动核心类登场 day08-一切皆是类,连TCP连接也不例外→

最近更新
01
6-其他操作
05-20
02
4-联结
05-20
03
7-管理
05-20
更多文章>
Theme by Vdoing | Copyright © 2019-2025 Consolexinhun | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×