linux网络编程之单reactor模型(二)

单 Reactor 单线程模型的主要缺点,简洁列出:

  1. 事件处理易阻塞:所有事件处理(包括业务逻辑)都在一个线程中,任一操作阻塞会拖慢整个事件循环。

  2. 无法利用多核 CPU:只有一个线程工作,不能发挥多核并行处理能力。

  3. 写操作可能阻塞:对端缓冲区满时写操作受限,若处理不当会阻塞主线程。

  4. 业务逻辑与 I/O 耦合:缺乏异步处理机制,复杂逻辑会拖慢事件分发。

  5. 抗并发能力差:连接数一多或负载升高,性能迅速下降,无法支撑高并发场景。

七、单reactor多线程模型

1、核心思想

        为了解决单 Reactor 单线程在高并发场景下的性能瓶颈和阻塞风险,我们需要引入多线程机制,将 I/O 事件的检测与业务处理解耦。通过将耗时任务分发到后台线程池,可以显著提升系统吞吐量和响应能力,充分利用多核 CPU,实现高性能、可扩展的网络服务架构。

单 Reactor 多线程模型的优势:

  • 主线程运行 Reactor(事件分发器):负责监听所有客户端连接的读写/新连接等 I/O 事件。

  • 工作线程池处理任务:当 Reactor 检测到某连接可读后,将任务(如业务逻辑处理、数据解析等)分发给后台线程处理,从而避免主线程阻塞。

  • 高性能 + 高并发:Reactor 线程不做阻塞操作,确保事件响应低延迟;业务逻辑处理并行,提高系统吞吐能力。

2、核心组件

1)Channel:事件通道

Channel 是事件驱动框架中的桥梁组件,连接了底层 epoll(或 poll/select)与上层的回调逻辑。它不拥有 fd,但负责:

  • 保存关注的事件(如读、写)

  • 设置事件的处理回调(如 read_cb_, write_cb_

  • 接收来自 epoll_wait 的触发事件 revents_

  • 调用对应的回调处理函数

常用函数说明:

  • EnableReading(), EnableWriting():控制监听哪些事件;

  • HandleEvent():事件触发时由 EventLoop 调用,执行回调;

  • Update():将事件变化通知所属 EventLoop,更新 epoll。

 .h

class EventLoop;

/* 负责在事件分发系统中起到“事件通道”的作用,连接底层的I/O多路复用机制(如epoll、select、poll)和具体的事件处理逻辑 */

class Channel {
public:
    using Callback = std::function<void()>;

    Channel(EventLoop& loop, int fd);
    ~Channel();

    void SetReadCallback(Callback cb);
    void SetWriteCallback(Callback cb);

    void EnableReading();
    void EnableWriting();
    void DisableWriting();
    void DisableAll();

    void HandleEvent();

    int Fd() const;
    uint32_t Events() const;
    void SetRevents(uint32_t revents);

    bool IsRegistered() const;
    void SetRegistered(bool reg);

private:
    void Update();

    int fd_;
    uint32_t events_;   // 关注的事件
    uint32_t revents_;  // 实际触发的事件
    bool registered_;

    EventLoop& loop_;
    Callback read_cb_;
    Callback write_cb_;
};

.cpp

Channel::Channel(EventLoop& loop, int fd)
    : fd_(fd), events_(0), revents_(0), registered_(false), loop_(loop) {}

Channel::~Channel() {
    if (registered_) {
        loop_.RemoveChannel(this);
    }
}

void Channel::SetReadCallback(Callback cb) {
    read_cb_ = std::move(cb);
}

void Channel::SetWriteCallback(Callback cb) {
    write_cb_ = std::move(cb);
}

void Channel::EnableReading() {
    events_ |= EPOLLIN;
    Update();
}

void Channel::EnableWriting() {
    events_ |= EPOLLOUT;
    Update();
}

void Channel::DisableWriting() {
    events_ &= ~EPOLLOUT;
    Update();
}

void Channel::DisableAll() {
    events_ = 0;
    Update();
}

void Channel::HandleEvent() {
    std::cout << "[Channel] fd=" << fd_ << " HandleEvent(), revents=" << revents_ << "\n";
    if ((revents_ & EPOLLIN) && read_cb_) {
        std::cout << "[Channel] fd=" << fd_ << " calling read_cb_\n";
        read_cb_();
    }
    if ((revents_ & EPOLLOUT) && write_cb_) {
        std::cout << "[Channel] fd=" << fd_ << " calling write_cb_\n";
        write_cb_();
    }
    // if ((revents_ & EPOLLIN) && read_cb_) read_cb_();
    // if ((revents_ & EPOLLOUT) && write_cb_) write_cb_();
}

int Channel::Fd() const {
    return fd_;
}

uint32_t Channel::Events() const {
    return events_;
}

void Channel::SetRevents(uint32_t revents) {
    revents_ = revents;
}

bool Channel::IsRegistered() const {
    return registered_;
}

void Channel::SetRegistered(bool reg) {
    registered_ = reg;
}

void Channel::Update() {
    loop_.UpdateChannel(this);
}

2)EventLoop:事件循环器

EventLoop 是框架的核心执行单元,负责:

  • 执行 epoll_wait 等待事件;

  • 分发事件给对应的 Channel::HandleEvent()

  • 提供线程安全的任务投递(RunInLoop, QueueInLoop);

  • 管理跨线程唤醒机制(基于 eventfd);

  • 控制退出和生命周期。

关键机制:每个线程只能拥有一个 EventLoop 实例,保证其线程安全。

  • Loop():是主循环,不断执行 epoll_wait + 事件分发;

  • pending_functors_:任务队列,允许其他线程投递任务到当前 loop;

  • Wakeup():主线程调用该函数可以唤醒 epoll_wait 阻塞,执行新任务。

 .h

#define MAX_EVENTS 1024

/* 负责事件的等待、分发和调度执行*/
class EventLoop {
public:
    EventLoop();
    ~EventLoop();

    void Loop();               // 主循环
    void Quit();               // 退出循环

    void RunInLoop(std::function<void()> cb);  // 若在当前线程直接执行,否则加入队列
    void QueueInLoop(std::function<void()> cb); // 加入队列(异步执行)

    void Wakeup();             // 通过 eventfd 唤醒 epoll_wait
    void UpdateChannel(Channel* ch);
    void RemoveChannel(Channel* ch);

    bool IsInLoopThread() const;

private:
    void HandleWakeup();       // eventfd 读事件处理器
    void DoPendingFunctors();  // 执行任务队列中的回调

private:
    int epoll_fd_;
    int wakeup_fd_;            // eventfd
    std::unique_ptr<Channel> wakeup_channel_;

    std::atomic<bool> looping_;
    std::atomic<bool> quit_;
    const pid_t thread_id_;

    std::mutex mutex_;
    std::vector<std::function<void()>> pending_functors_; // 任务队列
};

.cpp

namespace {
int CreateEventFd() {
    int fd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    if (fd < 0) {
        std::cerr << "[EventLoop] Failed to create eventfd: " << strerror(errno) << "\n";
        abort();
    }
    return fd;
}
}

EventLoop::EventLoop()
    : epoll_fd_(::epoll_create1(EPOLL_CLOEXEC)),
      wakeup_fd_(CreateEventFd()),
      wakeup_channel_(std::make_unique<Channel>(*this, wakeup_fd_)),
      looping_(false),
      quit_(false),
      thread_id_(::gettid()) {

    assert(epoll_fd_ >= 0);

    wakeup_channel_->SetReadCallback([this]() { HandleWakeup(); });
    wakeup_channel_->EnableReading();
}

EventLoop::~EventLoop() {
    ::close(epoll_fd_);
    ::close(wakeup_fd_);
}

void EventLoop::Loop() {
    assert(!looping_);
    looping_ = true;
    quit_ = false;

    struct epoll_event events[MAX_EVENTS];

    while (!quit_) {
        int n = ::epoll_wait(epoll_fd_, events, MAX_EVENTS, TimerInstance()->WaitTime());
        if (n < 0 && errno != EINTR) {
            std::cerr << "[EventLoop] epoll_wait error: " << strerror(errno) << "\n";
            break;
        }

        for (int i = 0; i < n; ++i) {
            auto* ch = static_cast<Channel*>(events[i].data.ptr);
            ch->SetRevents(events[i].events);  // 必须设置 revents
            ch->HandleEvent();
        }

        DoPendingFunctors();
        TimerInstance()->HandleTimeout();
    }
    looping_ = false;
}

