基于多反应堆的高并发服务器【C/C++/Reactor】(中)Buffer的创建和销毁、扩容、写入数据

TcpConnection:封装的就是建立连接之后得到的用于通信的文件描述符,然后基于这个文件描述符,在发送数据的时候,需要把数据先写入到一块内存里边,然后再把这块内存里边的数据发送给客户端,除了发送数据,剩下的就是接收数据。接收数据,把收到的数据先存储到一块内存里边。也就意味着,无论是发送数据还是接收数据,都需要一块内存。并且这块内存是需要使用者自己去创建的。所以就可以把这块内存做封装成Buffer。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>学习笔记>>>>>>>>>>>>>>>>>>>>>>>>>>>>

1.文件描述符与数据发送:

  • 在发送数据时,需要先将数据写入内存缓冲区(buffer)。
  • 内存缓冲区可以通过封装成一个Buffer结构体来实现
  • Buffer结构体中包含一个指向内存的指针(data)、内存总大小(capacity)、读数据位置(readPos)和写数据位置(writePos)等成员

2.Buffer结构体及其成员说明:

  • 指针:指向内存地址(data)
  • 总大小:内存块的字节数(capacity)
  • 读位置:当前读取数据的位置(readPos)
  • 写位置:当前写入数据的位置(writePos)

3.Buffer API函数:

  • 提供一系列API函数,以便对buffer中的内存进行操作
  • 主要操作包括初始化buffer和进行读写操作

4.初始化Buffer:

  • 需要为buffer申请指定大小的堆内存
  • 使用malloc函数申请堆内存,并将内存地址返回给调用者
  • 初始化buffer结构体中的成员,包括data指针、容量、读位置和写位置
  • data指针需要指向一个有效的内存块,因此需要再次申请内存
  • 使用memset函数将data指针指向的内存块初始化为零
  • 返回buffer指针给调用者

>>>>>>>>>>>>>>>>>>>>>>>>>>>>Buffer的创建和销毁>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  • Buffer.h 
struct Buffer {// 指向内存的指针char* data;int capacity;int readPos;int writePos;
}

 (一)Buffer的初始化

// 初始化
struct Buffer* bufferInit(int size);
// 初始化
struct Buffer* bufferInit(int size) {struct Buffer* buffer = (struct Buffer*)malloc(sizeof(struct Buffer));if(buffer!=NULL) {buffer->data = (char*)malloc(sizeof(char) * size);buffer->capacity = size;buffer->readPos = buffer->writePos = 0;memset(buffer->data, 0, size);}return buffer;
}

 (二)Buffer的销毁

