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

news2025/1/20 22:02:32

​🌈个人主页: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中写入一个字符串并向后移动write
    void WriteStringAndPush(const std::string &data)
    {
        WriteString(data);
        MoveWriteOffest(data.size());
    }
    //把一个buffer类型的数据写入
     void WriteBuffer(Buffer &data)
     {
        Write(data.ReadPos(), data.ReadAbleSize());
     }
    //向buffer中写入一个并向后移动write
     void 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模块

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

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

相关文章

12k Star!Continue:Github Copilot 开源本地版、开发效率和隐私保护兼得、丰富功能、LLM全覆盖!

原文链接&#xff1a;&#xff08;更好排版、视频播放、社群交流、最新AI开源项目、AI工具分享都在这个公众号&#xff01;&#xff09; 12k Star&#xff01;Continue&#xff1a;Github Copilot 开源本地版、开发效率和隐私保护兼得、丰富功能、LLM全覆盖&#xff01; &…

CSS--学习

CSS 1简介 1.1定义 层叠样式表 (Cascading Style Sheets&#xff0c;缩写为 CSS&#xff09;&#xff0c;是一种 样式表 语言&#xff0c;用来描述 HTML 文档的呈现&#xff08;美化内容&#xff09;。 1.2 特性 继承性 子级默认继承父级的文字控制属性。层叠性 相同的属性…

Elasticsearch 认证模拟题 - 5

一、题目 .在集群上有一个索引 food_ingredient&#xff0c;搜索需要满足以下要求&#xff1a; 三个字段 manufacturer&#xff0c;name&#xff0c;brand 都能匹配到文本 cake mix高亮 字段 name&#xff0c;并加标签排序&#xff0c;对字段 brand 正序&#xff0c;_score 降…

【Linux】Linux环境基础开发工具_3

文章目录 四、Linux环境基础开发工具2. vim3. gcc和g动静态库的理解 未完待续 四、Linux环境基础开发工具 2. vim vim 怎么批量化注释呢&#xff1f;最简单的方法就是在注释开头和结尾输入 /* 或 */ 。当然也可以使用快捷键&#xff1a; Ctrl v 按 hjkl 光标移动进行区域选择…

VR导航的实现原理、技术优势和应用场景

VR导航通过虚拟现实技术提供沉浸式环境&#xff0c;结合室内定位技术实现精准导航。目前&#xff0c;VR导航已在多个领域展现出其独特的价值和潜力&#xff0c;预示着智能导航系统的未来发展。 一、实现原理 VR导航技术依托于虚拟现实(VR)和室内定位系统。VR技术利用计算机模…

IMU状态预积分代码实现 —— IMU状态预积分类

IMU状态预积分代码实现 —— IMU状态预积分类 实现IMU状态预积分类 实现IMU状态预积分类 首先&#xff0c;实现预积分自身的结构。一个预积分类应该存储一下数据&#xff1a; 预积分的观测量 △ R ~ i j , △ v ~ i j , △ p ~ i j \bigtriangleup \tilde{R} _{ij},\bigtrian…

MySQL基础索引知识【索引创建删除 | MyISAM InnoDB引擎原理认识】

博客主页&#xff1a;花果山~程序猿-CSDN博客 文章分栏&#xff1a;MySQL之旅_花果山~程序猿的博客-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我们一起努力&#xff0c;一起成长&#xff01; 目录 一&#xff0c;索引用…

数据在内存中的存储<C语言>

导言 在计算机中不同类型的数据在计算机内部存储形式各不相同&#xff0c;弄懂各种数据在计算机内部存储形式是有必要的&#xff0c;C语言的学习不能浮于表面&#xff0c;更要锻炼我们的“内功”&#xff0c;将来在写程序的时候遇见各种稀奇古怪的bug时&#xff0c;也便能迎刃而…

Beamer中二阶导、一阶导数的显示问题

Beamer中二阶导、一阶导数的显示问题 在beamer中表示 f ′ f f′和 f ′ ′ f f′′时发现导数符号距离 f f f很近 \documentclass{beamer} \usepackage{amsmath,amssymb}\begin{document} \begin{frame}\frametitle{Derivative}Derivative:\[f^{\prime}(x) \quad f \quad…

4月啤酒品类线上销售数据分析

近期&#xff0c;中国啤酒行业正处于一个重要的转型期。首先&#xff0c;消费者对高品质啤酒的需求不断增加&#xff0c;这推动了行业向高端化、场景化和社交化的方向发展。精酿啤酒作为这一趋势的代表&#xff0c;其发展势头强劲&#xff0c;不仅满足了消费者对品质化、个性化…

Java集合【超详细】2 -- Map、可变参数、Collections类

文章目录 一、Map集合1.1 Map集合概述和特点【理解】1.2 Map集合的基本功能【应用】1.3 Map集合的获取功能【应用】1.4 Map集合的两种遍历方式 二、HashMap集合2.1 HashMap集合概述和特点【理解】2.2 HashMap的组成、构造函数2.3 put、查找方法2.4 HashMap集合应用案例【应用】…

退出登录后选择记住登录状态回显用户名和密码

项目背景 : react ant 需求 : 退出登录后 , 选择了记住登录 , 回显用户名和密码 ; 未选择记住 , 则不回显用户名和密码 如图注意 : 发现一个鸡肋的问题 , 未勾选退出后 , 还是会回显 , 后来我查看了cookie和自己的逻辑都没问题 , 原来是因为我保存了密码 , 浏览器保存后自动渲…

C# 代码配置的艺术

文章目录 1、代码配置的定义及其在软件工程中的作用2、C# 代码配置的基本概念和工具3、代码配置的实践步骤4、实现代码配置使用属性&#xff08;Properties&#xff09;使用配置文件&#xff08;Config Files&#xff09;使用依赖注入&#xff08;Dependency Injection&#xf…

Echarts 让柱状图在图表中展示,离开X轴

文章目录 需求分析需求 分析 话不多说,直接源码展示 option = {title: {text: Waterfall Chart,subtext: Li

数据隐私重塑:Web3时代的隐私保护创新

随着数字化时代的不断深入&#xff0c;数据隐私保护已经成为了人们越来越关注的焦点之一。而在这个数字化时代的新篇章中&#xff0c;Web3技术作为下一代互联网的代表&#xff0c;正在为数据隐私保护带来全新的创新和可能性。本文将深入探讨数据隐私的重要性&#xff0c;Web3时…

数字孪生技术为何备受各行业青睐?

数字孪生技术近年来在各行业中受到越来越多的重视&#xff0c;这是因为它具备了显著的优势和广泛的应用前景。数字孪生是指利用数字化技术&#xff0c;在虚拟空间中创建一个与现实世界对应的虚拟模型&#xff0c;通过数据的实时交互和反馈&#xff0c;实现对物理实体的模拟和监…

嵌入式Linux复制剪切删除指令详解

指令操作 1. cp 复制指令 a. 用法&#xff1a;cp [ 选项 ] [ 源文件或目录 ] [ 目标文件或目录 ]&#xff1b; b. 用途&#xff1a;用于复制文件或目录&#xff1b; c. 通常情况下&#xff0c;复制的都不是空文件夹&#xff0c;所以直接使用 cp 复制空文件会失败&#xff0…

三体中的冯诺依曼

你叫冯诺依曼&#xff0c;是一位科学家。你无法形容眼前的现态&#xff0c;你不知道下一次自己葬身火海会是多久&#xff0c;你也不知道会不会下一秒就会被冰封&#xff0c;你唯一知道的&#xff0c;就是自己那寥寥无几的科学知识&#xff0c;你可能会抱着他们终身&#xff0c;…

全国产飞腾模块麒麟信安操作系统安全漏洞

1、背景介绍 目前在全国产飞腾模块上部署了麒麟信安操作系统&#xff0c;经第三方机构检测存在以下漏洞 操作系统版本为 内核版本为 openssh版本为 2、openssh CBC模式漏洞解决 首先查看ssh加密信息 nmap --script "ssh2*" 127.0.0.1 | grep -i cbc 可以通过修改/…

结构设计模式 - 代理设计模式 - JAVA

代理设计模式 一. 介绍二. 代码示例2.1 定义 CommandExecutor 类2.2 定义 CommandExecutorProxy代理类2.3 模拟客户端2.4 测试结果 三. 结论 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子…