用户态网络缓冲区的设计

news2024/12/23 10:35:18

一、网络缓冲区

  • 在内核中也是有网络缓冲区的,比如使用 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 指针指向下一个节点。

在这里插入图片描述

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

安装VMware ESXi虚拟机系统

简介:ESXi是VMware公司开发的一款服务器虚拟化操作系统。它能够在一台物理服务器上运行多个虚拟机,每个虚拟机都可以独立运行操作系统和应用程序,而且对硬件配置要求低,系统运行稳定。 准备工具: 1.8G或者8G以上容…

7-155 好玩的游戏:消消乐

消消乐是一个非常流行的手机游戏。现在游戏创意设计师Jerry突发奇想设计一个如下图所示的一维消消乐游戏,Jerry想知道游戏生成的小球布局在玩家玩的过程中最高总分能得多少,现在Jerry向资深的程序员你求助,希望你能帮助他算出每个游戏初局的最高得分。 游戏规则是这样的:…

SWM341系列应用(RTC、FreeRTOS\RTTHREAD应用和Chip ID)

SWM341系列RTC应用 22.1、RTC的时钟基准 --liuzc 2023-8-17 现象:客户休眠发现RTC走的不准,睡眠2小时才走了5分钟。 分析与解决:经过排查RTC的时钟源是XTAL_32K,由于睡眠时时设置XTAL->CR0;,会把XTAL_32K给关…

【DM8】外部表

外部表是指不存在于数据库中的表。 通过向达梦数据库定义描述外部表的元数据,可以把一个操作系统文件当成一个只读的数据库表,对外部表将像普通定义的表一样访问。 外部表的数据存储在操作系统文件中,建立外部表的时候,不会产生…

B02、分析GC日志-6.3

1、相关GC日志参数 -verbose:gc 输出gc日志信息,默认输出到标准输出-XX:PrintGC 输出GC日志。类似:-verbose:gc-XX:PrintGCDetails 在发生垃圾回收时打印内存回收详细的日志, 并在进程退出时输出当前内存各区域分配情况-XX:PrintGCTimeStamp…

深度学习-多尺度训练的介绍与应用

一、引言 在当今快速发展的人工智能领域,多尺度训练已经成为了一种至关重要的技术,特别是在处理具有复杂结构和不同尺度特征的数据时。这种技术在许多应用中发挥着关键作用,例如图像识别、自然语言处理和视频分析等。 多尺度训练的定义 多尺…

设计模式(22):解释器模式

解释器 是一种不常用的设计模式用于描述如何构成一个简单的语言解释器,主要用于使用面向对象语言开发的解释器和解释器设计当我们需要开发一种新的语言时,可以考虑使用解释器模式尽量不要使用解释器模式,后期维护会有很大麻烦。在项目中&…

4月9日学习记录

[GXYCTF 2019]禁止套娃 涉及知识点&#xff1a;git泄露&#xff0c;无参数RCE 打开环境&#xff0c;源码什么的都没有&#xff0c;扫描后台看看 扫描发现存在git泄露 用githack下载查看得到一串源码 <?php include "flag.php"; echo "flag在哪里呢&#…

django之ajax

【一】前言 Ajax 异步提交局部刷新 发送请求的方式 浏览器地址栏直接输入url回车 GET请求a标签href属性 GET请求form表单 GET请求/POST请求ajax GET请求/POST请求 ​ AJAX 不是新的编程语言&#xff0c; 而是一种使用先有标准的新方法&#xff08;比如装饰器&#xff09; …

AtCoder ABC347 A-D题解

个人感觉这次D有点难。 比赛链接:ABC347 Problem A: 签到题。 #include <bits/stdc.h> using namespace std; int main(){int N,K;cin>>N>>K;for(int i1;i<N;i){int A;cin>>A;if(A%K0)cout<<A/K;}return 0; } Problem B: 主要考substr的…

unity按路径移动

using System; using System.Collections; using System.Collections.Generic; using UnityEngine;public class FollowPathMove : MonoBehaviour {public Transform[] wayPointArray;[SerializeField] private Transform PathA;//路径点的父物体[SerializeField]private Trans…

紫光展锐T610平台_4G安卓核心板方案定制开发

紫光展锐T610核心板配备Android 11操作系统&#xff0c;采用12nm制程工艺。该处理器CPU由2颗基于Cortex-A75架构的大核心和6颗基于Cortex-A55架构的小核心组成&#xff0c;最高主频为1.8GHz。GPU采用的是614.4MHz的Mali G52&#xff0c;可以流畅播放2400*1080分辨率视频&#x…

MacOS初识SIP——解决快捷指令sh脚本报错Operation not permitted

前言 因为一些原因&#xff0c;设计了一套快捷指令&#xff0c;中间涉及到一个sh脚本的运行&#xff0c;通过快捷指令运行时就会报错&#xff1a;operation not permitted 奇怪的是在快捷指令窗口下运行一切正常&#xff0c;但是从其他地方直接调用&#xff0c;例如通过Comma…

典型新能源汽车热管理系统方案分析

目前行业具有代表性的热管理系统有PTC电加热方案、热泵方案&#xff08;特斯拉八通阀热泵、吉利直接式热泵&#xff09;、威马的柴油加热方案以及以理想为代表的插电式混动车方案。 小鹏P7整车热管理方案分析&#xff08;PTC电加热方案&#xff09; 小鹏P7作为小鹏汽车的第2款…

免费Docker容器服务Koyeb和Zeabur介绍及推荐

想搭建个演示站点仅是演示用。在哪找免费的云服务&#xff1f;还是有很多的。前面介绍过replit&#xff0c;这里介绍下几个提供免费云服务的的PaaS平台Koyeb和Zeabur&#xff0c;Zeabur平台或许是最好的选择。比如把个人的博客免费部署上去&#xff0c;也是个不错的选择呢。 前…

JVM字节码与类的加载——类的加载过程详解

文章目录 1、概述2、加载(Loading)阶段2.1、加载完成的操作2.2、二进制流的获取方式2.3、类模型与Class实例的位置2.4、数组类的加载 3、链接(Linking)阶段3.1、链接阶段之验证(Verification)3.1.1、格式检查3.1.2、字节码的语义检查3.1.3、字节码验证3.1.4、符号引用验证 3.2、…

PVE下安装配置openwrt和ikuai

开端 openwrt 和 ikuai 是比较出名的软路由系统。我最早接触软路由还是因为我的一个学长要改自己家里的网络&#xff0c;使用软路由去控制网络。我听说后便来了兴致&#xff0c;也在我家搞了一套软路由系统。现在我已经做完了&#xff0c;就想着写个文章记录一下。 软路由简介…

GFS部署实验

目录 1、部署环境 ​编辑 2、更改节点名称 3、准备环境 4、磁盘分区&#xff0c;并挂载 5. 做主机映射--/etc/hosts/ 6. 复制脚本文件 7. 执行脚本完成分区 8. 安装客户端软件 1. 安装解压源包 2. 创建gfs 3. 安装 gfs 4. 开启服务 9、 添加节点到存储信任池中 1…

应急响应-拒绝服务钓鱼指南DDOS压力测试邮件反制分析应用日志

知识点 1、CC攻击分析 2、钓鱼邮件分析 3、内网渗透分析 一、演示案例-内网应急-日志分析-爆破 MSSQL-1433 SMB-445 二、演示案例-红队APT-钓鱼邮件-内容&发信人&附件 如何分析邮件安全性&#xff1a; 1、看发信人地址 2、看发信内容信息 3、看发信内容附件 看后…

2011年认证杯SPSSPRO杯数学建模B题(第一阶段)生物多样性的评估全过程文档及程序

2011年认证杯SPSSPRO杯数学建模 B题 生物多样性的评估 原题再现&#xff1a; 2010 年是联合国大会确定的国际生物多样性年。保护地球上的生物多样性已经越来越被人类社会所关注&#xff0c;相关的大规模科研和考察计划也层出不穷。为了更好地建立国际交流与专家间的合作&…