仿muduo库实现高并发服务器

这里是阿川的博客,祝您变得更强 ✨ 个人主页:在线OJ的阿川

💖文章专栏:项目实现
🌏代码仓库:项目源码


写在开头

现在您看到的是我的结论或想法但在这背后凝结了大量的思考、经验和讨论


目录


1.项目介绍

实现这个库,是有一天在课上老师推荐了一本书叫做《Linux多线程服务端编程》,然后下来自主地了解一下,发现muduo库很好的实现了C++中的网络编程,所以为了可以锻炼并提升自身的C++多线程编程和工程化的能力,参考github上的muduo库这个开源项目做了一些简化,自己实现了一个支持高并发的网络通信组件。

项目成果

  • 通过实现的高并发服务器组件,可以简洁快速的完成一个高性能的服务器搭建。
  • 通过组件内提供的不同应用层协议支持,也可以快速完成一个高性能应用服务器的搭建(当前项目中提供 HTTP 协议组件的支持)。

项目相关技术栈

这个项目作为一个基础的网络通信组件,是用纯 C++ 语言编写的,没有使用任何的第三方库。只是用到了 C++11 的线程库和多路复用的系统接口。

  • epoll:对比 select、poll 效率更高,但是只有 unix 平台支持。
  • std::thread:方便易用、可移植。

个人职责

  • 基于 Reactor 模式,使用 epoll 实现 IO 多路复用,封装 Poller 与 Channel 模块完成对大量文件描述符的事件监控与就绪事件分发

  • 采用 "one loop per thread" 的主从 Reactor 模型,主线程负责接收新连接,从线程池负责已建立连接的读写处理,新连接通过轮询分配到从线程

  • 封装 Buffer 缓冲区,解决 TCP 粘包/拆包问题,支持数据搬移与自动扩容

  • 使用 eventfd + 任务队列实现跨线程通信,通过 RunInLoop 将操作封装为任务投递到目标线程执行,保证连接操作的线程安全,避免加锁开销

  • 基于 timerfd + 时间轮算法,结合 shared_ptr / weak_ptr 管理定时任务生命周期,实现非活跃连接超时自动断开

  • 封装 Connection 时使用 RAII 思想与 enable_shared_from_this,通过智能指针管理连接生命周期,避免内存泄漏与重复释放

  • 在 TCP 框架之上扩展实现 HTTP/1.1 服务器,采用状态机解析请求(请求行→头部→正文),支持正则路由、静态资源服务、URL 编解码

  • 编写多客户端测试程序验证服务可靠性:通过连续发送不完整请求验证粘包处理,通过并发多客户端长连接验证线程安全与超时销毁机制

项目时间

关于时间,其实我没有制定详细的规划。是因为我也是抱着学习的心态来完成这个项目。在开发中可能会遇到一些问题,解决问题也是我学习的过程,因此时间就不能确定下来。大概花了 2 个月来完成,从项目目标的确定,到项目原型图的敲定,到各个模块的拆分,再到最后的开发测试部署。


2.模块设计

模块设计主要分为SERVER模块和协议模块两大块,而SERVER模块和协议模块又分为多个小模块,其中详细功能如下:

SERVER模块:

模块名称 作用
SERVER模块 实现Reactor模型的TCP服务器
Buffer模块 是一个缓冲区模块,用于实现通信中用户态的接收缓冲区和发送缓冲区功能
Socket 模块 Socket 模块是对套接字操作封装的一个模块,主要实现的 socket 的各项操作
Channel模块 Channel 模块是对一个描述符需要进行的 IO 事件管理的模块,实现对描述符可读,可写,错误... 事件的管理操作,以及 Poller 模块对描述符进行 IO 事件监控就绪后,根据不同的事件,回调不同的处理函数功能
Connection 模块 onnection 模块是对 Buffer 模块,Socket 模块,Channel 模块的一个整体封装,实现了对一个通信套接字的整体的管理,每一个进行数据通信的套接字(也就是 accept 获取到的新连接)都会使用 Connection 进行管理
Acceptor 模块 Acceptor 模块是对 Socket 模块,Channel 模块的一个整体封装,实现了对一个监听套接字的整体的管理
TimerQueue 模块 是实现固定时间定时任务的模块,可以理解就是要给定时任务管理器,向定时任务管理器中添加一个任务,任务将在固定时间后被执行,同时也可以通过刷新定时任务来延迟任务的执行。这个模块主要是对 Connection 对象的生命周期管理,对非活跃连接进行超时后的释放功能
Poller 模块 是对 epoll 进行封装的一个模块,主要实现 epoll 的 IO 事件添加,修改,移除,获取活跃连接功能
EventLoop 模块 EventLoop 模块可以理解就是我们上边所说的 Reactor 模块,它是对 Poller 模块,TimerQueue 模块,Socket 模块的一个整体封装,进行所有描述符的事件监控
TcpServer 模块 这个模块是一个整体 Tcp 服务器模块的封装,内部封装了 Acceptor 模块,EventLoop 模块作为主、EventLoopThreadPool 模块作为从

