004 仿muduo实现高性能服务器组件_Buffer模块与Socket模块的实现

2024-06-01 07:28

本文主要是介绍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模块的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1020351

相关文章

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo