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
    目录

    day14-重构核心库、使用智能指针

    # day13-重构核心库、使用智能指针

    • 在断开连接时,会发生内存泄漏。

    之前的操作已经对完成了一个分离业务逻辑的简单网络库,但是整个项目越来越复杂,模块越来越多。为了弥补之前的设计、细节缺陷。应该对程序进行重构和梳理,以方便自己进一步的去处理和实现功能。

    本次重构主要包含以下几个方面:

    • 更换了部分函数名。减少无用的函数,使代码看起来更简洁,可读。
    • 进行内存管理。在之前的操作所有的内存都是由裸指针进行处理的,在类的构造阶段分配内存,析构释放内存,为了更加方便的管理内存,对于类自己拥有的资源使用了智能指针std::unique_ptr<>进行管理,对于不属于自己的资源,但是借用的资源,使用裸指针进行处理。
    • 避免资源的复制操作 ,尽量使用移动语义进行所有权转移,以提升程序性能。

    # 关于common.h

    对于大部分的类,我们都不希望实现其拷贝构造函数,移动构造函数和赋值运算符,简单的操作可以在每一个类中使用=delete来保证其不被编译器自动实现。但是如果每个类都这么写,显然不够清晰且冗余,因此采用了宏来实现。

    // common.h
    #define DISALLOW_COPY(cname)     \
      cname(const cname &) = delete; \
      cname &operator=(const cname &) = delete;
    
    #define DISALLOW_MOVE(cname) \
      cname(cname &&) = delete;  \
      cname &operator=(cname &&) = delete;
    
    #define DISALLOW_COPY_AND_MOVE(cname) \
      DISALLOW_COPY(cname);               \
      DISALLOW_MOVE(cname);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # 关于Socket类

    Socket类主要是对socket操作进行了封装,并主要应用在Acceptor类中和Connection类中,但是非常明显的可以发现,在Connection中,Socket成员变量并没有发挥太大的作用,只是单纯的获取Socket中的文件描述符,因此,对于Connection类其成员变量在一定程度上是冗余的。因此完全可以删掉Socket类,并将相应的操作直接封装在Acceptor中。

    # 关于Channel类

    Channel类是网络库的核心组建之一,他对socket进行了更深度的封装,保存了我们需要对socket监听的事件,当前socket已经准备好的事件并进行处理。此外,为了更新和获取在epoller中的状态,需要使用EventLoop进行管理,由于只是使用EventLoop,因此采用裸指针进行内存管理。

    class Channel {
        public:
            DISALLOW_COPY_AND_MOVE(Channel);
            Channel(int fd, EventLoop * loop);
            
            ~Channel();
    
            void HandleEvent() const; // 处理事件
            void EnableRead();  // 允许读
            void EnableWrite(); // 允许写
            void EnableET(); // 以ET形式触发
            void DisableWrite();
    
            int fd() const;  // 获取fd
            short listen_events() const; // 监听的事件
            short ready_events() const; // 准备好的事件
    
            bool IsInEpoll() const; // 判断当前channel是否在poller中
            void SetInEpoll(bool in = true); // 设置当前状态为poller中
            
    
            void SetReadyEvents(int ev);
            void set_read_callback(std::function<void()> const &callback);// 设置回调函数
            void set_write_callback(std::function<void()> const &callback);
    
        private:
            int fd_;
            EventLoop *loop_;
            
            short listen_events_;
            short ready_events_;
            bool in_epoll_{false};
            std::function<void()> read_callback_;
            std::function<void()> write_callback_;
    
    };
    
    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

    # 针对Epoller类

    主要是进行IO多路复用,保证高并发。在Epoller类主要是对epoll中channel的监听与处理。

    class Epoller
    {
    public:
        DISALLOW_COPY_AND_MOVE(Epoller);
    
        Epoller();
        ~Epoller();
    
        // 更新监听的channel
        void UpdateChannel(Channel *ch) const;
        // 删除监听的通道
        void DeleteChannel(Channel *ch) const;
    
        // 返回调用完epoll_wait的通道事件
        std::vector<Channel *> Poll(long timeout = -1) const;
    
        private:
            int fd_;
            struct epoll_event *events_;
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    # 针对EventLoop类

    该类是对事件的轮询和处理。由于每一个EventLoop主要是不断地调用epoll_wait来获取激活的事件,并处理。这也就意味着Epoll是独属于EventLoop的成员变量,随着EventLoop的析构而析构,因此可以采用智能指针

    class EventLoop
    {
    public:
        DISALLOW_COPY_AND_MOVE(EventLoop);
        EventLoop();
        ~EventLoop();
    
        void Loop() const;
        void UpdateChannel(Channel *ch) const;
        void DeleteChannel(Channel *ch) const;
    
    private:
        std::unique_ptr<Epoller> poller_;
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    # 针对Acceptor类

    Acceptor主要用于服务器接收连接,并在接受连接之后进行相应的处理。这个类需要独属于自己的Channel,因此采用了智能指针管理。并且将socket相应的操作也直接封装在了Accptor中,并且为了方便自定义ip地址和port端口,不直接将ip和port绑死,而是通过传参的方式。

    // Acceptor.h
    class Acceptor{
        public:
            DISALLOW_COPY_AND_MOVE(Acceptor);
            Acceptor(EventLoop *loop, const char * ip, const int port);
            ~Acceptor();
    
            void set_newconnection_callback(std::function<void(int)> const &callback);
            
            // 创建socket
            void Create();
    
            // 与ip地址绑定
            void Bind(const char *ip, const int port);
            
            // 监听Socket
            void Listen();
    
            // 接收连接
            void AcceptConnection();
    
        private:
            EventLoop *loop_;
            int listenfd_;
            std::unique_ptr<Channel> channel_;
            std::function<void(int)> new_connection_callback_;
    };
    
    // Acceptor.cpp
    Acceptor::Acceptor(EventLoop *loop, const char * ip, const int port) :loop_(loop), listenfd_(-1){
        Create();
        Bind(ip, port);
        Listen();
        channel_ = std::make_unique<Channel>(listenfd_, loop);
        std::function<void()> cb = std::bind(&Acceptor::AcceptConnection, this);
        channel_->set_read_callback(cb);
        channel_->EnableRead();
    }
    
    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

    # 针对TcpConnection类

    对于每个TCP连接,都可以使用一个类进行管理,在这个类中,将注意力转移到对客户端socket的读写上,除此之外,他还需要绑定几个回调函数,例如当接收到信息时,或者需要关闭时进行的操作。 并且增加了一个conn_id的成员变量,主要是fd可能被复用,在debug时,可以更清晰的追寻问题。

    class TcpConnection
    {
    public:
        enum ConnectionState
        {
            Invalid = 1,
            Connected,
            Disconected
        };
    
        DISALLOW_COPY_AND_MOVE(TcpConnection);
    
        TcpConnection(EventLoop *loop, int connfd, int connid);
        ~TcpConnection();
    
         // 关闭时的回调函数
        void set_close_callback(std::function<void(int)> const &fn);   
        // 接受到信息的回调函数                                  
        void set_message_callback(std::function<void(TcpConnection *)> const &fn); 
    
    
        // 设定send buf
        void set_send_buf(const char *str); 
        Buffer *read_buf();
        Buffer *send_buf();
    
        void Read(); // 读操作
        void Write(); // 写操作
        void Send(const std::string &msg); // 输出信息
        void Send(const char *msg, int len); // 输出信息
        void Send(const char *msg);
    
    
        void HandleMessage(); // 当接收到信息时,进行回调
    
        // 当TcpConnection发起关闭请求时,进行回调,释放相应的socket.
        void HandleClose(); 
    
    
        ConnectionState state() const;
        EventLoop *loop() const;
        int fd() const;
        int id() const;
    
    private:
        // 该连接绑定的Socket
        int connfd_;
        int connid_;
        // 连接状态
        ConnectionState state_;
    
        EventLoop *loop_;
    
        std::unique_ptr<Channel> channel_;
        std::unique_ptr<Buffer> read_buf_;
        std::unique_ptr<Buffer> send_buf_;
    
        std::function<void(int)> on_close_;
        std::function<void(TcpConnection *)> on_message_;
    
        void ReadNonBlocking();
        void WriteNonBlocking();
    };
    
    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
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63

    # 关于TcpServer类

    TcpServer是对整个服务器的管理,他通过创建acceptor来接收连接。并管理TcpConnection的添加。

    在这个类中,由于TcpConnection的生命周期模糊,暂时使用了裸指针,后续将会改造成智能指针。

    // TcpServer.h
    class TcpServer
    {
        public:
        DISALLOW_COPY_AND_MOVE(TcpServer);
        TcpServer(const char *ip, const int port);
        ~TcpServer();
    
        void Start();
    
        void set_connection_callback(std::function < void(TcpConnection *)> const &fn);
        void set_message_callback(std::function < void(TcpConnection *)> const &fn);
    
        void HandleClose(int fd);
        void HandleNewConnection(int fd);
    
        private:
            std::unique_ptr<EventLoop> main_reactor_;
            int next_conn_id_;
            std::unique_ptr<Acceptor> acceptor_;
            std::vector<std::unique_ptr<EventLoop>> sub_reactors_;
    	    std::unordered_map<int, TcpConnection *> connectionsMap_;
            std::unique_ptr<ThreadPool> thread_pool_;
            std::function<void(TcpConnection *)> on_connect_;
            std::function<void(TcpConnection *)> on_message_;
    };
    
    // TcpServer.cpp
    void TcpServer::HandleClose(int fd){
        auto it =  connectionsMap_.find(fd);
        assert(it != connectionsMap_.end());
        TcpConnection * conn = connectionsMap_[fd];
        connectionsMap_.erase(fd);
        // 如果析构会导致内存泄漏
        // delete conn;
        // 但是没有析构就不会close,服务端停留在`close_wait`状态,客户端停留在`fin_wait`状态。所以在这里暂时进行了close以先关闭连接
        // 可以尝试着两种操作所带来的问题
        ::close(fd);
        conn = nullptr;
    }
    
    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
    编辑 (opens new window)
    上次更新: 2025/05/21, 06:42:57
    day13-支持业务逻辑自定义、完善Connection类
    day15-重构Connection、修改生命周期

    ← day13-支持业务逻辑自定义、完善Connection类 day15-重构Connection、修改生命周期→

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