协议模块:

模块名称 作用
协议模块 对当前的 Reactor 模型服务器提供应用层协议支持
Util 模块 这个模块是 HTTP 请求数据模块,用于保存 HTTP 请求数据被解析后的各项请求元素信息
HttpResponse 模块 这个模块是 HTTP 响应数据模块,用于业务处理后设置并保存 HTTP 响应数据的的各项元素信息,最终会被按照 HTTP 协议响应格式组织成为响应信息发送给客户端
Connection 模块 onnection 模块是对 Buffer 模块,Socket 模块,Channel 模块的一个整体封装,实现了对一个通信套接字的整体的管理,每一个进行数据通信的套接字(也就是 accept 获取到的新连接)都会使用 Connection 进行管理
HttpContext 模块 这个模块是一个 HTTP 请求接收的上下文模块
HttpServer 模块 这个模块是最终给组件使用者提供的 HTTP 服务器模块了,用于以简单的接口实现 HTTP 服务器的搭建

项目分层

项目架构:

整个框架的核心架构是主从Reactor + one loop per thread ,具体如下:

项目主要流程

  1. 在实例化 TcpServer 对象过程中,完成 BaseLoop 的设置,Acceptor 对象的实例化,以及 EventLoop 线程池的实例化,以及std::shared_ptr的 hash 表的实例化。
  2. 为 Acceptor 对象设置回调函数:获取到新连接后,为新连接构建 Connection 对象,设置 Connection 的各项回调,并使用 shared_ptr 进行管理,并添加到 hash 表中进行管理,并为 Connection 选择一个 EventLoop 线程,为 Connection 添加一个定时销毁任务,为 Connection 添加事件监控。
  3. 启动 BaseLoop。
  4. 通过 Poller 模块对当前模块管理内的所有描述符进行 IO 事件监控,有描述符事件就绪后,通过描述符对应的 Channel 进行事件处理。
  5. 所有就绪的描述符 IO 事件处理完毕后,对任务队列中的所有操作顺序进行执行。
  6. 由于 epoll 的事件监控,有可能会因为没有事件到来而持续阻塞,导致任务队列中的任务不能及时得到执行,因此创建了 eventfd,添加到 Poller 的事件监控中,用于实现每次向任务队列添加任务的时候,通过向 eventfd 写入数据来唤醒 epoll 的阻塞。
  7. 实现向 Channel 提供可读,可写,错误等不同事件的 IO 事件回调函数,然后将 Channel 和对应的描述符添加到 Poller 事件监控中。
  8. 当描述符在 Poller 模块中就绪了 IO 可读事件,则调用描述符对应 Channel 中保存的读事件处理函数,进行数据读取,将 socket 接收缓冲区全部读取到 Connection 管理的用户态接收缓冲区中。然后调用由组件使用者传入的新数据到来回调函数进行处理。
  9. 组件使用者进行数据的业务处理完毕后,通过 Connection 向使用者提供的数据发送接口,将数据写入 Connection 的发送缓冲区中。
  10. 启动描述符在 Poll 模块中的 IO 写事件监控,就绪后,调用 Channel 中保存的写事件处理函数,将发送缓冲区中的数据通过 Socket 进行面向系统的实际数据发送。

3.核心模块详解

基础设施层模块:

Buffer模块 :解决 TCP 粘包问题

  • Buffer 是一个支持自动扩容的字节缓冲区,用来临时存放"从 socket 收到的数据"和"要发出去的数据"。
  • 支持自动扩容:
cpp 复制代码
void EnsureWriteSpace(uint64_t len) {
    // 情况1:尾部空闲空间就够了,直接返回
    if (TailIdleSize() >= len) { return; }
    
    // 情况2:尾部不够,但加上头部空闲空间够了
    // 把数据往前搬,腾出尾部空间
    if (len <= TailIdleSize() + HeadIdleSize()) {
        uint64_t rsz = ReadAbleSize();                    // 保存当前数据大小
        std::copy(ReadPosition(), ReadPosition() + rsz, Begin());  // 数据搬到开头
        _reader_idx = 0;                                   // 读指针归零
        _writer_idx = rsz;                                 // 写指针 = 数据大小
    } else {
        // 情况3:总体空间不够,只能扩容
        _buffer.resize(_writer_idx + len);
    }
}
  • HTTP 协议是按行解析的,Buffer 提供了 GetLine()
cpp 复制代码
char *FindCRLF() {
    return (char*)memchr(ReadPosition(), '\n', ReadAbleSize());  // 找换行符
}
std::string GetLine() {
    char *pos = FindCRLF();
    if (pos == NULL) return "";                                    // 没找到换行,说明数据不完整
    return ReadAsString(pos - ReadPosition() + 1);                 // +1 把换行符也读出来
}

Socket模块 :套接字的 RAII封装

  • Socket 类是对 Linux socket API 的 RAII 封装 ,把零散的 socket/bind/listen/accept/recv/send 收拢成一个对象。
  • 构造时获取 fd,析构时自动 close:
cpp 复制代码
class Socket {
    int _sockfd;
public:
    Socket(int fd): _sockfd(fd) {}
    ~Socket() { Close(); }   // 析构自动关闭,绝不泄漏
    void Close() {
        if (_sockfd != -1) {
            close(_sockfd);
            _sockfd = -1;
        }
    }
};
  • 关键API说明
cpp 复制代码
// 创建服务端套接字的完整流程
bool CreateServer(uint16_t port, ...) {
    Create();         // socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
    NonBlock();       // fcntl 设为非阻塞
    Bind(ip, port);   // bind 绑定地址
    Listen();         // listen 进入监听
    ReuseAddress();   // 设置地址重用
    return true;
}
  • 非阻塞 IO 与错误处理
cpp 复制代码
ssize_t Recv(void *buf, size_t len, int flag = 0) {
    ssize_t ret = recv(_sockfd, buf, len, flag);
    if (ret <= 0) {
        // EAGAIN:非阻塞模式下缓冲区没数据了(正常现象)
        // EINTR:被信号中断(重试即可)
        if (errno == EAGAIN || errno == EINTR) {
            return 0;   // 返回 0 表示"这次没读到",不是错误
        }
        return -1;      // 真正的错误
    }
    return ret;
}

Timerwheel模块 :时间轮定时器

  • TimerWheel 是一个基于 时间轮算法 的定时器,配合 timerfd,用来实现"非活跃连接超时自动断开"。
  • timerfd:
cpp 复制代码
static int CreateTimerfd() {
    int timerfd = timerfd_create(CLOCK_MONOTONIC, 0);    // 创建定时器 fd
    struct itimerspec itime;
    itime.it_value.tv_sec = 1;          // 首次 1 秒后触发
    itime.it_interval.tv_sec = 1;       // 之后每 1 秒触发一次
    timerfd_settime(timerfd, 0, &itime, NULL);
    return timerfd;
}
  • shared_ptr / weak_ptr 的精妙配合:
cpp 复制代码
class TimerWheel {
    std::vector<std::vector<PtrTask>> _wheel;                 // 轮子,存 shared_ptr
    std::unordered_map<uint64_t, WeakTask> _timers;           // 映射表,存 weak_ptr
};
  • 刷新定时器:
cpp 复制代码
void TimerRefreshInLoop(uint64_t id) {
    auto it = _timers.find(id);
    PtrTask pt = it->second.lock();        // weak_ptr.lock() 提升为 shared_ptr
    int delay = pt->DelayTime();
    int pos = (_tick + delay) % _capacity;
    _wheel[pos].push_back(pt);             // 重新放回轮子(延迟执行)
}
  • 析构即执行:
cpp 复制代码
class TimerTask {
    bool _canceled;
    TaskFunc _task_cb;
    ReleaseFunc _release;
public:
    ~TimerTask() {                          // 析构时自动执行!
        if (_canceled == false) _task_cb(); // 没被取消就执行任务
        _release();                          // 从映射表中移除自己
    }
    void Cancel() { _canceled = true; }    // 取消任务
};

核心层模块:

Channel模块与poller模块 :事件监控

  • Channel 把"一个 fd、它关心的事件、事件触发时该执行的回调"这三者打包到一起。
cpp 复制代码
class Channel {
    int _fd;                                    // 监控的 fd(但不拥有它)
    EventLoop *_loop;                           // 所属的 EventLoop
    uint32_t _events;                           // 当前需要监控的事件(如 EPOLLIN)
    uint32_t _revents;                          // 实际就绪的事件(epoll_wait 填充)
    EventCallback _read_callback;               // 可读回调
    EventCallback _write_callback;              // 可写回调
    EventCallback _error_callback;              // 错误回调
    EventCallback _close_callback;              // 连接关闭回调
    EventCallback _event_callback;              // 任意事件回调
};
  • 事件处理顺序:
cpp 复制代码
void HandleEvent() {
    // 1. 处理可读事件(EPOLLRDHUP 是对端关闭,EPOLLPRI 是带外数据)
    if ((_revents & EPOLLIN) || (_revents & EPOLLRDHUP) || (_revents & EPOLLPRI)) {
        if (_read_callback) _read_callback();
    }
    // 2. 处理可写事件
    if (_revents & EPOLLOUT) {
        if (_write_callback) _write_callback();
    } 
    // 3. 注意:下面用了 else if!错误和关闭只能处理一个
    else if (_revents & EPOLLERR) {
        if (_error_callback) _error_callback();   // 错误通常意味着要关闭连接
    } 
    else if (_revents & EPOLLHUP) {
        if (_close_callback) _close_callback();
    }
    // 4. 任意事件回调(比如刷新连接活跃度)
    if (_event_callback) _event_callback();
}
  • Poller 是对 epoll 三个核心 API(epoll_create / epoll_ctl / epoll_wait)的封装,并维护一个 map<fd, Channel*> 用于查找。
cpp 复制代码
class Poller {
    int _epfd;                                              // epoll 实例
    struct epoll_event _evs[MAX_EPOLLEVENTS];               // 存就绪事件数组
    std::unordered_map<int, Channel *> _channels;           // fd → Channel 映射
public:
    Poller() {
        _epfd = epoll_create(MAX_EPOLLEVENTS);              // 创建 epoll
    }
    void UpdateEvent(Channel *channel) {
        bool ret = HasChannel(channel);
        if (ret == false) {
            _channels[channel->Fd()] = channel;             // 不存在 → ADD
            Update(channel, EPOLL_CTL_ADD);
        } else {
            Update(channel, EPOLL_CTL_MOD);                 // 已存在 → MOD
        }
    }
    void Poll(std::vector<Channel*> *active) {
        int nfds = epoll_wait(_epfd, _evs, MAX_EPOLLEVENTS, -1);  // 阻塞等事件
        for (int i = 0; i < nfds; i++) {
            auto it = _channels.find(_evs[i].data.fd);
            it->second->SetREvents(_evs[i].events);               // 设置实际事件
            active->push_back(it->second);                        // 收集就绪的 Channel
        }
    }
};

EventLoop模块 :事件循环

  • EventLoop 是整个框架的核心调度器,每个线程拥有一个 EventLoop 实例。
  • EventLoop 的主循环有三步:
cpp 复制代码
void Start() {
    while(1) {
        // 第1步:事件监控 ------ 阻塞等 epoll 就绪事件
        std::vector<Channel *> actives;
        _poller.Poll(&actives);
        
        // 第2步:事件处理 ------ 处理所有就绪的事件
        for (auto &channel : actives) {
            channel->HandleEvent();
        }
        
        // 第3步:执行任务 ------ 处理其他线程投递过来的任务
        RunAllTask();
    }
}
  • 跨线程操作的唯一入口