void EventLoop::Quit() {
    quit_ = true;
    if (!IsInLoopThread()) {
        Wakeup();
    }
}

void EventLoop::RunInLoop(std::function<void()> cb) {
    if (IsInLoopThread()) {
        cb();
    } else {
        QueueInLoop(std::move(cb));
    }
}

void EventLoop::QueueInLoop(std::function<void()> cb) {
    {
        std::lock_guard<std::mutex> lock(mutex_);
        pending_functors_.push_back(std::move(cb));
    }
    Wakeup();
}

void EventLoop::Wakeup() {
    uint64_t one = 1;
    ::write(wakeup_fd_, &one, sizeof(one));
}

void EventLoop::HandleWakeup() {
    uint64_t one;
    ::read(wakeup_fd_, &one, sizeof(one));
}

void EventLoop::DoPendingFunctors() {
    std::vector<std::function<void()>> functors;
    {
        std::lock_guard<std::mutex> lock(mutex_);
        functors.swap(pending_functors_);
    }
    for (const auto& func : functors) {
        func();
    }
}

void EventLoop::UpdateChannel(Channel* ch) {
    struct epoll_event ev{};
    ev.data.ptr = ch;
    ev.events = ch->Events();
    int fd = ch->Fd();

    if (!ch->IsRegistered()) {
        int ret = ::epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev);
        std::cout << "[EventLoop] Register fd=" << fd << " to epoll, ret=" << ret << ", errno=" << errno << "\n";
        ch->SetRegistered(true);
    } else {
        ::epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &ev);
    }
}

void EventLoop::RemoveChannel(Channel* ch) {
    int fd = ch->Fd();
    if (ch->IsRegistered()) {
        ::epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr);
        ch->SetRegistered(false);
    }
}

bool EventLoop::IsInLoopThread() const {
    return thread_id_ == ::gettid();
}

3)TcpServer:服务器管理器

TcpServer 是用户使用的主类,核心职责:

  • 启动监听 socket,创建 Acceptor;

  • 创建线程池 EventLoopThreadPool,并启动各子线程;

  • 每当有新连接,调用 AcceptConnection() 进行 accept()

  • 将新连接的 fd 分发给某个子线程中的 EventLoop

  • 创建对应的 TcpConn 实例管理连接。

通过组合主 loop 和多个子 loop 实现事件分发与连接隔离。

.h

// 多线程 Reactor 架构下的 Echo Server 示例(使用 TcpConn + EventLoop + 线程池)
class TcpServer {
public:
    TcpServer(EventLoop* loop, const std::string& ip, uint16_t port, int thread_num);
    void Start();

private:
    void AcceptConnection();
    void NewConnection(int fd, EventLoop* loop);

private:
    EventLoop* base_loop_;
    EventLoopThreadPool thread_pool_;
    std::unique_ptr<Channel> accept_channel_;
    int listen_fd_;
    std::string ip_;
    uint16_t port_;
    std::map<int, std::shared_ptr<TcpConn>> conns_;
};

.cpp

TcpServer::TcpServer(EventLoop* loop, const std::string& ip, uint16_t port, int thread_num)
    : base_loop_(loop), thread_pool_(loop, thread_num), ip_(ip), port_(port) {}

