当前位置: 首页 > news >正文

WebeServer实现:学到了哪些东西

前言

  这里话就是总结一下之前没讲过的一些东西

系统调用

  1. accept与accept4
      当我们调用accept接收一个新的fd的时候,往往需要在调用fcntl将这个fd变成非阻塞IO,那么有没有一个系统调用可以一次性做完这两件事呢,有的有的就是accept4.
// accept 函数原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);// accept4 函数原型
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);int connfd = ::accept4(sockfd_, (sockaddr *)&addr, &len, SOCK_NONBLOCK | SOCK_CLOEXEC);

  accept4 可以通过 flags 参数设置以下 socket 选项:
  SOCK_NONBLOCK:将新 socket 设置为非阻塞模式。
  SOCK_CLOEXEC:将新 socket 设置为在执行 exec 时自动关闭(避免子进程继承)
2. epoll_create与epoll_create1
  epoll_create1 通过 flags 参数提供了额外的功能:
* EPOLL_CLOEXEC:设置文件描述符的 close-on-exec 标志,确保在执行 exec() 系统调用时自动关闭 epoll 实例。
* 0:如果 flags 为 0,则 epoll_create1 的行为与 epoll_create 相同
3. eventfd与epoll
  eventfd本质上是一个计数器,对它的read/write操作都是原子的,当我们通过write向eventfd写入的时候,此时epoll会监听到此fd可读,至于可写嘛…只要没到最大值就是可写,监听的意义不大。
4. timefd与epoll
  timefd在muduo库作为超时机制也使用的不少,当timefd对应的time超时的时候,也会触发epoll的可读事件,可以用来配合回调处理某些超时时间。
5. readv与writev
  readv系统调用用于把数据读取到不同的位置,这在有些场景下还是很好用的:(1) 把报文头和报文内容分开,读取到不同的位置;(2)一次性把所有数据都读出来,避免调用多次read

// read 函数原型
ssize_t read(int fd, void *buf, size_t count);// readv 函数原型
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
  1. mmap与sendFile
      sendFile避免了将数据的多次拷贝,直接就可以把数据发送出去
#include <sys/sendfile.h>ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
  1. IO多路复用与阻塞/非阻塞IO
      先说我个人的看法:无论是水平触发还是边缘触发,都应该使用非阻塞IO,当然边缘触发必须使用非阻塞IO。
      为什么呢?对于水平触发而言,epoll可读就意味着我们一定可以读出数据吗,答案是否定的,man手册对这块有解释:就算可读,数据在后期也可能被丢弃了,那如果是阻塞IO的话,这是非常危险的。
    非阻塞
      对于边缘触发呢,则要求我们一定得把所有数据一次性读出来,因为边缘触发一定是只有在状态发生变化的时候才会报道,那我们怎么一次性读出所有数据呢----当然是调用多次read啦,这也就意味着如果是阻塞IO,那就很难做到了因为读不到就阻塞了,而非阻塞IO就不同,利用非阻塞IO的返回值,就可以这么设计

/*水平触发和边缘触发都可以用这个逻辑*/
/*水平触发为了保证公平性,也可以只读一次*/
bool ET = true; /*表示边缘触发*/
do{ssize_t readLen =  read()if(readLen < 0) {if(errno != EAGAIN) {/*处理一下*/}break;}
}while(ET)
  1. epoll的边缘触发与水平触发
      对于水平触发,你可以每次只read一部分,然后如果没读完那么epoll还会触发可读的,但是对于边缘触发,必须要一次性把缓冲区内容都读出来,要不然它是不会再上报的。
      至于边缘触发是不是一定比水平触发性能好,我个人还是觉得要视场景分析:
      场景1:fd可读,但是读取的缓存区buf长度大于等于需要读取的长度。此时水平触发性能更好,因为我们可以一次性读完所有的数据,对于水平触发的代价就是一次epoll + 一次read即可。但是此时边缘触发呢,则是需要一次epoll + 两次read(第二次read返回负值break)
      场景2:fd可读,但是读取的缓存区buf长度小于需要读取的长度。此时边缘触发性能更好。边缘触发一次epoll + 多次read,而水平触发需要多次epoll + 多次read调用。
      场景3:公平性,水平触发很明显比边缘触发更公平,假设我们需要处理所有的活跃文件fd,如果有的fd需要读的内容过多,意味着边缘触发需要读取多次,这对其他文件描述符是不公平的。而水平触发嘛…看你怎么设计程序了,可以每个人都读一次保证公平,也可以使用上面伪代码介绍的方式(前提是非阻塞IO)