cpp 复制代码
void RunInLoop(const Functor &cb) {
    if (IsInLoop()) {
        cb();              // 如果是本线程,立即执行
    } else {
        QueueInLoop(cb);   // 如果是其他线程,投递到任务队列
    }
}
  • 跨线程唤醒机制
cpp 复制代码
void QueueInLoop(const Functor &cb) {
    {
        std::unique_lock<std::mutex> _lock(_mutex);
        _tasks.push_back(cb);                  // 任务塞进池子
    }
    WeakUpEventFd();                           // 唤醒 EventLoop
}
void WeakUpEventFd() {
    uint64_t val = 1;
    write(_event_fd, &val, sizeof(val));       // 写入1,触发可读事件
}
  • 任务池的 swap 技巧:最小化临界区
cpp 复制代码
void RunAllTask() {
    std::vector<Functor> functor;
    {
        std::unique_lock<std::mutex> _lock(_mutex);
        _tasks.swap(functor);                  // 交换,瞬间完成
    }
    for (auto &f : functor) {
        f();                                    // 不持锁地执行
    }
}

连接层模块:

Concection模块 :连接的完整生命周期

  • Connection 是一个连接的"大管家",封装了 socket、Channel、输入输出缓冲、各种回调,管理从连接建立到关闭的全过程。
  • Connection 继承自 enable_shared_from_this<Connection>
cpp 复制代码
class Connection : public std::enable_shared_from_this<Connection> {
  • Send 的异步设计
cpp 复制代码
void Send(const char *data, size_t len) {
    Buffer buf;
    buf.WriteAndPush(data, len);                       // 先把数据拷贝到临时 Buffer
    _loop->RunInLoop(std::bind(&SendInLoop, this, std::move(buf)));
}

服务层模块:

Acceptor模块:监听套接字

  • Acceptor 封装了监听套接字,当有新连接到来时,调用回调把新 fd 交给上层。
cpp 复制代码
class Acceptor {
    Socket _socket;           // 监听套接字
    EventLoop *_loop;
    Channel _channel;         // 用于事件监控
    AcceptCallback _accept_callback;
private:
    void HandleRead() {
        int newfd = _socket.Accept();              // 获取新连接
        if (newfd < 0) return;
        if (_accept_callback) _accept_callback(newfd);  // 交给 TcpServer 处理
    }
public:
    Acceptor(EventLoop *loop, int port) {
        // 构造时就创建了监听套接字(CreateServer 内部完成 socket/bind/listen)
        _socket.CreateServer(port);
        _channel.SetReadCallback(std::bind(&Acceptor::HandleRead, this));
    }
    void Listen() { _channel.EnableRead(); }       // 启动读监控
};
  • 从线程池:
cpp 复制代码
class LoopThreadPool {
    int _thread_count;                           // 线程数量
    int _next_idx;                               // 下一个分配的索引
    EventLoop *_baseloop;                        // 主线程的 loop(回退用)
    std::vector<LoopThread*> _threads;
    std::vector<EventLoop *> _loops;             // 每个线程的 loop
public:
    EventLoop *NextLoop() {
        if (_thread_count == 0) return _baseloop;     // 没有从线程,回退到主线程
        _next_idx = (_next_idx + 1) % _thread_count;   // 轮询
        return _loops[_next_idx];
    }
};
  • 一个线程 + 一个 EventLoop:
cpp 复制代码
class LoopThread {
    std::mutex _mutex;
    std::condition_variable _cond;
    EventLoop *_loop;
    std::thread _thread;
private:
    void ThreadEntry() {
        EventLoop loop;                          // 线程内实例化 EventLoop
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _loop = &loop;
            _cond.notify_all();                  // 通知 GetLoop 不再阻塞
        }
        loop.Start();                            // 开始事件循环
    }
public:
    LoopThread():_loop(NULL), _thread(std::thread(&LoopThread::ThreadEntry, this)) {}
    EventLoop *GetLoop() {
        std::unique_lock<std::mutex> lock(_mutex);
        _cond.wait(lock, [&](){ return _loop != NULL; });  // 等 loop 创建好
        return _loop;
    }
};

