首页 > 其他分享 >UDT(四):接收缓冲区管理

UDT(四):接收缓冲区管理

时间:2024-10-29 10:44:33浏览次数:1  
标签:UDT 管理 队列 CUnitQueue 接收缓冲区 iSize 数据 单元

1. 简介

  • 此文章尚未涉及到接收缓冲区中数据的重组/重传/可靠性等相关内容,这部分内容会在后续的文章中详细介绍
  • 这里先简单介绍一下接收缓冲区的数据是如何存储的,以及接收缓冲区的容量是如何调整的
  • 分析接收缓冲区的具体实现时,要带有如下几个问题
    • 接收缓冲区中的数据是如何划分的?数据管理的基本单元是什么?
    • 接收缓冲区的容量是固定的吗?如果接收缓冲区的容量不固定,调整策略又是什么?
    • 接收缓冲区的数据有几种状态?接收缓冲区中的数据何时会被丢弃?

2. 源码分析

  • 相关文件:queue.cpp/queue.h buffer.cpp/buffer.h

2.1 数据基本单元

  • 如前文所述,发送缓冲区中的数据是以数据块的形式进行管理;无论是读写数据还是丢弃数据,都是按数据块进行的
  • 接收缓冲区则不然,接收缓冲区的数据按一个一个的数据单元进行管理,每个数据单元都有四种状态
    • 0 - 空闲
    • 1 - 已占用
    • 2 - 当前数据单元中的数据已读取,但是还未释放所占用的内存,用于处理数据包乱序的情况
    • 3 - 丢弃
    struct CUnit
    {
        // 一个UDT数据包,参考前文中的UDT包结构
        CPacket m_Packet;
        // 数据单元状态:0-空闲;1-已占用;2-已读取未释放;3-丢弃
        int m_iFlag;
    };
    

2.2 数据单元队列

  • 使用一个顶层循环队列CUnitQueue来管理所有数据单元队列
  • 顶层队列中的每个节点存储的都是一个数据单元队列的入口地址

2.2.1 队列入口

  • 数据单元队列入口
    • m_pUnit指向一个数据单元
    • m_pBuffer指向数据单元真正使用的堆空间地址
    • m_iSize用于统计队列中共有多少个数据单元
    // CQEntry == Class Queue Entry
    struct CQEntry
    {
        // 队列元素指针
        CUnit* m_pUnit;
        // 指针域,指向堆空间
        char* m_pBuffer;
        // 队列中共有多少个数据单元
        int m_iSize;
    
        // 指向下一个队列
        CQEntry* m_pNext;
    }
    

2.2.2 顶层队列CUnitQueue初始化

  • 文件queue.cpp/queue.h
  • 用于初始的函数int CUnitQueue::init(int size, int mss, int version);
    • 为所有的数据单元申请堆空间
    • 初始化所有数据单元为空闲状态,并初始化所有数据单元的负载指向真正的堆空间
    • CUnitQueue使用一个循环队列来管理管理所有的数据单元队列CUnit*,初始化时CUnitQueue中只有一个数据单元队列,共有m_iSize个数据单元
    • 每个数据单元中负载数据的最大大小为mss
    int CUnitQueue::init(int size, int mss, int version)
    {
        CQEntry* tempq = NULL;
        CUnit* tempu = NULL;
        char* tempb = NULL;
    
        // CUnitQueue入口
        tempq = new CQEntry;
        // CUnitQueue中的数据单元
        tempu = new CUnit [size];
        // CUnitQueue真正存储数据的堆空间,每个数据单元中负载数据的最大大小为mss
        tempb = new char [size * mss];
    
        // 初始化数据单元队列中的所有数据单元为空闲状态
        for (int i = 0; i < size; ++ i)
        {
            // 初始化所有数据单元为空闲状态
            tempu[i].m_iFlag = 0;
            // 初始化所有数据单元占用的堆空间地址,基地址 + 偏移量
            tempu[i].m_Packet.m_pcData = tempb + i * mss;
        }
        
        // 初始化CUnitQueue队列入口
        tempq->m_pUnit = tempu;          // 队列入口指针
        tempq->m_pBuffer = tempb;        // 真正存储数据的堆空间
        tempq->m_iSize = size;           // 队列中共有多少个节点
    
        // 队列入口 = 当前队列 = 最后一个队列
        m_pQEntry = m_pCurrQueue = m_pLastQueue = tempq;
        // CUnitQueue循环队列
        m_pQEntry->m_pNext = m_pQEntry;
    
        // 第一个可用的数据单元
        m_pAvailUnit = m_pCurrQueue->m_pUnit;
        
        // 队列中的数据单元总数
        m_iSize = size;
        // 队列中每个数据单元负载的最大长度
        m_iMSS = mss;
        // IPv4/IPv6
        m_iIPversion = version;
        
        return 0;
    }
    

2.2.3 顶层队列CUnitQueue扩容

  • CUnitQueue可动态扩容,当已占用的数据单元个数超过队列空间的90%后,就需要进行扩容
  • 所谓扩容,就是创建一个新的数据单元队列,为其分配m_size * m_iMSS字节的堆空间,然后将这个新的数据单元队列纳入CUnitQueue的管理中
    int CUnitQueue::increase()
    {
        // 统计有多少数据单元已被占用
        int real_count = 0;
        // CUnitQueue队列入口
        CQEntry* p = m_pQEntry;
        // 遍历所有队列,统计有多少数据单元出于非空闲状态
        while (p != NULL)
        {
            // 队列中的数据单元
            CUnit* u = p->m_pUnit;
                // 遍历队列中的数据单元
                for (CUnit* end = u + p->m_iSize; u != end; ++ u)
                    // 统计处于有多少数据单元出于非空闲状态
                    if (u->m_iFlag != 0)
                        ++ real_count;
        
            // 所有队列都遍历完成
            if (p == m_pLastQueue)
                p = NULL;
            // 当前队列统计完成,切换到下一个队列
            else
                p = p->m_pNext;
        }
        // 有多少节点处于非空闲状态
        m_iCount = real_count;
        
        // 如果队列中处于非空闲状态的数据单元比例小于90%,则认为不需要增加队列容量
        if (double(m_iCount) / m_iSize < 0.9)
            return -1;
    
        CQEntry* tempq = NULL;
        CUnit* tempu = NULL;
        char* tempb = NULL;
    
        // 每个数据单元队列的容量,所有的队列容量都相同,都有m_iSize个数据单元
        int size = m_pQEntry->m_iSize;
    
        // 创建创建一个新的数据单元队列
        tempq = new CQEntry;
        tempu = new CUnit [size];
        tempb = new char [size * m_iMSS];
    
        for (int i = 0; i < size; ++ i)
        {
            // 初始化所有数据单元为空间状态
            tempu[i].m_iFlag = 0;
            // 初始化所有数据单元的堆空间
            tempu[i].m_Packet.m_pcData = tempb + i * m_iMSS;
        }
        tempq->m_pUnit = tempu;
        tempq->m_pBuffer = tempb;
        tempq->m_iSize = size;
    
        // 将新建的数据单元队列纳入到CUnitQueue中
        m_pLastQueue->m_pNext = tempq;
        m_pLastQueue = tempq;
        m_pLastQueue->m_pNext = m_pQEntry;
    
        // CUnitQueue容量
        m_iSize += size;
    
        return 0;
    }
    

2.2.4 获取一个可用的数据单元

  • 获取一个可用的数据单元,用来存储接收到的数据包
    • 遍历所有的数据单元队列,直到找到一个空间的数据单元
    • 若遍历完成仍未找到一个空闲的数据单元,则进行扩容
    CUnit* CUnitQueue::getNextAvailUnit()
    {
        // m_iCount > 0.9 * m_iSize; 即堆空间的使用率超过了90%,需要扩容
        if (m_iCount * 10 > m_iSize * 9)
            increase();
    
        // 堆空间已满
        if (m_iCount >= m_iSize)
            return NULL;
    
    // 队列入口,当前正在使用的队列
    CQEntry* entrance = m_pCurrQueue;
    
        do
        {
            // 从m_pAvailUnit开始,遍历队列,找到一个空闲的数据单元
            for (CUnit* sentinel = m_pCurrQueue->m_pUnit + m_pCurrQueue->m_iSize - 1; m_pAvailUnit != sentinel; ++ m_pAvailUnit)
            if (m_pAvailUnit->m_iFlag == 0)
                return m_pAvailUnit;
        
            // 检查当前队列的第一个数据单元是否可用
            if (m_pCurrQueue->m_pUnit->m_iFlag == 0)
            {
                m_pAvailUnit = m_pCurrQueue->m_pUnit;
                return m_pAvailUnit;
            }
    
            // 当前队列中没有可用的数据单元,遍历下一个队列
            m_pCurrQueue = m_pCurrQueue->m_pNext;
            m_pAvailUnit = m_pCurrQueue->m_pUnit;
        } while (m_pCurrQueue != entrance);
    
        // 没有找到可用的数据单元,需要扩容
        increase();
    
        return NULL;
    }
    

3. 总结

  • 接收缓冲区中的数据按数据单元CUnit为基本单位进行管理
  • 接收缓冲区中的数据单元有几种状态:空闲/已占用/数据已被读取待释放内存/丢弃数据 四种状态
  • 接收缓冲区可动态扩容,当接收缓冲区的占用率达到90%后,就会重新申请一段固定大小的堆空间来进行扩容

标签:UDT,管理,队列,CUnitQueue,接收缓冲区,iSize,数据,单元
From: https://www.cnblogs.com/zhijun1996/p/18512443

相关文章

  • GaussDB技术解读——GaussDB架构介绍之集群管理层(CM)关键技术方案
    GaussDBKernelV5集群管理层关键模块如下。图4集群管理层组件设计图CM组件提供了四种服务CMAgent,CMServer,OMMonitor,cm_ctl,与各类实例服务组件(CN,DN,GTM等)一起构成了整个数据库集群系统。cm_ctl通过命令行执行集群的启动、停止、状态查询、主备倒换、备机重......
  • GaussDB技术解读——GaussDB架构介绍之全局事务管理层(GTM)关键技术方案
    GTM仅处理全局时间戳请求,64位CSN递增,几乎都是CPU++和消息收发操作。不是每次都写ETCD,而是采用定期持久化到ETCD里,每次写ETCD的CSN要加上一个backup_step(100w),一旦GTM故障,CSN从ETCD读取出来的值保证单调递增。当前GTM只完成CSN++,预估可以支持200M/s请求。GTM处理......
  • 多线程应用在鸿蒙 HarmonyOS Next 中的内存管理与 GC 实战
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。在构建高性能应用时,尤其是需要处理大......
  • 高性能 ArkUI 应用开发:复杂 UI 场景中的内存管理与 XML 优化
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。在开发高性能ArkUI应用时,尤其是涉及......
  • Java毕业设计-基于Springboot框架的档案管理系统项目实战(附源码+论文)
    大家好!我是程序猿老A,感谢您阅读本文,欢迎一键三连哦。......
  • 计算机毕业设计-基于Java+springboot架构的档案管理系统项目开发实战(附源码+论文)
    大家好!我是程序员一帆,感谢您阅读本文,欢迎一键三连哦。......
  • Springboot计算机毕业设计房屋租赁管理系统的设计与实现64tm5
    Springboot计算机毕业设计房屋租赁管理系统的设计与实现64tm5本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表项目功能:房东,租户,房源类型,房源信息,预定房子,合同信息,退租信息开题报告内容一、项目背景与......
  • HarmonyOS Next:内存管理与 GC 基础
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。引言HarmonyOSNext作为华为自研的......
  • GaussDB数据库事务管理
    ​一、引言事务管理是数据库系统中至关重要的一部分,它确保了数据库的一致性和可靠性。在GaussDB数据库中,事务管理不仅遵循传统的ACID特性,还提供了一些高级功能。本文将深入探讨GaussDB数据库事务管理的各个方面。二、事务的基本概念2.1事务的定义事务是数据库操作的基本单元......
  • GaussDB轻量化运维管理工具介绍
    ​前言本期课程将从管理平台的架构出发,结合平台的实例管理、实例升级、容灾管理和监控告警的功能和操作介绍,全面覆盖日常运维操作,带您理解并熟练运用GaussDB运维平台完成运维工作。一、GaussDB运维管理平台简介开放生态层友好Web界面,多云皮肤个性化定制丰富的原子API公有......