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中写入一个字符串并向后移动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模块

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

打造你的首个QT 5计算器应用

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;QT 5的力量与我们的计算器 二、QT 5基础&#xff1a;理解UI设计与文件…

mac安装allure及allure:command not fund问题解决

一、下载 下载连接&#xff1a;https://github.com/allure-framework/allure2/releases 选择任意压缩包进行下载 二、解压 解压后是一个文件夹 三、打开终端 # bash终端 vim ~/.bash_profile # zsh终端 vim ~/.zshrc四、配置环境变量 export PATH/usr/bin:/bin:/usr/sb…

「浏览器」服务端渲染

前言 服务端渲染&#xff08;Server-Side Rendering&#xff0c;SSR&#xff09;是一种常见于网页应用的技术&#xff0c;它指的是在服务器上将网页的内容生成&#xff0c;然后发送完整的HTML页面到客户端的浏览器的过程。这与传统的客户端渲染&#xff08;Client-Side Render…

LC 旋转 - 模拟对象

原文链接 链接 液晶 (LC) 旋转网格属性允许您以 theta、phi 为单位指定空间变化的 LC 导向。 液晶由杆状分子结构组成&#xff0c;这些分子结构具有相对于长轴的旋转对称性。因此&#xff0c;液晶具有空间变化的单轴光学特性。 相对于分子长轴和分子短轴的折射率称为非寻常 ne …

STM32使用ST-LINK下载程序中需要注意的几点

使用keil5的ST-link下载界面 前提是ST-LINK已经连接好&#xff0c;&#xff08;下图中是没有连接ST-link设备&#xff09;&#xff0c;只是为了展示如何查看STlink设备是否连接的方式 下载前一定设置下载完成后自启动 这个虽然不是必须&#xff0c;但对立即看到新程序的现象…

python爬取每日天气情况

python爬取每日天气情况 一、项目简介二、完整代码一、项目简介 本次爬取的目标数据来源于天气网,数据所在的页面如下图所示,本次任务较为简单,按照正常操作流程操作即可,即抓包分析数据接口,发送请求获取数据,解析数据并持久化存储。发送请求使用requests库,解析数据使…

2.11 下载安装Oracle数据库(Win10 JP)

2.11 下载安装Oracle数据库 目录一、下载Oracle 11g客户端安装包1. 官网选数据库配置2. Oracle下载器 下载安装包 二、 安装Oracle 11g客户端1. 安装 Oracle Database Client 11.2.0.1.02. 安装 Oracle Database 11.2.0.4.0 for HP OpenVMS Itanium 三、下载Oracle 12c数据库安…

三相智能电表通过Modbus转Profinet网关与PLC通讯案例

Modbus转Profinet网关&#xff08;XD-MDPN100/300&#xff09;的主要功能是实现Modbus协议和Profinet协议之间的转换和通信。Modbus转Profinet网关集成了Modbus和Profinet两种协议&#xff0c;支持Modbus RTU主站/从站&#xff0c;并可以与RS485接口的设备&#xff0c;它自带网…

C盘文件被格式化了,要怎么恢复?

C盘通常是操作系统(如Windows)的默认安装目录。它包含了操作系统的核心文件、驱动程序及系统所需的各种支持文件。这些文件对于计算机的正常运行至关重要。但在使用的过程中&#xff0c;有时可能会因为各种原因导致C盘被格式化&#xff0c;从而丢失了这些重要文件。这无疑是一个…

定个小目标之每天刷LeetCode热题(5)

今天这是一道简单题&#xff0c;解决方法是递归&#xff0c;遍历二叉树最简便的方法就是递归了&#xff0c;我们以递归三大要素对此题进行简单分析一下 第一要素&#xff1a;明确你这个函数想要干什么 在这道题中&#xff0c;我们需要定义一个传入任意二叉树根节点可以左右翻…

【移动端】商场项目路由设计