应用层模块:

EchoServer模块

  • Echo Server 是对 TcpServer 的最简封装,收到数据后原样返回
cpp 复制代码
class EchoServer {
    TcpServer _server;
    void OnMessage(const PtrConnection &conn, Buffer *buf) {
        conn->Send(buf->ReadPosition(), buf->ReadAbleSize());
        buf->MoveReadOffset(buf->ReadAbleSize());
        conn->Shutdown();
    }
public:
    EchoServer(int port) : _server(port) {
        _server.SetThreadCount(2);           // 2 个从线程
        _server.EnableInactiveRelease(10);   // 10 秒非活跃超时
        // 设置回调...
    }
};

HttpServer模块

  • HTTP/1.1 请求解析(GET / POST / PUT / DELETE / HEAD)
  • 状态机解析(请求行 → 头部 → 正文)
  • 正则路由(URL 模式匹配 + 处理函数)
  • 静态资源服务(根据扩展名返回 MIME 类型)
  • URL 编解码(处理查询字符串中的特殊字符)
  • 长短连接支持(Connection: keep-alive / close)
  • HttpRequest / HttpResponse 数据结构
cpp 复制代码
class HttpRequest {
    std::string _method;                          // GET / POST ...
    std::string _path;                            // /index.html
    std::string _version;                         // HTTP/1.1
    std::string _body;                            // 请求正文
    std::unordered_map<std::string, std::string> _headers;  // 头部字段
    std::unordered_map<std::string, std::string> _params;   // 查询字符串
};

class HttpResponse {
    int _statu = 200;                             // 状态码
    std::string _body;                            // 响应正文
    std::unordered_map<std::string, std::string> _headers;
    // 还支持重定向
    bool _redirect_flag;
    std::string _redirect_url;
};
  • HttpContext:状态机解析
cpp 复制代码
typedef enum {
    RECV_HTTP_LINE,      // 正在接收请求行
    RECV_HTTP_HEAD,      // 正在接收头部
    RECV_HTTP_BODY,      // 正在接收正文
    RECV_HTTP_OVER,      // 接收完成
    RECV_HTTP_ERROR      // 解析出错
} HttpRecvStatu;

class HttpContext {
    HttpRecvStatu _recv_statu;                    // 当前状态
    HttpRequest _request;
public:
    void RecvHttpRequest(Buffer *buf) {
        // 注意:这里没有 break!处理完一行立即继续处理头部
        switch(_recv_statu) {
            case RECV_HTTP_LINE: RecvHttpLine(buf);   // 故意不 break
            case RECV_HTTP_HEAD: RecvHttpHead(buf);   // 故意不 break
            case RECV_HTTP_BODY: RecvHttpBody(buf);
        }
    }
};
  • 请求行解析用正则
cpp 复制代码
bool ParseHttpLine(const std::string &line) {
    // GET /login?user=xm&pass=123 HTTP/1.1
    std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\?(.*))? (HTTP/1\.[01])");
    std::regex_match(line, matches, e);
    _request._method = matches[1];                // GET
    _request._path = matches[2];                  // /login
    // matches[3] 是查询字符串 user=xm&pass=123
    return true;
}
  • 路由设计:正则 + Handler 表
cpp 复制代码
class HttpServer {
    using Handler = std::function<void(const HttpRequest &, HttpResponse *)>;
    using Handlers = std::vector<std::pair<std::regex, Handler>>;
    Handlers _get_route;        // GET 请求的路由表
    Handlers _post_route;       // POST 请求的路由表
    // ...
};

// 用户注册路由
server.Get("/hello", Hello);                    // 注册 GET /hello 的处理函数
server.Post("/login", Login);

// 路由匹配
void Dispatcher(HttpRequest &req, HttpResponse *rsp, Handlers &handlers) {
    for (auto &handler : handlers) {
        if (std::regex_match(req._path, req._matches, handler.first)) {
            handler.second(req, rsp);            // 匹配成功,调用处理函数
            return;
        }
    }
    rsp->_statu = 404;                           // 都没匹配上
}
  • 静态资源服务与安全防护