C++这一块

  正好给我这个c with class的人开开眼(>.<),不过我感觉c++的回调虽然很强大,但是用的挺难受的,什么bind move啦,也许是我不适应吧

  1. 智能指针
    unique_ptr与share_ptr与weak_ptr
    unique_ptr没啥好讲的,主要是这里的share_ptr与weak_ptr和交叉引用问题,如果交叉引用则这两对象的析构函数将都不能调用。结合项目来说吧,这里的channel里用到了weak_ptr:
      假设由于某些意外,需要我们关闭掉Clientfd对应的连接,此时可能尚有可读事件还没有处理,为了防止出问题,我们可以看看Clientfd对应的对象是否存在。
    std::weak_ptr<void> tie_;       /*观察对象是否存在*/bool tied_;// Channel本身是fd的封装 是绑定到某个连接了的
void Channel::handleEvent(Timestamp receiveTime)
{if(tied_)   /*如果绑定了对象 weak_prt的作用就体现出来了 */{std::shared_ptr<void> guard = tie_.lock();if(guard){handleEventWithGuard(receiveTime);}/*如果对象都不存在了,就没必要处理对应事件了,要不还可能会报错,毕竟回调函数里可是要访问对象的资源的*/}else /*监听Socket的fd是不存在绑定的*/{handleEventWithGuard(receiveTime);}}

后期补充一下啦
3. unique_lock与lock_gurad
4. 虚函数
5. move
7. bind函数与函数指针相比,好在哪里
8. 右值引用与forward

相关文章:

  • STM32F103_LL库+寄存器学习笔记12.3 - 串口DMA高效收发实战3:支持多实例化的版本
  • 如何在MacOS系统和Windows系统安装节点小宝远程工具
  • Java-52 深入浅出 Tomcat SSL工作原理 性能优化 参数配置 JVM优化
  • 爬虫获取数据:selenium的应用
  • Docker简单介绍与使用以及下载对应镜像(项目前置)
  • CVE-2024-6387漏洞、CVE-2025-26465漏洞、CVE-2025-26466漏洞 一口气全解决
  • 【JS-4.3-鼠标常用事件】深入理解DOM鼠标事件:全面指南与最佳实践
  • FPGA四十年创新:因仿真加速而生,AI加速而盛!
  • 股票账户的管理和交易
  • 车载电子电器架构 --- 法律和标准对电子电气架构的影响
  • Mac电脑-Markdown编辑器-Typora
  • 数据库part3---表关联、索引、视图
  • 深入浅出:Go语言中的Cookie、Session和Token认证机制
  • Vibe Coding - 进阶 Cursor Rules
  • gRPC 框架面试题精选及参考答案
  • C++模板基础
  • Python实现MySQL建表语句转换成Clickhouse SQL
  • OpenAI与微软的未来合作之路:充满挑战的AI竞赛与共赢
  • [Github]GitHub 2FA快速安全配置全攻略
  • 前端登录不掉线!Vue + Node.js 双 Token 无感刷新方案
  • 下载app安装/朝阳seo搜索引擎
  • 哪些网站做的比较炫/免费的舆情网站app
  • 中国万网域名登录/西安seo搜推宝
  • 营销型网站建设深度网/百度seo优化多少钱
  • 塘沽做网站/安卓优化大师官方版本下载
  • ppt模板网站大全/营销方法