news 2026/8/16 6:29:39

网络缓冲区

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
网络缓冲区

首先说说为什么需要缓冲区?

第一,我们在就行linux网络编程的时候,每次recv/send,数据都不一定能完全接收/发送,因为这里面还会涉及内核的缓冲区容量问题,

第二,我们接收到的数据需要我们的处理函数处理,但是有时候我们的处理函数可能来不及处理,或者处理完的数据太多,我们的socket来不及发送,这些都需要我们在应用层把数据保存起来

网络数据包接收的流程:

1.物理层:数据包到达网卡,网卡接收以太网帧,进行基本验证,比如crc循环冗余验证

2.网卡 DMA 上传到内核内存,网卡利用DMA技术讲数据直接写入网卡驱动预先分配好的接收缓冲区

3,网卡触发硬件中断,网卡经过硬中断告诉cpu接收到数据

4.内核进入中断上下文 → 软中断处理

5.链路层处理(以太网),解析以太网头:目的 MAC、源 MAC、协议类型,判断是ipv4,ipv6还是arp,交给上层函数

6.网络层处理(IP),解析 IP 头:版本、长度、TTL、协议(TCP/UDP/ICMP)、校验和,路由判断:是不是本机接收,是:继续往上,不是:转发(如果开启转发),根据协议(TCP/UDP)交给传输层

7.传输层处理

udp:校验和检查,根据目的端口找到对应 socket,把数据放到 socket 接收队列

tcp:解析 TCP 头:端口、序号、确认号、标志位(SYN/ACK/FIN),查找 socket,做滑动窗口、重传、拥塞控制、时序排序,合法数据放入socket 接收队列

8.应用层:从内核拷贝到用户空间,应用程序调用:read()/recv()/recvfrom(),内核把sk_buff里的数据拷贝到用户态缓冲区,应用拿到数据,流程结束

怎么设计网络缓冲区呢

目前的解决方案有:
1. 定长 buffer
2. ringbuffer
3. chainbuffer

定长buffer:优点是简单,容易实现,缺点是数据挪动频繁,导致开销大,且需要实现扩容机制

ringbuffer:其实就是一个类似队列的一个环形数组,有一个头指针还有尾指针,特点是取数据后不需要挪移数据,但是一样需要实现扩容机制

chainbuffer:

这种设计就比较特别了,不需要大规模挪移数据,而且天生就具有自动扩容的机制。

下面就介绍一下简单的实现,也许用的结构体字段和上面不太一样:

结构体构建:

// 缓冲区节点结构 struct buf_chain_s { struct buf_chain_s *next; // 下一个节点 uint32_t buffer_len; // 节点内存总容量 uint32_t off; // 已写入数据长度 uint32_t is_erad; uint8_t *buffer; // 节点内存指针 }; // 缓冲区管理结构 struct buffer_s { buf_chain_t *first; // 第一个节点 buf_chain_t *last; // 最后一个节点 buf_chain_t *last_read_p; //最后读到数据的点,刚开始就是null uint32_t total_len; // 缓冲区总有效数据长度 uint32_t BUFFER_CHAIN_MAX; // 缓冲区总容量上限 };

我们为调用者暴露了几个方法:

buffer_t *buffer_new(void); int buffer_add(buffer_t *buf, const void *data_in, uint32_t datlen); uint32_t buffer_read(buffer_t *buf, void *data_out, uint32_t datlen); void buffer_destroy(buffer_t *buf)

接下来就是我的整体实现:

#include "buffer.h" // 缓冲区节点结构 struct buf_chain_s { struct buf_chain_s *next; // 下一个节点 uint32_t buffer_len; // 节点内存总容量 uint32_t off; // 已写入数据长度 uint32_t is_erad; uint8_t *buffer; // 节点内存指针 }; // 缓冲区管理结构 struct buffer_s { buf_chain_t *first; // 第一个节点 buf_chain_t *last; // 最后一个节点 buf_chain_t *last_read_p; //最后读到数据的点,刚开始就是null uint32_t total_len; // 缓冲区总有效数据长度 uint32_t BUFFER_CHAIN_MAX; // 缓冲区总容量上限 }; //创建缓冲区 buffer_t *buffer_new() { buffer_t *buf = (buffer_t *)malloc(sizeof(buffer_t)); if(buf == NULL){ return NULL; } buf->first = NULL; buf->last = NULL; buf->last_read_p = NULL; buf->total_len = 0; buf->BUFFER_CHAIN_MAX = DEFAULT_BUFFER_MAX; return buf; } static buf_chain_t *buf_chain_new(uint32_t size){ buf_chain_t *buff_node = (buf_chain_t *)malloc(sizeof(buf_chain_t)); if(buff_node == NULL){ return NULL; } buff_node->buffer_len = size; buff_node->next = NULL; buff_node->off = 0; buff_node->is_erad = 0; buff_node->buffer = (uint8_t *)malloc(size); if(buff_node->buffer == NULL){ free(buff_node); return NULL; } return buff_node; } int buffer_add(buffer_t *buf, const void *data_in, uint32_t datlen){ buf_chain_t *chain; if (datlen > DEFAULT_BUFFER_MAX - buf->total_len) { printf("总容量不够,需要%u, 还剩%u\n", datlen, buf->BUFFER_CHAIN_MAX - buf->total_len); return -1; } const uint8_t *data = (const uint8_t *)data_in; chain = buf->last; if (chain == NULL) { chain = buf_chain_new(DEFAULT_CHAIN_SIZE); if(chain == NULL){ return -1; } buf->last_read_p = chain; buf->first = chain; buf->last = chain; } //循环写入 while(datlen > 0){ //计算最后一个节点是否够用 uint32_t size = chain->buffer_len - chain->off; if(size >= datlen){ //直接复制 memcpy(chain->buffer + chain->off, data, datlen); chain->off += datlen; buf->total_len += datlen; datlen = 0; } else { // 先复制,然后创建节点,继续复制 memcpy(chain->buffer + chain->off, data, size); chain->off += size; buf->total_len += size; data += size; datlen -= size; if(buf->first != NULL && buf->first != buf->last_read_p ){ buf_chain_t *newnode = buf->first; buf->first = newnode->next; newnode->is_erad = 0; newnode->next = NULL; newnode->off = 0; chain->next = newnode; // 链接新节点 chain = newnode; // 切换到新节点 buf->last = newnode; // 更新last指针 printf("复用成功\n"); }else{ buf_chain_t *newnode = buf_chain_new(DEFAULT_CHAIN_SIZE); chain->next = newnode; // 链接新节点 chain = newnode; // 切换到新节点 buf->last = newnode; // 更新last指针 } } } return 0; } uint32_t buffer_read(buffer_t *buf, void *data_out, uint32_t datlen){ if(buf == NULL || data_out == NULL || datlen == 0 || buf->total_len == 0){ return 0; } uint32_t read_len = 0; uint8_t *dest_data = (uint8_t*)data_out; buf_chain_t *chain = buf->first; //找到还没有读取的数据 while(chain){ if(chain->is_erad < chain->off){ break; } chain = chain->next; } while(chain && read_len < datlen){ //当前节点能够读取的数据 uint32_t avail_read_len = chain->off - chain->is_erad; if(avail_read_len > (datlen - read_len)){ memcpy(dest_data + read_len,(chain->buffer) + chain->is_erad,datlen - read_len); buf->last_read_p = chain; buf->total_len -= datlen - read_len; chain->is_erad += datlen - read_len; //以后得更新缓冲区总长度 read_len = datlen; return datlen; }else{ //当前节点没有datlen长度的数据,读取当前节点数据,节点next memcpy(dest_data + read_len,(chain->buffer) + chain->is_erad,avail_read_len); buf->total_len -= avail_read_len; chain->is_erad += avail_read_len; //chain->off += avail_read_len; buf->last_read_p = chain; chain =chain->next; read_len += avail_read_len; } } //以后得更新缓冲区总长度 return read_len; } void buffer_destroy(buffer_t *buf){ if(buf == NULL){ return; } buf_chain_t *chain = buf->first; while(chain != NULL){ buf_chain_t *temp = chain; chain = chain->next; free(temp->buffer); free(temp); } free(buf); return ; } int main() { buffer_t *buffer_ = buffer_new(); char *data = "我真的好想你,在每个雨季!"; char *data1 = "abcdefghijklmnopqrstuvwxyz\n"; char *data2 = "我真的好"; buffer_add(buffer_, data, strlen(data)); buffer_add(buffer_, data1, strlen(data1)); printf("缓冲区总长度:%u\n", buffer_->total_len); uint32_t datlen = buffer_->total_len; char data_out[100] = {0}; buffer_read(buffer_,data_out,12); buffer_add(buffer_, data2, strlen(data2)); buffer_read(buffer_,data_out,datlen); printf("%s",data_out); printf("缓冲区总长度:%u\n", buffer_->total_len); buffer_destroy(buffer_); return 0; }