void TcpServer::Start() {
    thread_pool_.Start();

    // 创建监听 socket
    listen_fd_ = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    int opt = 1;
    setsockopt(listen_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port_);
    inet_pton(AF_INET, ip_.c_str(), &addr.sin_addr);

    ::bind(listen_fd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
    ::listen(listen_fd_, SOMAXCONN);

    accept_channel_ = std::make_unique<Channel>(*base_loop_, listen_fd_);
    accept_channel_->SetReadCallback([this]() { AcceptConnection(); });
    accept_channel_->EnableReading();

    std::cout << "[TcpServer] Listening on " << ip_ << ":" << port_ << "\n";
}

void TcpServer::AcceptConnection() {
    while (true) {
        sockaddr_in cli_addr;
        socklen_t len = sizeof(cli_addr);
        int conn_fd = ::accept4(listen_fd_, reinterpret_cast<sockaddr*>(&cli_addr), &len, SOCK_NONBLOCK);
        if (conn_fd < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                break;
            } else {
                std::cerr << "[TcpServer] accept4 failed: " << strerror(errno) << "\n";
                break;
            }
        }
        EventLoop* sub_loop = thread_pool_.GetNextLoop();
        std::cout << "[TcpServer] Dispatching fd=" << conn_fd << " to sub loop\n";
        sub_loop->RunInLoop([this, conn_fd, sub_loop]() {
            std::cout << "[TcpServer] RunInLoop executing NewConnection\n";
            NewConnection(conn_fd, sub_loop);
        });

    }
}

void TcpServer::NewConnection(int fd, EventLoop* loop) {
    auto conn = std::make_shared<TcpConn>(fd, *loop);

    conn->SetReadCallback([conn]() {
        std::string msg = conn->GetAllData();
        if (!msg.empty()) {
            std::cout << "received: " << msg << std::endl;
        }
        TimerInstance()->AddTimeout(1000,[&]() {
            std::string msg = "wugongzi";
            conn->Send(msg.data(), msg.size());
        });
    });

    conn->SetCloseCallback([this, fd]() {
        conns_.erase(fd);
        std::cout << "[TcpServer] Closed fd=" << fd << "\n";
    });

    conns_[fd] = conn;
    std::cout << "[TcpServer] New connection fd=" << fd << "\n";
}

4)TcpConn:客户端连接对象

TcpConn 封装了一个客户端连接的全部通信逻辑,包括:

  • 与客户端的读写操作;

  • 管理输入输出缓冲区;

  • 设置和执行回调函数(如读、关闭);

  • 持有一个 Channel 对象,注册进 EventLoop

其设计中使用了 enable_shared_from_this,保证在回调中获取安全的 shared_ptr,防止生命周期问题。

关键函数说明:

  • HandleRead():处理读事件,填充 ReadBuffer,触发上层逻辑;

  • HandleWrite():处理写事件,从 WriteBuffer 中取数据发送;

  • Send():提供线程安全的异步写入接口;

.h

/*
 *声明 TcpConn 类,同时继承 enable_shared_from_this,方便在回调中安全获取 shared_ptr<TcpConn> 自己的智能指针。
 */
class TcpConn : public std::enable_shared_from_this<TcpConn> {
public:
    // 声明回调函数
    using ReadCallback = std::function<void()>;
    using CloseCallback = std::function<void()>;

    TcpConn(int fd, EventLoop& loop);
    ~TcpConn();

    // 设置回调函数
    void SetReadCallback(ReadCallback cb);
    void SetCloseCallback(CloseCallback cb);

    // 异步发送数据
    int Send(const char* data, size_t size);
    // 获取当前接收缓冲区内的全部数据
    std::string GetAllData();

private:
    // 内部事件处理函数:读取、写入、关闭连接
    void HandleRead();
    void HandleWrite();
    void Close();

private:
    int fd_;
    bool closed_;
    EventLoop& loop_;
    ReadBuffer input_buffer_;       // 读缓冲区
    WriteBuffer output_buffer_;        // 写缓冲区
    std::shared_ptr<Channel> channel_; // 封装 fd 的 epoll 管理类
    ReadCallback read_cb_;             // 外部注入的回调函数
    CloseCallback close_cb_;
};

.cpp

TcpConn::TcpConn(int fd, EventLoop& loop)
    : fd_(fd), closed_(false), loop_(loop) {
    // 创建 Channel 并设置读写回调,注册 EPOLLIN 监听可读事件。
    channel_ = std::make_shared<Channel>(loop_, fd);
    channel_->SetReadCallback([this]() { HandleRead(); });
    channel_->SetWriteCallback([this]() { HandleWrite(); });
    channel_->EnableReading();
}

TcpConn::~TcpConn() {
    Close();
}

void TcpConn::SetReadCallback(ReadCallback cb) {
    read_cb_ = std::move(cb);
}

void TcpConn::SetCloseCallback(CloseCallback cb) {
    close_cb_ = std::move(cb);
}