cpp 复制代码
static std::string ExtMime(const std::string &filename) {
    size_t pos = filename.find_last_of('.');
    std::string ext = filename.substr(pos);      // .html / .jpg / .css
    return _mime_msg[ext];                        // text/html / image/jpeg / text/css
}
  • 路径穿越防护
cpp 复制代码
static bool ValidPath(const std::string &path) {
    // 按 / 分割路径,遇到 .. 深度减1,深度 < 0 说明试图跳出根目录
    std::vector<std::string> subdir;
    Split(path, "/", &subdir);
    int level = 0;
    for (auto &dir : subdir) {
        if (dir == "..") {
            level--;
            if (level < 0) return false;         // 跳出根目录,拒绝
        } else {
            level++;
        }
    }
    return true;
}

4.项目测试

测试思路:

编写一个简单的 html 欢迎页面,然后使用浏览器进行访问

  • 长连接连续请求测试:一个连接中每隔 3s 向服务器发送一个请求,查看是否会收到响应。
  • 时连接释放测试:创建一个客户端,连接上服务器后,不进行消息发送,等待看超时后,连接是否会自动释放(当前默认设置超时时间为 10s)。预期结果:10s 后连接被释放。
  • 数据中多条请求处理测试:给服务器发送的一条数据中包含有多个 HTTP 请求,观察服务器的处理。预期结果:每一条请求都有其对应的响应
  • PUT 大文件上传测试:使用 put 请求上传一个大文件进行保存,大文件数据的接收会被分在多次请求中接收,然后计算源文件和上传后保存的文件的 MD5 值,判断请求的接收处理是否存在问题。(这里主要观察的是上下文的处理过程是否正常。)

5.困难挑战和心得体会

困难挑战:

  1. 跨线程操作的线程安全
    最初不理解为什么要用任务队列而不是直接加锁,后来明白了 muduo 的核心思想:避免共享优于加锁。把操作投递到目标线程执行,既安全又高效。

2.epoll 事件处理的顺序
HandleEvent 里错误和关闭事件为什么用 else-if,想了很久才理解是为了避免 double-free(一次事件循环只能关闭一次连接)。

心得体会:

  1. 学到了以项目为主导的开发应该如何完成,比如从项目确定到功能拆解,再到开发测试部署,从而独立完成项目的设计、实现、测试、部署

  2. 学到了当项目中遇到问题,迫使我思考和寻找解决方案,这增强了我的问题解决能力。

  3. 学到了如何进行有效的测试,以确保项目不出现 bug。例如功能测试,安全性测试等。


  1. 夹带私货

若你能看到看到这篇文章且能看到这 ,则说明你我有缘留个关注吧 ,后面还会接着计算机408、底层原理、开源项目、以及数据、后端研发相关、实习、笔试/面试、秋招/春招、各种竞赛相关、简历相关、考研、学术相关......,祝你我变得更强

好的,到此为止啦,祝您变得更强

道阻且长 行则将至
个人主页:在线OJ的阿川大佬的支持和鼓励,将是我成长路上最大的动力
相关推荐
j7~1 小时前
【Linux 系统】二十.进程间通信(共享内存、消息队列、信号量)
linux·运维·消息队列·共享内存·信号量·system v
坚定学代码1 小时前
C++ SOLID 原则(上):单一职责、开闭原则与里氏替换
c++
YMWM_1 小时前
python-venv虚拟环境
linux·开发语言·python
电商API_180079052471 小时前
电商ERP 自动同步订单功能实现拆解与技术手段
服务器·前端·网络·爬虫
万俟淋曦1 小时前
【ROS2】动作通信机制 Action 代码示例(C++版)
linux·人工智能·机器人
Web极客码1 小时前
如何在WordPress中通知用户他们的评论已获批准?
服务器·wordpress·评论
ysa0510301 小时前
【板子】欧拉序
c++·笔记·算法·深度优先·图论
不负岁月无痕2 小时前
STL -- C++ 红黑树封装 Map 和 Set
java·c语言·开发语言·c++·面试
山峰哥2 小时前
‌Explain实战:打开数据库执行计划的黑盒‌
大数据·服务器·数据库·sql·深度优先·宽度优先