用户态网络缓冲区的设计

一、网络缓冲区

  • 在内核中也是有网络缓冲区的,比如使用 read 读取数据(read 是一种系统调用,第一个参数为 fd),当陷入到内核态的时候,会通过 fd 指定 socketsocket 会找到对应的接收缓冲区。
  • 在应用程序中设计缓冲区,通过一定的算法来组织好我们的网络数据,方便应用程序在处理业务逻辑的时候从缓冲区中获取数据,进行解析,展开相对应的业务逻辑。

二、Linux 如何接收发送网络数据

在这里插入图片描述

  • 应用程序通过系统调用由用户态陷入到内核态,在内核态中找到对应的 socket
    • socket 层有对应的接收缓冲区发送缓冲区
  • 根据具体的接口判断是 TCP 还是 UDP,如果是 TCP,就加上一些 TCP 的处理,比如说加上 TCP 头
  • 进入 IP 层。
  • 进入 MAC 层,会加上一个 MAC 头部信息(帧头帧尾)。
  • 经过网卡驱动把数据写到一个环形缓冲区中,便于网卡环形缓冲区中读取数据,然后把数据发送到网络中。

  • 内核中读取数据是独立运行的,应用程序读取数据也是独立运行的。
  • 如果应用层需要拿对端发送的数据,先需要通过系统调用陷入到内核态,然后把在 socket 层中的已经准备好的接收缓冲区中的数据拷贝到应用程序中
  • 两个流程:
    • 网络协议栈从网络中把数据写到 socket 对应的的接收缓冲区。
    • 应用程序通过系统调用从接收缓冲区中拷贝数据到应用层去处理。

  • 网络数据
    1. 数据帧 frame → 网卡驱动、MAC 层。
    2. 数据包 packet → IP 层。
    3. 数据段 segment → TCP / UDP 层。
    4. data → 应用层。

  • 接收网络数据包的流程:
    • 网卡接收到数据包,然后把数据写到 DMA 区域(ringbuffer 结构)
      • DMA:Direct Memory Access,直接内存操作,不需要 CPU 参与
    • 网卡向 CPU 发起硬件中断,CPU 收到中断请求,根据中断表查找中断处理函数调用中断处理函数
      • 为什么需要硬件中断 ?
        • 因为需要使用 CPU 将数据拷贝出来,然后在内核中进行处理。
    • 中断处理函数将屏蔽硬件中断发起软件中断让 CPU 参与将 DMA 区域中的数据拷贝到网卡驱动,然后经过网络协议栈进行处理
      • 为什么需要先屏蔽硬件中断 ?
        • 避免 CPU 频繁被网卡中断。
      • 为什么需要软件中断 ?
        • 使用软件中断处理耗时操作,避免执行时间过长,导致 CPU 没法响应其他硬件中断。
    • 内核 ksoftirqd 线程负责软中断处理,该线程从 ringbuffer 中逐个取出数据帧到 sk_buff
    • 从帧头取出 IP 协议,判断是 IPv4 还是 IPv6,去掉帧头帧尾。
    • 从 IP 头看上一层协议是 TCP 还是 UDP ,根据五元组找到 socket并将数据提取出来放到 socket 的接收缓冲区当全部数据提取完毕后软件中断处理结束然后开启硬件中断
    • 应用程序通过系统调用将 socket 的接收缓冲区中的数据拷贝到应用层缓冲区

  • 发送网络数据包的流程(TCP)
    • 应用程序通过系统调用将用户数据拷贝到 sk_buff 并放到 socket 的发送缓冲区里。(UDP 没有发送缓冲区)
    • 网络协议栈从 socket 的发送缓冲区中取出 sk_buff并克隆出一个新的 sk_buff(TCP 支持丢失重传)
    • 向下传递依次增加 TCP / UDP 头部、IP 头部、帧头(MAC 头部)、帧尾。
    • 触发软中断通知网卡驱动程序,有新的网络包需要发送
    • 网卡驱动程序从发送队列依次取出 sk_buff 写到 DMA 区域。(有发送 DMA 区域和接收 DMA 区域)
    • 触发网卡发送,发送成功,触发硬件中断,释放 sk_buffringbuffer 的内存。(TCP 对应的是克隆而来的,UDP 对应的是原始的)
    • 当收到 TCP 报文的 ACK 应答时,将释放原始的 sk_buff(socket 的发送缓冲区中的 sk_buff

三、系统调用

在这里插入图片描述

  • read / write(TCP)
    • read 是一个系统调用,由用户态陷入到内核态,内核态会执行 system call read,把接收缓冲区中的数据拷贝到用户态准备的 buf 中,sz 是预期拷贝多少字节,n 表示实际拷贝了多少字节。
    • write 是把用户态准备发送的数据拷贝到发送缓冲区中,sz 是预期拷贝多少字节,n 表示实际拷贝了多少字节。
    • readwrite 都是同步 IO 处理读写数据,直接通过返回值就可以判断 IO 是否完成了 → 是不是将数据从内核态拷贝到用户态了
    • 使用非阻塞 IO 时,n = -1errno = EWOULDBLOCK,说明接收缓冲区为空。
    • 使用阻塞 IO 时,如果接收缓冲区为空,read 会阻塞当前线程,直到接收缓冲区中有数据。
    • 如果 n = 0,说明 read 在接收缓冲区中读到了一个 EOF(四次挥手,final 包做的标记,1 个字节),即连接断开了。
  • recv / send(TCP 或 UDP)
    • recv 最后一个参数为 0 的话,就和 read 等价。
    • send 最后一个参数为 0 的话,就和 write 等价。
  • recvfrom / sendto(UDP)
    • recvfrom 能够返回发送方的地址信息,而 recv 则不能。在需要识别或根据发送方地址作出响应的应用场景中,recvfrom 更加适用。
    • send 需要预先通过 connect 指定目的地址,之后可以重复使用 send 发送数据到这个地址,而无需每次都指定。sendto 允许在每次调用时指定目的地址,增加了灵活性,特别是在需要与多个不同的远端通信的场景中。
    • 一个固定远端通信使用 recv / send。
    • 多个远端动态通信使用 recvfrom / sendto。
  • WSARecv / WSASend(windows 下异步 IO)
    • WSARecvWSASend 都是异步 IO 处理读写数据(在 windows 下的 iocp 中),比如通过一个线程不断轮询调用 GetQueuedCompletionStatus 接口,获知完成通知。

  • 网络编程只处理 4 件事:
    1. 连接的建立。
    2. 连接的断开。
    3. 数据的接收。
    4. 数据的发送。

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

  • 从业务层生产消费模型出发:
    • 对于 read接收缓冲区
      • read 从接收缓冲区中读取数据 → 生产者。
      • 业务层根据这些数据来处理对应的业务逻辑 → 消费者。
      • 如果生产者的速度大于消费者的速度,就会导致读出来了很多数据,但是业务层来不及处理,那这些来不及处理的数据就应该缓存起来,等待业务层来处理。
    • 对于 write发送缓冲区
      • 业务逻辑产生的数据 → 生产者。
      • 网络协议栈从发送缓冲区中取出数据并发送 → 消费者。
      • 如果生产者的速度大于消费者的速度,也需要把业务逻辑产生的数据缓存起来,等待网络协议栈空闲的时候再把剩余的数据发送出去。
    • 会为每一个连接都准备一个接收缓冲区和发送缓冲区
  • 从 posix api 接口出发(粘包处理)
    • 读取数据的时候,可能不是一个完整数据包,而是多个数据包,也就是不能一次性接收数据,一次性发送数据。
    • 完整数据包是用户定义的:
      • 特殊字符来界定完整数据包,比如使用 \r\n
      • 固定长度来界定完整数据包,在包头加上 2 个字节的长度信息。
    • 为什么要界定完整数据包 ? 因为应用程序是按照一个完整数据包进行处理的。

  • UDP 和 TCP 协议是否影响用户态缓冲区设计 ?
    • 不会影响。
  • 不同网络编程模型是否影响用户态缓冲区设计 ?
    • 不会影响。

  • 网络编程模型:不同网络编程模型处理 IO 的方式不一样
    • 处理 IO 分为两部分:先检测 IO 是否就绪再操作 IO 进行数据拷贝
      • 对于 read ,接收缓冲区中有数据了,IO 就处于就绪状态,否则,IO 处于未就绪状态
      • 对于 write,发送缓冲区满了,IO 就处于未就绪状态,否则 IO 处于就绪状态
  • 阻塞 IO 网络编程模型
    • 通过阻塞线程的方式等待 IO 就绪。
  • reactor 网络编程模型
    • 基于同步 IO 模型
    • IO 多路复用:只能检测 IO 是否就绪,不能操作 IO
      • 一个 IO 多路复用的对象可以同时检测多个连接的 IO 是否就绪,一个连接对应一个 fd
    • 事件循环:调用 IO 多路复用,获取那些就绪的事件,依次处理,操作 IO
    • 服务端如何知道客户端什么时候发送数据 ?
      • selectreactor 网络编程模型fd 交由 select 进行管理,会去注册一个读事件,select 会检测接收缓冲区,判断对端是否发送数据,如果发送数据了,会触发读事件(抛出读事件到应用层),应用层拿到触发的读事件,调用 read, 从用户态陷入到内核态,将数据从接收缓冲区拷贝到用户态。
  • proactor 网络编程模型
    • 基于异步 IO 模型
    • windows 下的 iocp 机制:
      • 将 fd 绑定在完成端口上。
      • 抛出具体的读写请求(WSARecvWSASend)到完成端口上。
      • 完成端口负责检测 IO 是否就绪。
    • IOCP 对象是一个事件队列,用于存储完成的 IO 操作的结果。
    • iocp 机制会检测接收缓冲区,判断对端是否发送数据,如果发送数据了,会在内核中直接进行拷贝,拷贝到 buffer, 拷贝结束后会以事件的形式通知用户态,用户态通过调用 GetQueuedCompletionStatus 接口来获知完成通知。
  • reactor 和 proactor 都是一种异步事件的处理方式
    • proactor 在内核中检测 IO 是否就绪,由内核操作 IO 进行数据拷贝 → 内核自己把数据拷贝到 buffer
    • reactor 会涉及到多次内核和用户态的交互,在内核中检测 IO 是否就绪,在用户态主动操作 IO 进行数据拷贝 → 用户态主动调用 read

五、如何设计用户态网络缓冲区

  • 因为处理数据的时候是一个生产消费模型,所以设计用户态网络缓冲区要实现类似队列的结构

定长 buffer

char buffer[16 * 1024 * 1024];
uint offset; // 可用数据包的长度,从网络缓冲区拷贝了多少数据到 buffer
  • 生产消费模型
    • 生产者:read追加数据 → 把数据从内核态拷贝到用户态,填充用户态网络缓冲区 buffer
    • 消费者:从用户态网络缓冲区界定数据包,界定成功,取数据包 → 把剩余数据挪到最前面,修改 offsetoffset = offset - 完整数据包长度
      在这里插入图片描述
  • 优点:结构简单,易于实现。
  • 缺点:
    • 需要频繁腾挪数据,只要界定成功一个完整数据包,就需要把后面的数据挪到前面,以空余更多的空间供生产者往里面填充数据。
    • 需要实现扩缩容机制,如果缓冲区剩余空间不足以存放数据,需要对缓冲区进行扩容,并且将旧缓冲区中的数据挪到新缓冲区中。
  • 使用场景:
    • 客户端发送的数据比较少,并且发送频率不高。
    • Redis 的接收缓冲区,使用的是定长 buffer

ringbuffer

char buffer[16 * 1024 * 1024]
uint head;
uint tail;
  • ringbuffer 是逻辑上的环形缓冲区,记录头尾指针 headtail 来标识数据范围。
    • 生产者往 tail 追加,消费者移动 head 指针。
    • head % size
    • tail % size

在这里插入图片描述

  • 优点:不需要腾挪数据。
  • 缺点:
    • 需要实现扩缩容机制。
    • 造成不连续空间,可能引发多次系统调用。
      • 缓冲区数据为不连续空间,虽然剩余空间远大于 100 个字节,但是由于物理空间不连续,需要调用两次 read,第一次调用 read 往末尾填充 50 个字节,第二次调用 read 往最前面填充 50 个字节。调用多次 read,即引发多次系统调用,造成系统损耗

在这里插入图片描述

  • 优化方法:Linux 下的 readvwritev,windows 下的 WSARecvWSASend
    • 内核态中的发送缓冲区和接收缓冲区都是连续空间
    • readv:通过一次系统调用将内核中连续空间的数据拷贝到用户态不连续空间。
    • writev:通过一次系统调用将用户态不连续空间数据拷贝到内核中的连续空间。
      ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
      ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
      

  • 系统调用:线程正在执行用户态代码,调用 read,此时会保存用户态代码运行现场,从用户态陷入到内核态,线程开始执行内核态代码,执行 syscall_read检测 IO 是否就绪,如果就绪了,将数据拷贝到用户态,然后从内核态切换回用户态

chainbuffer

在这里插入图片描述

  • 不腾挪数据misalign 表示有效数据的起始指针,offset 表示有效数据的长度。
  • 动态扩缩容并且不腾挪数据
    • 动态扩容:当前节点剩余空间不足以存放 100 字节的完整数据,先扩容一个节点,然后将当前节点的剩余空间填充 50 个字节,扩容的节点填充 50 个字节,当前节点的 next 指针指向扩容的节点,并且把 last 指针指向扩容的节点。
    • 动态缩容:当发现 offset 为 0 的时候,删除该节点,将 first 指针指向下一个节点。

在这里插入图片描述

  • 优点:不需要腾挪数据,动态扩缩容,并且无需拷贝数据。
  • 缺点:造成不连续空间,可能引发多次系统调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/529322.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

抓住风口,快速上手RAG应用开发!

免责声明~ 任何文章不要过度深思! 万事万物都经不起审视,因为世上没有同样的成长环境,也没有同样的认知水平,更「没有适用于所有人的解决方案」; 不要急着评判文章列出的观点,只需代入其中,适度…

37-代码测试(下):Go语言其他测试类型及IAM测试介绍

。 Go中的两类测试:单元测试和性能测试。 我就来介绍下Go 语言中的其他测试类型:示例测试、TestMain函数、Mock测试、Fake测试等, 示例测试 示例测试以Example开头,没有输入和返回参数,通常保存在example_test.go…

Go语言实现Redis分布式锁2

项目地址: https://github.com/liwook/Redislock 1.支持阻塞式等待获取锁 之前的是只尝试获取一次锁,要是获取失败就不再尝试了。现在修改为支持阻塞式等待获取锁。 添加LockOptions结构体 添加option.go文件。 在LockOptions中 isBlock表示是否是阻塞模式blo…

美团一面:说说synchronized的实现原理?问麻了。。。。

引言 在现代软件开发领域,多线程并发编程已经成为提高系统性能、提升用户体验的重要手段。然而,多线程环境下的数据同步与资源共享问题也随之而来,处理不当可能导致数据不一致、死锁等各种并发问题。为此,Java语言提供了一种内置…

Pots(DFS BFS)

//新生训练 #include <iostream> #include <algorithm> #include <cstring> #include <queue> using namespace std; typedef pair<int, int> PII; const int N 205; int n, m; int l; int A, B, C; int dis[N][N];struct node {int px, py, op…

谱重排变换和同步压缩变换的区别是什么?

谱重排方法能够得到非常高的时频分辨率&#xff0c;但是同样也存在一个问题&#xff0c;不能重构原始信号&#xff0c;2011 年 Daubechies 提出了一种基于相位的高分辨率时频分析方法—同步压缩小波变换&#xff0c;该方法也是一种谱重排的方法&#xff0c;能使非平稳非线性信号…

Mybatis报错:Unsupported conversion from LONG to java.sql.Timestamp

Mybatis在封装结果集的时候&#xff0c;如果方法返回的是对象&#xff0c;则会去调用这个对象的无参构造方法。 如果实体类标注了Builder注解&#xff0c;则此注解会把默认的构造方法全部改成私有的&#xff0c;则Mybatis在通过无参构造方法反射创建对象时&#xff0c;就会找不…

Redis中的集群(二)

节点 集群数据结构 redisClient结构和clusterLink结构的相同和不同之处 redisClient结构和clusterLink结构都有自己的套接字描述符和输入、输出缓冲区&#xff0c;这两个结构的区别在于&#xff0c;redisClient结构中的套接字和缓冲区是用于连接客户端的&#xff0c;而clust…

已解决:windows 下无法加载文件 xxx.ps1,因为在此系统上禁止运行脚本

目录 1&#xff0c;问题描述2&#xff0c;问题解决 1&#xff0c;问题描述 当通过 npm 全局安装依赖后&#xff08;比如 ts 对应的 tsc 命令&#xff0c;还有 pnpm&#xff09;&#xff0c;想直接使用安装的命令&#xff0c;就会报错&#xff1a; 2&#xff0c;问题解决 以管…

2024年AI带来的革命性变革与创新

大家好&#xff01;相信大家对于AI&#xff08;人工智能&#xff09;的发展已经有了一定的了解&#xff0c;但你是否意识到&#xff0c;到了2024年&#xff0c;AI已经变得如此强大和普及&#xff0c;带来了我们从未想象过的便利和创新呢&#xff1f;让我们一起来看看AI在这个时…

Python学习笔记11 - 列表

1. 列表的创建与删除 2. 列表的查询操作 3. 列表的增、删、改操作 4. 列表元素的排序 5. 列表生成式

负荷预测 | Matlab基于TCN-GRU-Attention单输入单输出时间序列多步预测

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于TCN-GRU-Attention单输入单输出时间序列多步预测&#xff1b; 2.单变量时间序列数据集&#xff0c;采用前12个时刻预测未来96个时刻的数据&#xff1b; 3.excel数据方便替换&#xff0c;运行环境matlab20…

QT drawPixmap和drawImage处理图片模糊问题

drawPixmap和drawImage显示图片时&#xff0c;如果图片存在缩放时&#xff0c;会出现模糊现象&#xff0c;例如将一个100x100 的图片显示到30x30的区域&#xff0c;这个时候就会出现模糊。如下&#xff1a; 实际图片&#xff1a; 这个问题就是大图显示成小图造成的像素失真。 当…

蓝桥杯刷题-16-买瓜-DFS+剪枝优化⭐⭐

蓝桥杯2023年第十四届省赛真题-买瓜 该如何剪枝呢&#xff1f;⭐⭐ 如果当前方案的切的刀数&#xff0c;已经大于等于了之前已知合法方案的最优解&#xff0c;那么就没必要 往后搜了。如果后面的瓜的总和加起来&#xff0c;再加上当前已有的重量&#xff0c;都不到m,那么也没…

Flask Web框架的使用-安装Flask

Flask Web框架的使用-安装Flask 一、前言二、安装Flask 一、前言 个人主页: ζ小菜鸡大家好我是ζ小菜鸡&#xff0c;让我们一起来学习Flask Web框架的使用-安装Flask如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连) 二、安装Flask 大多数Python 包都是使用pip 实用工具安…

《看漫画学C++》第9章 直达记忆深处的数据类型——指针类型

C中最难的主题之一莫过于指针&#xff0c;《看漫画学C》通过漫画形式介绍知识。 上述知识点摘录于&#xff1a;《看漫画学C》第9章 直达记忆深处的数据类型——指针类型

机器学习(五) -- 监督学习(2) -- k近邻

系列文章目录及链接 目录 前言 一、K近邻通俗理解及定义 二、原理理解及公式 1、距离度量 四、接口实现 1、鸢尾花数据集介绍 2、API 3、流程 3.1、获取数据 3.2、数据预处理 3.3、特征工程 3.4、knn模型训练 3.5、模型评估 3.6、结果预测 4、超参数搜索-网格搜…

VRRP虚拟路由实验(思科)

一&#xff0c;技术简介 VRRP&#xff08;Virtual Router Redundancy Protocol&#xff09;是一种网络协议&#xff0c;用于实现路由器冗余&#xff0c;提高网络可靠性和容错能力。VRRP允许多台路由器共享一个虚拟IP地址&#xff0c;其中一台路由器被选为Master&#xff0c;负…

杨笛一新作:社恐有救了,AI大模型一对一陪聊,帮i人变成e人

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了免费的人工智能中文站https://ai.weoknow.com 新建了收费的人工智能中文站ai人工智能工具 更多资源欢迎关注 在社交活动中&#xff0c;大语言模型既可以是你的合作伙伴&#xff08;partner&#xff09;&#xff0…

链表的中间结点——每日一题

题目链接&#xff1a; OJ链接 题目&#xff1a; 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,5] 解释&…