int TcpConn::Send(const char* data, size_t size) {
    // 若连接已关闭或无数据,直接返回。
    if (closed_ || data == nullptr || size == 0) return -1;

    output_buffer_.Append(reinterpret_cast<const uint8_t *>(data), size);
    channel_->EnableWriting();

    return 1;
}

std::string TcpConn::GetAllData() {
    auto data = input_buffer_.GetAllData();
    if (data.first != nullptr) {
        // 获取读缓冲区全部有效数据
        std::string result(reinterpret_cast<char*>(data.first), data.second);
        // 标记已读的数据
        input_buffer_.ReadCompleted(data.second);
        return result;
    }
    return "";
}

void TcpConn::HandleRead() {
    int err = 0;
    // 调用 MessageBuffer::Recv() 使用 readv() 读取数据,读取数据到缓冲区中。
    int n = input_buffer_.Recv(fd_, &err);
    if (n > 0 && read_cb_) {
        read_cb_(); // 触发读取回调逻辑
    } else if (n == 0 || (n < 0 && err != EAGAIN && err != EWOULDBLOCK)) { // 连接关闭或错误则关闭连接。
        Close();
    }
}

void TcpConn::HandleWrite() {
    int err = 0;
    int n = output_buffer_.Send(fd_, &err);
    if (n < 0 && err != EAGAIN && err != EWOULDBLOCK) {
        Close();
    } else if (output_buffer_.GetPendingSize() == 0) {
        channel_->DisableWriting();
    }
}

void TcpConn::Close() {
    if (closed_) return;
    closed_ = true;
    channel_->DisableAll();
    close(fd_);
    if (close_cb_) close_cb_();
}

5)EventLoopThread:线程封装器

EventLoopThread 封装了一个后台线程和一个与之关联的 EventLoop。用于在多线程环境中,确保每个线程拥有自己的事件循环。

启动流程:

  • StartLoop() 启动线程,等待其内部 EventLoop 初始化;

  • ThreadFunc() 中创建 EventLoop 实例,并执行 loop->Loop()

  • 提供 InitCallback,用于线程启动时执行用户自定义初始化逻辑。

确保线程和事件循环生命周期绑定,避免线程逃逸或资源泄露。

.h

class EventLoopThread {
public:
    using InitCallback = std::function<void(EventLoop*)>;

    explicit EventLoopThread(InitCallback cb = InitCallback());
    ~EventLoopThread();

    EventLoop* StartLoop(); // 外部调用启动线程,并获取 loop 指针

private:
    void ThreadFunc(); // 线程函数

    EventLoop* loop_;             // 指向子线程中的 loop
    std::thread thread_;
    std::mutex mutex_;
    std::condition_variable cond_;
    bool exiting_;
    InitCallback callback_;
};

.cpp

EventLoopThread::EventLoopThread(InitCallback cb)
    : loop_(nullptr), exiting_(false), callback_(std::move(cb)) {}

EventLoopThread::~EventLoopThread() {
    exiting_ = true;
    if (loop_) loop_->Quit(); // 通知 loop 退出
    if (thread_.joinable()) thread_.join();
}

EventLoop* EventLoopThread::StartLoop() {
    thread_ = std::thread([this]() { ThreadFunc(); });

    // 等待 loop_ 被初始化
    std::unique_lock<std::mutex> lock(mutex_);
    cond_.wait(lock, [this] { return loop_ != nullptr; });
    return loop_;
}

void EventLoopThread::ThreadFunc() {
    EventLoop loop;
    if (callback_) callback_(&loop);

    {
        std::lock_guard<std::mutex> lock(mutex_);
        loop_ = &loop;
        cond_.notify_one();
    }

    loop.Loop();  // 子线程进入事件循环
    loop_ = nullptr;
}

6)EventLoopThreadPool:线程池管理器

EventLoopThreadPool 管理多个 EventLoopThread,是多线程 Reactor 的核心支持模块。它负责:

  • 初始化并启动多个后台线程;

  • 保存每个线程中的 EventLoop 指针;

  • 提供 GetNextLoop() 接口实现轮询调度,用于连接分发。

适配不同的并发场景:可平滑扩展并发处理能力。

  • thread_num == 0,退化为单 Reactor 单线程模型;

  • 否则,主线程负责监听,子线程处理连接读写。

.h

class EventLoopThreadPool {
public:
    explicit EventLoopThreadPool(EventLoop* base_loop, int num_threads);
    ~EventLoopThreadPool() = default;

    void Start();                // 创建线程并启动各自的 loop
    EventLoop* GetNextLoop();    // 获取下一个 EventLoop*

private:
    EventLoop* base_loop_;                          // 主线程的 loop(用于 0 线程情况)
    int thread_count_;
    int next_;                                      // 用于轮询选择 loop
    std::vector<std::unique_ptr<EventLoopThread>> threads_;
    std::vector<EventLoop*> loops_;
};

.cpp

EventLoopThreadPool::EventLoopThreadPool(EventLoop* base_loop, int num_threads)
    : base_loop_(base_loop), thread_count_(num_threads), next_(0) {}

void EventLoopThreadPool::Start() {
    for (int i = 0; i < thread_count_; ++i) {
        auto thread = std::make_unique<EventLoopThread>();
        EventLoop* loop = thread->StartLoop();
        threads_.push_back(std::move(thread));
        loops_.push_back(loop);
    }

    if (thread_count_ == 0) {
        loops_.push_back(base_loop_);
    }
}

EventLoop* EventLoopThreadPool::GetNextLoop() {
    if (loops_.empty()) return base_loop_;
    EventLoop* loop = loops_[next_];
    next_ = (next_ + 1) % loops_.size(); // 轮询
    return loop;
}

3、工作流程

启动阶段:

在主线程中执行,主要完成服务初始化与监听注册:

执行过程如下:

  1. 创建 listen_fd,设置为非阻塞模式;

  2. 创建 accept_channel_,封装 listen_fd

  3. 设置其读事件回调为:TcpServer::HandleAccept()

  4. accept_channel_ 注册到主线程的 EventLoop 中;

  5. 启动 EventLoopThreadPool,每个线程拥有独立的 EventLoop

📌 目标: 主线程负责新连接接入,子线程负责连接 I/O 处理。

连接建立阶段

客户端连接到来 → 内核通知 listen_fd 可读 → 触发 accept 逻辑

执行过程如下:

  1. 主线程 epoll_wait() 返回,触发 accept_channel_->HandleEvent()

  2. 调用 read_cb_TcpServer::HandleAccept()

  3. 调用 accept() 获取 conn_fd

  4. 设置 conn_fd 为非阻塞;

  5. 通过 thread_pool_.GetNextLoop() 选取一个子线程的 EventLoop(如 loop1);

  6. loop1 提交任务(通过 RunInLoop()):

    • 构造 TcpConn 对象,封装连接生命周期;

    • 创建连接的 Channel 并设置其读事件回调为:TcpConn::HandleRead()

    • 将该 Channel 注册到 loop1epoll 中。

📌 关键点: 每个连接的 I/O 事件都被绑定到一个子线程中独立处理,实现多线程并发。

数据收发阶段

conn_fd 可读 → epoll 通知 → 子线程 loop 触发 TcpConn::HandleRead()

执行过程如下:

  • EventLoop 中的 epoll_wait() 检测到 conn_fd 可读;

  • 调用 Channel::HandleEvent() → 执行 TcpConn::HandleRead()

  • conn_fd 读取数据到 input_buffer_

  • 调用用户注册的 read_cb_ 处理数据(如:解析协议、业务逻辑处理);

  • 若业务处理耗时,可进一步将任务投递到业务线程池执行;

  • 若需要回应客户端,调用 TcpConn::Send()

数据发送阶段

TcpConn::Send(data, size)

执行过程如下:

  1. 如果当前线程是所属 EventLoop 线程:

    • 直接调用 SendInLoop()

  2. 否则通过 RunInLoop() 投递到正确线程执行;

  3. SendInLoop() 内部逻辑:

    • 如果 fd 可写且 output_buffer_ 为空,尝试直接调用 write() 发送;

    • 若未能一次发送完:

      • 将剩余数据写入 output_buffer_

      • 注册 EPOLLOUT 写事件;

      • 设置 channel_->SetWriteCallback()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值