// 销毁
void bufferDestroy(struct Buffer* buf);
// 销毁
void bufferDestroy(struct Buffer* buf) {if(buf!=NULL) {if(buf->data!=NULL) { // buf->data指向有效的堆内存free(buf->data); // 释放}}free(buf);
}


>>>>>>>>>>>>>>>>>>>>>>>>>>>>Buffer的扩容>>>>>>>>>>>>>>>>>>>>>>>>>>> 

(一)readPoswritePos 相对位置发生变化的三种情况:

(1)Buffer初始时 - 未写入任何数据

(2)Buffer - 写入了部分数据

  • 剩余的可写的内存容量 = 可写数据内存大小
// 得到剩余的可写的内存容量
int bufferWriteableSize(struct Buffer* buf);
// 得到剩余的可写的内存容量
int bufferWriteableSize(struct Buffer* buf) {return buf->capacity - buf->writePos;
}

 (3)Buffer - 写入了部分数据并读出了部分数据

  • 计算已写数据内存(未读)的大小 
// 已写数据内存(未读)的大小 --- 得到剩余的可读的内存容量
int bufferReadableSize(struct Buffer* buf);
// 已写数据内存(未读)的大小 --- 得到剩余的可读的内存容量
int bufferReadableSize(struct Buffer* buf) {return buf->writePos - buf->readPos;
}

对于内存数据已读的区域的数据为无效数据,此处的无效指的是内存数据,由于数据已经被读了出来,故这里边的数据已经无效了。对于这个图来说,剩余的可用内存块一共有多大呢? 

  • 剩余的可写的内存容量 = 内存数据已读大小 + 可写数据内存大小

但这个是理论值,因为这两块内存不是连续的,故即使空间够存储,但是不连续的存放会导致读写麻烦。此时的解决方案是:移动内存实现合并内存

(1)先获取已写数据内存(未读)这块内存的大小,将值赋给readableSize

// 得到已写但未读的内存大小
int readableSize = bufferReadableSize(buf);

(2)然后把这块内存的数据拷贝到前面去,这就实现了合并

// 移动内存实现合并
memcpy(buf->data, buf->data + buf->readPos, readableSize);

 (3)更新位置

// 更新位置
buf->readPos = 0;
buf->writePos = readableSize;

(二)Buffer扩容

当往buffer中写入数据时,如果剩余的内存不足以容纳新的数据,需要进行扩容。有三种情况需要考虑:     

  1. 剩余的可写的内存容量够用- 不需要扩容
  2. 内存需要合并才够用 - 不需要扩容
  3. 内存不够用 - 需要扩容
// 扩容
void bufferExtendRoom(struct Buffer* buf, int size);
// 扩容
void bufferExtendRoom(struct Buffer* buf, int size) {// 1.内存够用 - 不需要扩容if(bufferWriteableSize(buf)>= size) {return;}// 2.内存需要合并才够用 - 不需要扩容// 剩余的可写的内存 +  已读的内存 >= sizeelse if(bufferWriteableSize(buf) + bufferReadableSize(buf) >= size) {// 得到已写但未读的内存大小int readableSize = bufferReadableSize(buf);// 移动内存实现合并memcpy(buf->data, buf->data + buf->readPos, readableSize);// 更新位置buf->readPos = 0;buf->writePos = readableSize;}// 3.内存不够用 - 需要扩容else{void* temp = realloc(buf->data, buf->capacity + size);if(temp ==NULL) {return;// 失败了}  memset(temp + buf->capacity, 0, size);// 只需要对拓展出来的大小为size的内存块进行初始化就可以了// 更新数据buf->data = temp;buf->capacity += size;}
}

>>>>>>>>>>>>>>>>>>>>>>>>>>>>往Buffer里写入数据>>>>>>>>>>>>>>>>>>>>>>>>>>> 

(1)直接写  

// 写内存 1.直接写 
int bufferAppendData(struct Buffer* buf, const char* data, int size); int bufferAppendString(struct Buffer* buf, const char* data); 
// 写内存 1.直接写 
int bufferAppendData(struct Buffer* buf, const char* data, int size) {// 判断传入的buf是否为空,data指针指向的是否为有效内存,以及数据大小是否大于零if(buf == NULL || data == NULL || size <= 0) {return -1;}// 扩容(试探性的)bufferExtendRoom(buf,size);// 数据拷贝memcpy(buf->data + buf->writePos, data, size);// 更新写位置buf->writePos += size;return 0;
}int bufferAppendString(struct Buffer* buf, const char* data) {int size = strlen(data);int ret = bufferAppendData(buf, data, size);return ret;
}

实现bufferAppendData函数重点:

1. 实现写内存函数时,需要判断传入的buf是否为空,data指针指向的是否为有效内存,以及数据大小是否大于零

2. 在写数据之前,需要进行内存扩容(试探性的,可能剩余的可写容量就够写入那就不必扩容)

3. 写数据时,需要从上次写入的writePos位置开始

4. 数据写入完成后,需要更新writePos的位置

总结:在实现bufferAppendData函数时,需要考虑如何处理内存的写入和接收数据的情况。在写数据之前,可能需要进行内存扩容以确保有足够的空间。写数据时,需要从上次写入的writePos位置开始。完成写入后,需要再次更新writePos的位置。

(2)接收套接字数据

#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
struct iovec {void  *iov_base;    /* Starting address */size_t iov_len;     /* Number of bytes to transfer */
};功能:readv函数从文件描述符(包括TCP Socket)中读取数据,并将读取的数据存储到指定的多个缓冲区中。
-> 成功时返回接收的字节数,失败时返回-1filedes 传递接收数据的文件(套接字)描述符
iov 包含数据保存位置和大小的iovec结构体数组的地址值
iovcnt 第二个参数中数组的长度fd:要读取数据的文件描述符,可以是TCP Socket。
iov:存储读取数据的多个缓冲区的数组。
iovcnt:缓冲区数组的长度。
返回值:成功时返回实际读取的字节数,失败时返回-1,并设置errno变量来指示错误的原因。

read/recv/readv  在接收数据的时候,

  • read/recv 只能指定一个数组
  • readv 能指定多个数组(也就是说第一个用完,用第二个...)

readv函数可以一次接收多个缓冲区中的数据,并在内核中减少了多次系统调用的开销。

// 写内存 2.接收套接字数据
int bufferSocketRead(struct Buffer* buf,int fd);
  • bufferSocketRead函数实现功能:当调用这个bufferSocketRead函数之后,一共接收到了多少个字节
  • bufferSocketRead函数具体细节:在这个函数里边,通过malloc申请了一块临时的堆内存(tmpbuf),这个堆内存是用来接收套接字数据的。当buf里边的数组容量不够了,那么就使用这块临时内存来存储数据,还需要把tmpbuf这块堆内存里边的数据再次写入到buf中。当用完了之后,需要释放内存。
// 写内存 2.接收套接字数据
int bufferSocketRead(struct Buffer* buf,int fd) {struct iovec vec[2]; // 根据自己的实际需求来// 初始化数组元素int writeableSize = bufferWriteableSize(buf); // 得到剩余的可写的内存容量// 0号数组里的指针指向buf里边的数组,记得 要加writePos,防止覆盖数据vec[0].iov_base = buf->data + buf->writePos;vec[0].iov_len = writeableSize;char* tmpbuf = (char*)malloc(40960); // 申请40k堆内存vec[1].iov_base = buf->data + buf->writePos;vec[1].iov_len = 40960;// 至此,结构体vec的两个元素分别初始化完之后就可以调用接收数据的函数了int result = readv(fd, vec, 2);// 表示通过调用readv函数一共接收了多少个字节if(result == -1) {return -1;// 失败了}else if (result <= writeableSize) { // 说明在接收数据的时候,全部的数据都被写入到vec[0]对应的数组里边去了,全部写入到// buf对应的数组里边去了,直接移动writePos就好buf->writePos += result;}else {// 进入这里,说明buf里边的那块内存是不够用的,// 所以数据就被写入到我们申请的40k堆内存里边,还需要把tmpbuf这块// 堆内存里边的数据再次写入到buf中。// 先进行内存的扩展,再进行内存的拷贝,可调用bufferAppendData函数// 注意一个细节:在调用bufferAppendData函数之前,通过调用readv函数// 把数据写进了buf,但是buf->writePos没有被更新,故在调用bufferAppendData函数// 之前,需要先更新buf->writePosbuf->writePos = buf->capacity; // 需要先更新buf->writePosbufferAppendData(buf, tmpbuf, result - writeableSize);}free(tmpbuf);return result;
}

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

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

相关文章

基于综合特征的细菌噬菌体宿主预测工具iPHoP (Integrated Phage HOst Prediction)的介绍以及使用方法详细流程

介绍 iPHoP&#xff08;Integrated Phage HOst Prediction&#xff09;是一种基于综合特征的细菌噬菌体宿主预测方法。它是通过整合基因组序列、蛋白质序列和宿主基因组信息来预测细菌噬菌体的宿主范围。 iPHoP的预测过程分为三个步骤&#xff1a;特征提取、特征选择和宿主预…

【Spring实战】21 Spring Data REST 常用功能详细介绍

文章目录 1. 资源导出&#xff08;Resource Exporting&#xff09;2. 查询方法&#xff08;Query Methods&#xff09;3. 分页和排序&#xff08;Pagination and Sorting&#xff09;4. 关联关系&#xff08;Associations&#xff09;5. 事件&#xff08;Events&#xff09;6. …

“华为杯”杭州电子科技大学2023新生编程大赛---树

题目链接 Problem Description 给定一棵包含 n 个节点的带边权的树&#xff0c;树是一个无环的无向联通图。定义 xordist(u,v) 为节点 u 到 v 的简单路径上所有边权值的异或和。 有 q 次询问&#xff0c;每次给出 l r x&#xff0c;求 ∑rilxordist(i,x) 的值。 Input 测试…

JVM之内存模型带参数

Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里)&#xff1a; java ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize256M ‐XX:MaxMetaspaceSize256M ‐jar xxxxxx.jar-Xss&#xff1a;每个线程的栈大小 -Xms&#xff1a;设置…

关于“Python”的核心知识点整理大全61

目录 注意 20.1.4 使用 jumbotron 设置主页的样式 index.html 20.1.5 设置登录页面的样式 login.html 20.1.6 设置 new_topic 页面的样式 new_topic.html 20.1.7 设置 topics 页面的样式 topics.html 元素&#xff0c;让它们在页面上显得大些&#xff08;见2&#xf…

imgaug库指南(三):从入门到精通的【图像增强】之旅

引言 在深度学习和计算机视觉的世界里&#xff0c;数据是模型训练的基石&#xff0c;其质量与数量直接影响着模型的性能。然而&#xff0c;获取大量高质量的标注数据往往需要耗费大量的时间和资源。正因如此&#xff0c;数据增强技术应运而生&#xff0c;成为了解决这一问题的…

prometheus与zabbix监控的对比介绍

一、普米与zabbix基本介绍 1、prometheus介绍 Prometheus的基本原理是Prometheus Server通过HTTP周期性抓取被监控组件的监控数据&#xff0c;任意组件只要提供对应的HTTP接口并且符合Prometheus定义的数据格式&#xff0c;就可以接入Prometheus监控。 工作流程大致分为收集数…

嵌入式Linux之MX6ULL裸机开发学习笔记(汇编LED灯点亮)

汇编LED驱动实验 1.驱动编写 首先创建在vscode上创建工作区&#xff0c;创建led.s汇编文件&#xff0c;然后编写以下程序 .global _start 全局标号 _start: /* 使能所有外设时钟 */ ldr r0,0x020c4068 CCGR0 ldr r1,0xffffffff 要向CCGR0写入的数据 str r1,[r0] 将0xff…

优化企业运营,深入探索SAP库存管理解决方案

SAP库存管理是SAP提供的一款领先的企业库存管理解决方案。它致力于帮助企业实现对库存的全面掌控&#xff0c;优化供应链管理&#xff0c;降低库存成本&#xff0c;提高客户满意度。这个功能强大的系统为企业提供了丰富的仓储管理功能&#xff0c;如库存盘点、物料追踪、供应商…

【LeetCode】150. 逆波兰表达式求值(ASCII码)

今日学习的文章链接和视频链接 leetcode题目地址&#xff1a;150. 逆波兰表达式求值 代码随想录题解地址&#xff1a;代码随想录 题目简介 即将后缀表达式转换成中缀表达式并计算。 给你一个字符串数组 tokens &#xff0c;表示一个根据 逆波兰表示法 表示的算术表达式。 …

【编译原理】期末预习PPT前四章笔记II

看了看学校的ppt&#xff0c;记的比较随意O.o 因为我的考试范围里边没有简答所以概念什么的没怎么记 没有简答只有选择真是太好了嘿嘿嘿 目录 I. 概述&#xff08;好多字。。&#xff09; 一、高级语言的分类 1、体裁 2、执行方式 二、各种语言的执行方式 三、编译程序…

读算法霸权笔记11_微目标

1. 脸书 1.1. 一份请愿书属于脸书了&#xff0c;而社交网络的算法会对如何最大限度地利用这份请愿书做出判断 1.1.1. 脸书的算法在决定谁能看到我的请愿书时会把所有因素都考虑在内 1.2. 通过改变信息推送的方式&#xff0c;脸书研究了我们…

智能分析网关V4智慧港口码头可视化视频智能监管方案

一、需求背景 近年来&#xff0c;水利港口码头正在进行智能化建设&#xff0c;现场管理已经是重中之重。港口作为货物、集装箱堆放及中转机构&#xff0c;具有昼夜不歇、天气多变、环境恶劣等特性&#xff0c;安全保卫工作显得更加重要。港口码头的巡检现场如何高效、快捷地对…

5G工业物联网网关:连接未来的智能工业

在当今数字化时代&#xff0c;工业物联网正迅速崛起&#xff0c;并引领着全球工业的数字转型。而5G工业物联网网关作为实现IIoT的关键基础设施&#xff0c;在连接未来的智能工业中发挥着举足轻重的作用。 什么是5G工业物联网网关 5G工业物联网网关是连接工业设备和5G网络的关键…

(湖科大教书匠)计算机网络微课堂(下)

第四章、网络层 网络层概述 网络层主要任务是实习网络互连&#xff0c;进而实现数据包在各网络之间的传输 因特网使用TCP/IP协议栈 由于TCP/IP协议栈的网络层使用网际协议IP&#xff0c;是整个协议栈的核心协议&#xff0c;因此TCP/IP协议栈的网络层常称为网际层 网络层提供…

SpringBoot整合sentinel

1、引入依赖 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> 2、 配置文件添加 spring:cloud:sentinel:transport:dashboard: ip:8858 项目重启&#x…

【mujoco】Ubuntu20.04中解决mujoco报错raise error.MujocoDependencyError

【mujoco】Ubuntu20.04中解决mujoco报错raise error.MujocoDependencyError 文章目录 【mujoco】Ubuntu20.04中解决mujoco报错raise error.MujocoDependencyError1. 报错的具体情况2. 解决过程3. 其他问题3.1 ModuleNotFoundError: No module named OpenGL3.2 ModuleNotFoundEr…

第84讲:基于各种场景使用mysqldump逻辑备份数据库

文章目录 1.mysqldump备份工具的语法格式2.使用mysqldump进行全库备份3.备份单个库或者多个库的数据4.备份某个库下的单表或者多表的数据5.mysqldump备份数据库时必加的一些参数5.1.基本参数5.2.核心参数 6.mysqldump备份数据库时的一些其他参数 1.mysqldump备份工具的语法格式…

Docker Compose--部署SpringBoot项目--实战

原文网址&#xff1a;Docker Compose--部署SpringBoot项目--实战-CSDN博客 简介 本文用实战介绍Docker Compose部署SpringBoot项目。 1.创建SpringBoot项目 Controller package com.knife.example.controller;import io.swagger.annotations.Api; import io.swagger.annot…

HTTP基础知识总结

目录 一、什么是HTTP&#xff1f; 二、与HTTP有关的协议 三、HTTP请求特征 四、HTTP组成格式 五、HTTP标头 1.通用标头 2.实体标头 3.请求标头 4.响应标头 六、HTTP状态码分类 我们在日常测试过程中&#xff0c;也可以通过浏览器F12简单定位是前端问题还是后端问题&a…