代码可能有瑕疵,如果你发现了,请在评论区写出来,谢谢;

此外,知识来源于网上学习,感谢零声,还有网络数据包接受过程分析——从网卡到内核协议栈(以Intel e1000 + Linux 4.4为例) | rand0m's blog

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 16:05:55

短视频内容全栈解决方案:从技术突破到商业价值落地

短视频内容全栈解决方案&#xff1a;从技术突破到商业价值落地 【免费下载链接】douyin-downloader 项目地址: https://gitcode.com/GitHub_Trending/do/douyin-downloader 随着数字内容产业的爆发式增长&#xff0c;短视频已成为信息传播的核心载体。据艾瑞咨询2025年…

作者头像 李华
网站建设 2026/7/14 16:05:55

三步解锁飞书文档高效转换:告别格式困扰的终极解决方案

三步解锁飞书文档高效转换&#xff1a;告别格式困扰的终极解决方案 【免费下载链接】feishu2md 一键命令下载飞书文档为 Markdown 项目地址: https://gitcode.com/gh_mirrors/fe/feishu2md 你是否曾遇到飞书文档导出后格式错乱、手动调整耗时费力的情况&#xff1f;是否…

作者头像 李华
网站建设 2026/7/14 16:05:54

Step3-VL-10B部署教程:GPU多实例(MIG)切分支持与资源隔离实测

Step3-VL-10B部署教程&#xff1a;GPU多实例&#xff08;MIG&#xff09;切分支持与资源隔离实测 1. 引言&#xff1a;当大模型遇见多用户 想象一下这个场景&#xff1a;你有一台配备了顶级GPU的服务器&#xff0c;上面部署了强大的Step3-VL-10B视觉语言模型。这个模型能看懂…

作者头像 李华
网站建设 2026/7/14 16:05:54

Phi-3-vision-128k-instruct商业应用:金融财报截图结构化提取方案

Phi-3-vision-128k-instruct商业应用&#xff1a;金融财报截图结构化提取方案 1. 金融财报处理的行业痛点 在金融分析领域&#xff0c;每天需要处理海量的财报截图和PDF文档。传统的人工录入方式面临三大核心挑战&#xff1a; 效率瓶颈&#xff1a;分析师平均需要15-20分钟处…

作者头像 李华
网站建设 2026/7/14 16:05:56

从 SQL 语句到数据库操作

1. SQL 语句分类数据定义语言 DDL &#xff1a; 用于定义或修改数据库中的结构&#xff0c;如&#xff1a;创建、修改、删除数据库对象。create、drop alter数据操作语言 DML &#xff1a; 用于添加、删除、更新数据库中的数据。select、insert alter、drop数据控制语言 DCL &a…

作者头像 李华
网站建设 2026/7/14 16:05:53

vLLM部署大模型时,FP16和AWQ量化到底该怎么选?实测对比告诉你答案

vLLM部署大模型&#xff1a;FP16与AWQ量化的实战选择指南 当你在深夜的办公室里盯着服务器监控面板&#xff0c;发现显存占用即将爆表而推理请求还在不断涌入时&#xff0c;选择正确的模型量化策略就成了生死攸关的决定。作为经历过数十次大模型部署的老兵&#xff0c;我清楚地…

作者头像 李华