1&#xff1a;路由设计配置&#xff1a; 一级路由配置 分析项目&#xff0c;设计路由&#xff0c;配置一级路由 一级路由&#xff1a;单个页面独立展示的&#xff0c;都是一级路由&#xff0c;例如&#xff1a;登录页面&#xff0c;首页架子&#xff0c;报错页面 二级路由&…

Java项目对接redis,客户端是选Redisson、Lettuce还是Jedis?

JAVA项目对接redis&#xff0c;客户端是选Redisson、Lettuce还是Jedis&#xff1f; 一、客户端简介1. Jedis介绍2. Lettuce介绍3. Redisson介绍 二、横向对比三、选型说明 在实际的项目开发中&#xff0c;对于一个需要对接Redis的项目来说&#xff0c;就面临着选择合适的Redis客…

基于Vue+Node.js的购物网站设计与实现-计算机毕业设计源码28500

摘 要 近年来&#xff0c;随着移动互联网的快速发展&#xff0c;电子商务越来越受到网民们的欢迎&#xff0c;电子商务对国家经济的发展也起着越来越重要的作用。简单的流程、便捷可靠的支付方式、快捷畅通的物流快递、安全的信息保护都使得电子商务越来越赢得网民们的青睐。现…

将局部变量指针传递给某个c++类,离开类时数据发生变化

最近遇到一个c的问题&#xff0c;将一个局部变量的值传递给某个类&#xff0c;类中没有对该数据进行任何显式修改&#xff0c;结果该变量的值发生变化并且不可访问。 我开始很奇怪为何会发生这样的事情&#xff0c;后来经过调试&#xff0c;发现原来是该类发生了异常&#xff…

迅为RK3562开发板专为3562编写10大分类2900+页文档

iTOP-3562开发板采用瑞芯微RK3562处理器&#xff0c;内部集成了四核A53Mali G52架构&#xff0c;主频2GHZ&#xff0c;内置1TOPSNPU算力&#xff0c;RK809动态调频。支持OpenGLES1.1/2.0/3.2、0penCL2.0、Vulkan 1.1内嵌高性能2D加速硬件。 内置独立NPU, 算力达 1TOPS,可用于轻…

中学生学人工智能系列:如何用AI学化学

经常有读者朋友给公众号《人工智能怎么学》留言咨询如何使用人工智能学习语文、数学、英语、化学等科目。这些都是中学教师、中学生朋友及其家长们普遍关注的问题。仅仅使用留言回复的方式&#xff0c;不可能对这些问题做出具体和透彻的解答&#xff0c;因此本公众号近期将推出…

【Linux基础】Linux了解、安装centos虚拟机

【Linux基础】Linux了解、安装centos虚拟机 文章目录 【Linux基础】Linux了解、安装centos虚拟机1、什么是Linux2、Linux安装2.1、使用VMware安装Linux centos72.2、启动虚拟机安装 1、什么是Linux Linux是一套免费使用和自由传播的操作系统。说到操作系统&#xff0c;大家比较…

【深度强化学习】如何平衡cpu和gpu来加快训练速度(实录)

文章目录 问题抛出问题展示 问题探索参考&#xff1a;如何平衡cpu和gpu来加快训练速度呢&#xff1f; 解决问题实现逻辑&#xff1a;PPO算法示例&#xff1a;偷懒改法&#xff1a;第三处修改再次修改--24.5.22 不偷懒改法修改总结1 最终成绩&#xff08;不是&#xff09;附加赛…

(2024,Video2Game,NeRF,Mesh,物理模块,游戏引擎)通过单个视频实现实时、交互、逼真且兼容浏览器的环境

Video2Game: Real-time, Interactive, Realistic and Browser-Compatible Environment from a Single Video 公众号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 2. 相关工作 3. Video…

CentOS配置DNS

1.打开/etc/resolv.conf文件 sudo vi /etc/resolv.conf2.添加配置 nameserver 114.114.114.1143.保存并关闭文件。 4.为了确保配置生效&#xff0c;重启网络服务或重启系统。例如&#xff1a; 重启网络&#xff1a; sudo systemctl restart network重启系统&#xff1a; …