004 仿muduo实现高性能服务器组件_Buffer模块与Socket模块的实现
🌈个人主页:Fan_558
🔥 系列专栏:仿muduo
🌹关注我💪🏻带你学更多知识
文章目录
- 前言
- Buffer模块
- Socket模块
- 小结
前言
这章将会向你介绍仿muduo高性能服务器组件的buffer模块与socket模块的实现
Buffer模块
设计思想
实现思想:
1、实现缓冲区得有一块内存空间,采用vector,string字符串的操作遇到’\0’就停止了,网络操作中什么样的数据都有,'\0’可能也有,string大部分的操作都是字符串操作,所以不太行
2、记录当前的读取数据位置与当前的写入数据位置,避免每次写入数据需要重新遍历数组找写入读入位置
3、考虑整体缓冲区空闲空间是否足够 (因为读位置也会向后偏移,前边有可能会有空间) 足够:则将数据(读位置开始)移动到起始位置即可
不够:扩容,从当前写位置开始扩容足够大小 数据一旦写入成功,当前写位置就要向后偏移
4、读取数据/写入数据
当前的读取/写入位置指向哪里,就从哪里开始读取/写入,前提是有数据可读/有空间可写,读取/写入完数据,读偏移/写偏移向后偏移
为了方便查阅
代码如下:
class Buffer{
private:std::vector<char> _buffer; //使用vector进行内存空间管理uint64_t _reader_idx; //读偏移uint64_t _writer_idx; //写偏移
public:Buffer():_reader_idx(0), _writer_idx(0) ,_buffer(BUFFER_SIZE) {}//获取_buffer起始元素的地址char* begin() {return &*_buffer.begin();}//获取当前写入起始地址(_buffer的空间起始地址,加上写偏移量char* WritePos() { return begin() + _writer_idx; }//获取当前读取起始地址(_buffer的空间起始地址,加上读偏移量char* ReadPos() { return begin() + _reader_idx; }//获取缓冲区末尾空闲空闲大小--写偏移之后的空闲空间uint64_t TailIdleSize() {return _buffer.size() - _writer_idx; }//获取缓冲区起始地址空闲空间大小--读偏移之前的空闲空间uint64_t HeadIdleSize() {return _reader_idx; }//获取可读数据大小uint64_t ReadAbleSize() {return _writer_idx - _reader_idx; }//读取数据后,将读偏移向后移动void MoveReadOffest(uint64_t len) { //向后移动的大小,必须小于可读数据大小assert(len <= ReadAbleSize());_reader_idx += len; }//写入数据后,将写偏移向后移动void MoveWriteOffest(uint64_t len) { _writer_idx += len; }//确保可写空间足够(整体空闲空间够了就移动数据,否则就扩容)void EnsureWriteSpace(uint64_t len){//如果末尾空闲空间大小足够,直接返回if(len < TailIdleSize()) return;//如果不够,判断加上起始位置的空闲空间大小是否足够,够了就将可读数据移动到起始位置else if(len <= HeadIdleSize() + TailIdleSize()) {uint64_t sz = ReadAbleSize(); //可读数据大小_reader_idx = 0; //更新读偏移_writer_idx = sz; //更新写偏移return;}//总体空间不够,则需要扩容,不移动数据,直接给写偏移之后扩容足够空间即可else _buffer.resize(_writer_idx + len);}//写入数据void Write(const void* data, uint64_t len){//保证是否有足够空间EnsureWriteSpace(len);const char* d = (const char* )data;//拷贝数据到buffer当中std::copy(d, d + len, WritePos());}void WriteAndPush(const void* data, uint64_t len){Write(data, len);MoveWriteOffest(len);}//写入一个字符串void WriteString(const std::string &data){Write(data.c_str(), data.size());}//向buffer中写入一个字符串并向后移动writevoid WriteStringAndPush(const std::string &data){WriteString(data);MoveWriteOffest(data.size());}//把一个buffer类型的数据写入void WriteBuffer(Buffer &data){Write(data.ReadPos(), data.ReadAbleSize());}//向buffer中写入一个并向后移动writevoid WriteBufferAndPush(Buffer &data){WriteBuffer(data);MoveWriteOffest(data.ReadAbleSize());}//读取数据void Read(void* buf, uint64_t len){assert(len <= ReadAbleSize());//保持参数类型一致std::copy(ReadPos(), ReadPos() + len, (char*)buf);}void ReadAndPop(void* buf, uint64_t len){Read(buf, len);MoveReadOffest(len);}//把读取的数据当作一个string返回 std::string ReadAsString (uint64_t len){assert(len <= ReadAbleSize());std::string str;str.resize(len);//从缓冲区中读取长度为len的数据,并将其存储到字符串str的内存地址开始处的位置Read(&str[0], len);return str;}//读取一个string并向后移动(确保下一次不会重复读取)std::string ReadAsStringAndPop(uint64_t len){assert(len <= ReadAbleSize());std::string str = ReadAsString(len);MoveReadOffest(len);return str;}/*由于后面我们的高并发服务器会支持应用层协议的HTTP,而在HTTP协议中通常就是读取一行的数据,因为请求行和请求报头以及响应行和响应报头都是以\r\n作为分隔符的,都是一行行的数据所以我们的缓冲区也提供一个查找换行字符的位置*/char* FindCRLF(){//在可读数据范围内查找第一个出现的换行符的位置char* res = (char*)memchr(ReadPos(), '\n', ReadAbleSize());return res;}//获取一行数据std::string Getline(){char* pos = FindCRLF();if(pos == nullptr) return "";/*将换行符\n前的数据读出,+1:包括换行符(不然的话下一次再查找,换行符就在开头) */return ReadAsString(pos - ReadPos() + 1); }//读出一行数据后,将读偏移向后移std::string GetLineAndPop(){std::string str = Getline();MoveReadOffest(str.size());return str;}//清空缓冲区void clear(){//只需要将偏移量归零_writer_idx = _reader_idx = 0;}
};
Socket模块
设计思想:
在该模块当中除了对socket套接字原有的操作进行封装,还提供了直接创建服务端和客户端连接的接口
为了方便查阅
代码如下
#define MAX_LISTEN 1024
class Socket{private:int _sockfd;public:Socket():_sockfd(-1){}Socket(int fd):_sockfd(fd){}//关闭套接字~Socket() { Close(); }int Fd(){return _sockfd;}//创建套接字bool Create(){//int socket(int domain, int type, int protocol) AF_INET: 表示使用ipv4地址族 SOCK_STREM: 表示创建面向连接的套接字类(TCP) IPPROTO_TCP: 表示使用TCP协议_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if(_sockfd < 0){ERR_LOG("CREATE SOCKET FAILEDQ!");return false;}return true;}//绑定地址信息bool Bind(const std::string &ip, uint16_t port){struct sockaddr_in addr;addr.sin_family = AF_INET; //ipv4地址域类型addr.sin_port = htons(port); //将端口号通过主机转网络字节序addr.sin_addr.s_addr = inet_addr(ip.c_str()); //将IP地址转化为网络字节序的32位ipv4地址socklen_t len = sizeof(struct sockaddr_in);//int bind(int socket, const struct sockaddr *addr. socklen_t addrlen);int ret = bind(_sockfd, (struct sockaddr*)&addr, len);if(ret < 0){ERR_LOG("BIND ADDRESS FAILEDQ!");return false;}return true;}//开始监听bool Listen(int backlog = MAX_LISTEN){int ret = listen(_sockfd, backlog);if(ret < 0){ERR_LOG("SOCKET LISTEN FAILED!");return false;}return true;}//向服务器发起连接(传入服务器的ip和端口信息)bool Connect(const std::string &ip, uint16_t port){//int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip.c_str());socklen_t len = sizeof(struct sockaddr_in);int ret = connect(_sockfd, (struct sockaddr*)&addr, len);if(ret < 0){ERR_LOG("CONNECT SERVER FAILEDQ!");return false;}return true;}//监听有新连接后,获取新连接(返回一个文件描述符)int Accept() {int newfd = accept(_sockfd, nullptr, nullptr);if(newfd < 0){ERR_LOG("SOCKET ACCEPT FAILED!");return -1;}return newfd;}//接收数据(ssize_t为有符号整数,size_t无符号整数,默认0为阻塞操作)ssize_t Recv(void* buf, size_t len, int flag = 0){ssize_t ret = recv(_sockfd, buf, len, flag);if(ret <= 0){//EAGAIN 当前socket的接收缓冲区中没有数据了,在非阻塞的情况下才会有这个错误//EINTR 当前socket的阻塞等待被信号打断了if(errno == EAGAIN || errno == EINTR)return 0;else{ERR_LOG("SOCKET RECV FAILED");return -1;}}return ret; //返回实际接收的数据长度}ssize_t NonBlockRecv(void* buf, size_t len){return Recv(buf, len, MSG_DONTWAIT); // MSG_DONTWAIT 表示当前接收为非阻塞}//发送数据ssize_t Send(const void* buf, size_t len, int flag = 0){ssize_t ret = send(_sockfd, buf, len, flag);if(ret < 0){if(errno == EAGAIN || errno == EINTR){return 0;}ERR_LOG("SOCKET RECV FAILED");return -1;}return ret; //返回实际发送的数据长度}ssize_t NonBlockSend(void* buf, size_t len){Send(buf, len, MSG_DONTWAIT); // MSG_DONTWAIT 表示当前接收为非阻塞}//关闭套接字void Close(){if(_sockfd != -1){close(_sockfd);_sockfd = -1;}}//创建一个服务端连接bool CreateServer(uint16_t port, const std::string &ip = "0.0.0.0", bool block_flag = false){if(Create()==false) return false;//是否启动非阻塞if(block_flag) NonBlock();if(Bind(ip, port) == false) return false;if(Listen() == false) return false;ReuseAddress();return true;}//创建一个客户端连接bool CreateClient(uint16_t port, const std::string &ip){if(Create() == false) return false;if(Connect(ip, port) == false) return false;return true;}//设置套接字选项---开启地址端口重用void ReuseAddress(){// int setsockopt(int fd, int leve, int optname, void *val, int vallen)int val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(int));val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEPORT, (void*)&val, sizeof(int));}//设置套接字阻塞属性---设置为非阻塞void NonBlock(){//int fcntl(int fd, int cmd, ... /* arg */ );int flag = fcntl(_sockfd, F_GETFL, 0);fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);}
};
小结
今日的项目分享就到这里啦,下一篇将会向你介绍Channel与Poller模块
相关文章:

004 仿muduo实现高性能服务器组件_Buffer模块与Socket模块的实现
🌈个人主页:Fan_558 🔥 系列专栏:仿muduo 🌹关注我💪🏻带你学更多知识 文章目录 前言Buffer模块Socket模块 小结 前言 这章将会向你介绍仿muduo高性能服务器组件的buffer模块与socket模块的实…...

研发效能DevOps: Ubuntu 部署 JFrog 制品库
目录 一、实验 1.环境 2.Ubuntu 部署 JFrog 制品库 3.Ubuntu 部署 postgresql数据库 4.Ubuntu 部署 Xray 5. 使用JFrog 增删项目 二、问题 1.Ubuntu 如何通过apt方式部署 JFrog 制品库 2.Ubuntu 如何通过docker方式部署 JFrog 制品库 3.安装jdk报错 4.安装JFrog Ar…...

hadoop学习笔记
hadoop集群搭建 hadoop摘要 Hadoop 是一个开源的分布式存储和计算框架,旨在处理大规模数据集并提供高可靠性、高性能的数据处理能力。它主要包括以下几个核心组件: Hadoop 分布式文件系统(HDFS):HDFS 是 Hadoop 的分布…...
使用dockerfile快速构建一个带ssh的docker镜像
不多说先给代码 FROM ubuntu:22.04 # 基础镜像 可替换为其他镜像 USER root RUN echo root:root |chpasswd RUN apt-get update -y \&& apt-get install -y git wget curl RUN apt-get install -y openssh-server vim && apt clean \&& rm -rf /tmp/…...

