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

用户态网络缓冲区设计

内核态网络缓冲区

在网络通信中,调用系统API收发数据实际上是从内核的缓冲区读取和发送,比如read/write,而数据何时收发到网络对端由内核协议决定。

linux 系统是如何收发数据包

 

接收流程

网卡接收数据-》通过DMA写入到内存-》发起硬件中断-》cpu处理硬件中断-》cpu屏蔽中断发起软中断-》其他线程处理软中断,从内存中读取数据帧-》去掉帧头帧尾-》-》根据tcp/udp协议确定socket,并将数据提取到对应socket接收缓冲区(重复该过程读取所有的数据帧)-》-》cpu开启硬件中断-》应用通过系统调用将数据从接收缓冲区拷贝到应用层

发送流程

应用通过系统调用将数据从应用层拷贝到发送缓冲区-》依次增加tcp/udp头部、ip头部、帧头、帧尾-》触发软中断通知网卡取数据-》将数据发送到内存-》通过DMA读取数据到网卡-》网卡发送数据。

tcp与udp在收发数据时的区别:

tcp使用缓冲区的原因

1.tcp粘包处理

为了保证tcp数据的可靠传输,tcp数据会被系统分段,因此需要数据缓冲区来存储数据,以此避免得到不完整的数据,但这也会导致多个用户定义的完整据粘合在一起,此时需要用户自定义完整数据的分割协议来识别每个完整数据。

1.特殊分隔符  \r\n

2.增加数据长度字段

2.发送数据的速度大于接收数据的速度

当发送数据的速度大于接收数据的速度时,需要缓冲区存储未接收的数据。

  1. udp不会使用缓冲区
  2. tcp数据会被系统自行分段,udp在ip层进行分片
  3. 发送成功后,tcp只删除内存中的克隆数据,udp删除原始数据

为什么需要用户态网络缓冲区

在用户态解决粘包(tcp)和生产者速度大于消费者速度的问题(tcp/udp)

用户态网络缓冲区设计

定长 buffer 设计

优点: 结构简单,易于实现

缺点: 需要频繁腾挪数据 ;需要实现扩缩容机制

ringbuffer 设计

使用环形数组解决频繁数据腾挪的问题

优点: 不需要腾挪数据

缺点: 需要实现扩缩容机制 ;造成不连续空间,可能引发多次系统调用

chainbuffer 设计

使用链表解决扩缩容的问题

优点: 不需要腾挪数据 动态扩缩容且无需数据拷贝

缺点: 造成不连续空间,可能引发多次系统调用

多次系统调用问题如何解决?

使用readv/writev函数,以readv为例:

readv 函数用于从文件描述符中读取数据到多个缓冲区中。它将数据从文件描述符读取到指定的缓冲区数组。 

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
  • fd 是要读取数据的文件描述符。

  • iov 是一个指向 iovec 结构数组的指针,每个 iovec 结构描述一个缓冲区的地址和大小。

  • iovcnt 是 iov 数组中 iovec 结构的数量。

  • readv 返回读取的总字节数,如果出错则返回 -1。

示例用法:

struct iovec iov[2];
char buffer1[100];
char buffer2[100];iov[0].iov_base = buffer1;
iov[0].iov_len = sizeof(buffer1);
iov[1].iov_base = buffer2;
iov[1].iov_len = sizeof(buffer2);ssize_t n = readv(fd, iov, 2);

用户动态网络缓冲区实现

