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

day26-监听写事件

# day26-监听写事件

在Channel中提供了EnableWrite函数,但是在整个系统中,并没有对写操作进行监听,服务器对客户端的写入一般直接发生在读事件的过程中。并且会一次性将所有内容写入。尽管保证了数据的完整性,但是性能并不高。

因此我们希望将写事件也注册到Epoll中以提高服务器的性能。

由于我们的服务器,TcpConnetion的socket是ET模式的,在ET模式下,读事件很容易理解,当TCP缓冲区中从无到有,就会触发,这也意味着,在进行读操作时,必须要将TCP缓冲区中的所有数据一次接收干净,因此很难有再次接收数据的机会。

但是写事件EPOLLOUT的触发一般发生在刚刚添加事件或者TCP缓冲区从不可写变成可写时。所以一般不会在创建socket时就直接监听读事件(这会导致触发一次EPOLLOUT,但是可能没有数据可写,而之后就不再触发了),而是有程序来控制,具体的来说,当我们调用Send函数时,我们会首先发送一次数据,如果此时TCP缓冲区满了导致后续数据没有发送才会注册一个EPOLLOUT事件,期待被通知进行下一次发送。

此外,EPOLLOUT也可以被强制触发,就是每次在epoll_ctl做EPOLL_CTL_ADD或者EPOLL_CTL_MOD时,如果此时是可写状态,也会被强制触发一次。

利用这两个机制,就可以重新实现服务器的Send函数。

在调用Send时,我们首先尝试先发一次数据测试,如果没有将所有数据发送,那么就进行监听写的操作等待后续机会继续发送。

void TcpConnection::Send(const char *msg, int len){

    int remaining = len;
    int send_size = 0;

    // 如果此时send_buf_中没有数据,则可以先尝试发送数据,
    if (send_buf_->readablebytes() == 0){
        // 强制转换类型,方便remaining操作
        send_size = static_cast<int>(write(connfd_, msg, len));

        if(send_size >= 0){
            // 说明发送了部分数据
            remaining -= send_size;
        }else if((send_size == -1) && 
                    ((errno == EAGAIN) || (errno == EWOULDBLOCK))){
            // 说明此时TCP缓冲区是慢的,没有办法写入,什么都不做
            send_size = 0;// 说明实际上没有发送数据
        }
        else{
            LOG_ERROR << "TcpConnection::Send - TcpConnection Send ERROR";
            return;
        }
    }
    
    // 将剩余的数据加入到send_buf中,等待后续发送。
    assert(remaining <= len);
    if(remaining > 0){
        send_buf_->Append(msg + send_size, remaining);
        // 到达这一步时
        // 1. 还没有监听写事件,在此时进行了监听
        // 2. 监听了写事件,并且已经触发了,此时再次监听,强制触发一次,如果强制触发失败,仍然可以等待后续TCP缓冲区可写。
        channel_->EnableWrite();
    }
}
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

由于我们使用了Epoll对写事件进行了监听,那么我们也没有必须一次性的将所有数据进行写完,我们只需要尽可能的将TCP缓冲区写满即可。并将剩余的数据等待下一次写事件被触发即可。

void TcpConnection::WriteNonBlocking(){

    int remaining = send_buf_->readablebytes();
    int send_size = static_cast<int>(write(connfd_, send_buf_->Peek(), remaining));
    if((send_size == -1) && 
                ((errno == EAGAIN) || (errno == EWOULDBLOCK))){
        // 说明此时TCP缓冲区是满的,没有办法写入,什么都不做 
        // 主要是防止,在Send时write后监听EPOLLOUT,但是TCP缓冲区还是满的,
        send_size = 0; // 在后续`Retrieve`处使用
    }
    else if (send_size == -1){
        LOG_ERROR << "TcpConnection::Send - TcpConnection Send ERROR";
    }

    remaining -= send_size;
    send_buf_->Retrieve(send_size);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

最后,我们在TcpConnection的构造函数中,为Channel设置写回调即可。

上述修改了Send和WriteNonBlocking代码,使我们的服务器在面对需要写入大量数据时的情况下不会阻塞在写的操作上。进一步提高服务器的性能。

编辑 (opens new window)
上次更新: 2025/05/21, 06:42:57
day25-更有效的缓冲区
day27-处理静态文件,实现POST请求

← day25-更有效的缓冲区 day27-处理静态文件,实现POST请求→

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