linux部署运维1——centos7.9离线安装部署涛思taos2.6时序数据库TDengine
在实际项目开发过程中,并非一直都使用关系型数据库,对于工业互联网类型的项目来说,时序型数据库也是很重要的一种,因此掌握时序数据库的安装配置也是必要的技能,不过对于有关系型数据库使用的开发工作者来说࿰…...

Linux shell编程学习笔记51: cat /proc/cpuinfo:查看CPU详细信息
0 前言 2024年的网络安全检查又开始了,对于使用基于Linux的国产电脑,我们可以编写一个脚本来收集系统的有关信息。对于中央处理器CPU比如,我们可以使用cat /proc/cpuinfo命令来收集中央处理器CPU的信息。 1. /proc/cpuinfo 保存了系统的cpu…...

Ps:调整画笔工具
调整画笔工具 Adjustment Brush Tool可以将选区、创建蒙版和应用调整的传统工作流程合并为一个步骤,简化了对图像进行非破坏性局部调整的操作。 快捷键:B 调整画笔工具是 Photoshop 2024 年 5 月版(25.9 版)新增的工具。 ◆ ◆ …...

香橙派 AIpro上手体验并验证车道线识别算法
香橙派 AIpro上手体验并验证车道线识别算法 1.前言 最近入手了一块香橙派AIpro,体验了一下,感觉还不错,在这里分享给大家,大家可以做个参考。 2.开箱 整套产品包含一块主板、一个电源插头和一条双端Type-C的数据线,…...

为啥装了erlang,还报错erl: command not found?
转载说明:如果您喜欢这篇文章并打算转载它,请私信作者取得授权。感谢您喜爱本文,请文明转载,谢谢。 问题背景: 在一台不通外网的服务器上装rabbitmq,然后在启动的时候,遇到了报错 “/usr/lib/…...

容器技术基础理论与常用命令:必知必会,效率翻倍!
如何利用容器技术提升你的工作效率?掌握基础理论和常用命令是必不可少的,本文将为你全面介绍容器技术,并教你必知必会的技能,让你工作、学习效率翻倍,对于网络安全工作者也是必不可少的技能! 0. 引言 学习…...

ChatGPT Edu版本来啦:支持GPT-4o、自定义GPT、数据分析等
5月31日,OpenAI在官网宣布,推出ChatGPT Edu版本。 据悉,这是一个专门为大学校园提供的ChatGTP,支持GPT-4o、网络搜索、自定义GPT、数据分析、代码生成等功能,可以极大提升学生、老师的学习质量和教学效率。 目前&…...
Spark RDD案例
Apache Spark中的RDD(Resilient Distributed Dataset)是一个不可变、分布式对象集合,它允许用户在大型集群上执行并行操作。虽然RDD在Spark的早期版本中非常核心,但随着DataFrame和Dataset的引入,RDD的使用在某些场景下…...
【线性表 - 数组和矩阵】
数组是一种连续存储线性结构,元素类型相同,大小相等,数组是多维的,通过使用整型索引值来访问他们的元素,数组尺寸不能改变。 知识点数组与矩阵相关题目 # 知识点 数组的优点: 存取速度快 数组的缺点: 事先必须知道…...
Springboot 开发 -- 跨域问题技术详解
一、跨域的概念 跨域访问问题指的是在客户端浏览器中,由于安全策略的限制,不允许从一个源(域名、协议、端口)直接访问另一个源的资源。当浏览器发起一个跨域请求时,会被浏览器拦截,并阻止数据的传输。 这…...
【Qt】之【项目】整理可参考学习的git项目链接(持续更新)
Tcp 通信相关 IM即时通讯设计 高并发聊天服务:服务器 qt客户端(附源码) - DeRoy - 博客园 未使用protobuf通讯协议格式 github:GitHub - ADeRoy/chat_room: IM即时通讯设计 高并发聊天服务:服务器 qt客户端 QT编…...
2024年5月个人工作生活总结
本文为 2024年5月工作生活总结。 研发编码 golang 多个defer函数执行顺序 golang 函数中如有多个defer,倒序执行。示例代码: func foo() {defer func() {fmt.Println("111")}()defer func() {fmt.Println("2222")}()defer func()…...

Kafka Java API
1、增加依赖 <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><version>1.0.0</version> </dependency>2、三个案例 案例1:生产数据 import org.apache.kafka.clients.p…...
pushd: not found
解决方法: pushd 比 cd 命令更高效的切换命令,非默认,可在脚本开头添加: #! /bin/bash ubuntu 编译时出现/bin/sh: 1: pushd: not found的问题-CSDN博客...

【第十三节】C++控制台版本坦克大战小游戏
目录 一、游戏简介 1.1 游戏概述 1.2 知识点应用 1.3 实现功能 1.4 开发环境 二、项目设计 2.1 类的设计 2.2 各类功能 三、程序运行截图 3.1 游戏主菜单 3.2 游戏进行中 3.3 双人作战 3.4 编辑地图 一、游戏简介 1.1 游戏概述 本项目是一款基于C语言开发的控制台…...

酷得单片机方案 2.4G儿童遥控漂移车
电子方案开发定制,我们是专业的 东莞酷得智能单片机方案之2.4G遥控玩具童车具有以下比较有特色的特点: 1、内置充电电池:这款小车配备了可充电的电池,无需频繁更换电池,既环保又方便。充电方式可能为USB充电或者专用…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...

c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...

uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...

从零开始了解数据采集(二十八)——制造业数字孪生
近年来,我国的工业领域正经历一场前所未有的数字化变革,从“双碳目标”到工业互联网平台的推广,国家政策和市场需求共同推动了制造业的升级。在这场变革中,数字孪生技术成为备受关注的关键工具,它不仅让企业“看见”设…...