#pragma once#include <bits/types/struct_iovec.h>
#include <stdint.h>
#include <vector>
#include <cstring>
#include <sys/uio.h>
#include <errno.h>class MessageBuffer
{
public:MessageBuffer() : rpos_(0), wpos_(0){//缓冲区默认大小为4096buffer_.resize(4096); // Initial size}explicit MessageBuffer(std::size_t size) : rpos_(0), wpos_(0){buffer_.resize(size);}// 不允许拷贝MessageBuffer(const MessageBuffer &) = delete;MessageBuffer &operator=(const MessageBuffer &) = delete;// 允许移动MessageBuffer(MessageBuffer &&other) noexcept: buffer_(std::move(other.buffer_)), rpos_(other.rpos_), wpos_(other.wpos_){other.rpos_ = 0;other.wpos_ = 0;}MessageBuffer &operator=(MessageBuffer &&other) noexcept{if (this != &other){buffer_ = std::move(other.buffer_);rpos_ = other.rpos_;wpos_ = other.wpos_;other.rpos_ = 0;other.wpos_ = 0;}return *this;}uint8_t *GetBasePointer(){return buffer_.data();}uint8_t *GetReadPointer(){return buffer_.data() + rpos_;}uint8_t *GetWritePointer(){return buffer_.data() + wpos_;}void ReadCompleted(std::size_t size){rpos_ += size;}void WriteCompleted(std::size_t size){wpos_ += size;}std::size_t GetActiveSize() const{return wpos_ - rpos_;}//返回从可写位置到vector大小之间的可用空间,该可用空间不包括从vector开始到可读位置之间的可用空间std::size_t GetFreeSize() const{return buffer_.size() - wpos_;}std::size_t GetBufferSize() const{return buffer_.size();}void Normalize(){//将所有数据移动到vector最前面if (rpos_ > 0){std::memmove(buffer_.data(), buffer_.data() + rpos_, GetActiveSize());wpos_ -= rpos_;rpos_ = 0;}}//确定可用空间是否够用,如果不够则进行扩容void EnsureFreeSpace(std::size_t size){if (GetBufferSize() - GetActiveSize() < size){Normalize();buffer_.resize(buffer_.size() + std::max(size, buffer_.size() / 2));}else if (GetFreeSize() < size)//如果总的可用空间够用,但后面的可用空间不够,则调整数据位置{Normalize();}}// windows iocp  boost.asiovoid Write(const uint8_t *data, std::size_t size){if (size > 0){EnsureFreeSpace(size);std::memcpy(GetWritePointer(), data, size);WriteCompleted(size);}}std::pair<uint8_t *, std::size_t> GetAllData(){return {GetReadPointer(), GetActiveSize()};}// 获取第一个 \r\n 之前的数据的指针和大小(若未找到返回nullptr和0)std::pair<uint8_t *, std::size_t> GetDataUntilCRLF(){uint8_t *data = GetReadPointer();std::size_t active_size = GetActiveSize();for (std::size_t i = 0; i < active_size - 1; ++i){if (data[i] == '\r' && data[i + 1] == '\n'){return {data, i}; // 数据长度为i,不包含\r\n}}return {nullptr, 0}; // 未找到}// linux reactor readv// 1. 尽可能的不腾挪数据// 2. 避免了每次都从栈上拷贝到堆上int Recv(int fd, int *err){char extra[65535]; // 65535struct iovec iov[2];iov[0].iov_base = GetWritePointer();iov[0].iov_len = GetFreeSize();iov[1].iov_base = extra;iov[1].iov_len = sizeof(extra);std::size_t n = readv(fd, iov, 2);if (n < 0){*err = errno;return n;}else if (n == 0){*err = ECONNRESET;return 0;}//如果读取的数据小于GetFreeSize,没有用到extra,表明已经读取完毕else if (n <= GetFreeSize()){WriteCompleted(n);return n;}//否则还要将剩余数据复制到缓冲区中else{// WRN: GetfreeSize() 在 WriteCompleted() 中会被更新, extra_size 需要提前计算std::size_t extra_size = n - GetFreeSize();WriteCompleted(GetFreeSize());Write(reinterpret_cast<uint8_t *>(extra), extra_size);return n;}}private:std::vector<uint8_t> buffer_;//使用std::vector数据结构,能够动态扩容std::size_t rpos_;//指向读位置std::size_t wpos_;//指向写位置
};

https://github.com/0voice

http://www.dtcms.com/a/337476.html

相关文章:

  • C++常见考点与易错点详细教程
  • SAP 数据脱敏工具:SNP TDO如何满足新颁敏感信息政策要求
  • 视觉语言导航(8)——任务驱动的架构增强 3.3
  • Redis——基础篇
  • 当使用STL容器去存放数据时,是存放对象合适,还是存放对象指针(对象地址)合适?
  • 将std容器的正向迭代器转换成反向迭代器
  • SCAU学习笔记 - 校科联自科二面通关指南
  • 淘宝扭蛋机小程序开发:引领电商娱乐化新潮流
  • Python循环语句 从入门到精通
  • Qt——对话框 QDialog
  • 51c大模型~合集170
  • Codeforces I Will Definitely Make It
  • 构建情感智能体:下一代AI心理助手的架构与实践
  • 折扣大牌点餐api接口对接适合本地生活吗?
  • QT中同时兼任VS2019和VS2022套件
  • 企业级大模型微调指南:数据准备、参数调优与部署优化全流程
  • 从0-1使用Fastmcp开发一个MCP服务,并部署到阿里云百炼 -持续更新中
  • 大数据服务完全分布式部署- 其他组件(阿里云版)
  • 数字隔离器,变频器安全运行的 “隐形引擎”
  • 深入对比:Chromium的base::RefCounted与std::shared_ptr——从线程安全到性能优化
  • 宏观认识 Unitree LiDAR L1 及其在自动驾驶中的应用
  • Avalonia实例实战七:动画
  • win环境断电强制拉库报ORA-600 kcbzib_kcrsds_1故障处理---惜分飞
  • 数据库:表和索引结构
  • 【datawhale组队学习】n8n TASK01
  • 知识蒸馏 Jensen-Shannon散度
  • 为什么品牌更愿意为新品打广告?
  • 机器学习——XGBoost算法
  • 力扣2道dp
  • easyexcel fastexcel 官方文档 easyexcel合并单元格