zoukankan      html  css  js  c++  java
  • 网络接收缓存的设计

    网络程序,为了提高程序的性能,应尽量减少内存的拷贝次数。


    以windows IOCP为例,complete线程在接收到数据后,应该马上将接收到的数据拷贝到
    解包缓存,然后立即发起一次新的WSARecv操作。然后再对解包缓存执行操作,解析出逻辑
    包。通常的方法是将数据拷贝到一个环形缓冲中,以减少数据的拷贝次数。但在解出一个数据
    包之后,免不了还要将一个完整的数据包拷贝到另一个包缓存中,再将这个包提交给应用层处理.


    本文介绍一种,拼包方式,以避免从解包缓存copy数据到逻辑包中.


    首先介绍逻辑包的组织结构:

    //RPacket数据可跨越多个buffer
    class RPacket
    {
    friend class Connection;
    friend class WPacket;
    public:
    RPacket(const RPacket &other):m_buf(other.m_buf),m_head(other.m_head),m_len(other.m_len)
    ,m_readPos(other.m_readPos),m_readBuf(0),m_binBuffer(0),m_binBufferPos(0)
    {
    }


    ~RPacket()
    {
    if(m_binBuffer)
    delete[] m_binBuffer;
    }

    bool _nil()
    {
    return m_buf._nil();
    }

    RPacket& operator = (const RPacket &other)
    {
    if(this == &other)
    return *this;
    m_head = other.m_head;
    m_len = other.m_len;
    m_buf = other.m_buf;
    m_readPos = other.m_readPos;
    if(m_binBuffer)
    {
    delete[] m_binBuffer;
    m_binBufferPos = 0;
    }
    return *this;
    }

    char ReadChar()
    {
    return Read<char>();
    }

    short ReadShort()
    {
    return Read<short>();
    }

    long ReadLong()
    {
    return Read<long>();
    }

    float ReadFloat()
    {
    return Read<float>();
    }

    double ReadDouble()
    {
    return Read<double>();
    }

    short ReadCmd()
    {
    return *(short*)&m_buf->m_buf[m_head+sizeOfLen];
    }

    const char* ReadString()
    {
    unsigned int strLen = (unsigned int)Read<short>();
    if(strLen == 0 || m_dataRemain < strLen)
    {
    return 0;
    }
    unsigned int sizeRemain = m_readBuf->m_bufSize - m_readPos;//当前buf还有多少有效数据
    char *str;
    if(sizeRemain >= strLen)
    {
    str = &m_readBuf->m_buf[m_readPos];
    m_readPos += strLen;
    }
    else
    {

    if(!m_binBuffer)
    {
    m_binBuffer = new char[m_len];
    m_binBufferPos = 0;
    }

    str = &m_binBuffer[m_binBufferPos];
    unsigned int copySize = sizeRemain;
    memcpy(&m_readBuf->m_buf[m_readPos],&m_binBuffer[m_binBufferPos],copySize);
    m_readBuf = m_readBuf->m_next;
    m_readPos = 0;
    m_binBufferPos += copySize;
    copySize = strLen - copySize;
    memcpy(&m_readBuf->m_buf[m_readPos],&m_binBuffer[m_binBufferPos],copySize);
    m_readPos += copySize;
    }
    m_dataRemain -= strLen;
    Arrange();
    return str;
    }

    const void* ReadBinary(unsigned short &len)
    {

    len = (unsigned int)Read<short>();
    if(len == 0 || m_dataRemain < len)
    {
    return 0;
    }

    if(m_dataRemain < len)
    return 0;//应提示错误,可能读包顺序与发包不一致了

    void *bin;

    unsigned int sizeRemain = m_readBuf->m_bufSize - m_readPos;//当前buf还有多少有效数据
    if(sizeRemain >= len)
    {
    bin = &m_readBuf->m_buf[m_readPos];
    m_readPos += len;
    }
    else
    {

    if(!m_binBuffer)
    {
    m_binBuffer = new char[m_len];
    m_binBufferPos = 0;
    }

    bin = &m_binBuffer[m_binBufferPos];

    unsigned int copySize = sizeRemain;
    memcpy(&m_readBuf->m_buf[m_readPos],&m_binBuffer[m_binBufferPos],copySize);
    m_readBuf = m_readBuf->m_next;
    m_readPos = 0;
    m_binBufferPos += copySize;
    copySize = len - copySize;
    memcpy(&m_readBuf->m_buf[m_readPos],&m_binBuffer[m_binBufferPos],copySize);
    m_readPos += copySize;
    }
    m_dataRemain -= len;
    Arrange();
    return bin;
    }

    private:
    template <typename T>
    T Read()
    {

    if(m_readBuf._nil())
    return 0;

    unsigned int TypeSize = sizeof(T);

    if(m_dataRemain < TypeSize)
    return 0;//应提示错误,可能读包顺序与发包不一致了

    T ret = 0;

    unsigned int sizeRemain = MAX_PACKET_SIZE - m_readPos;//当前buf还有多少有效数据
    if(sizeRemain >= TypeSize)
    {
    ret = *(T*)&m_readBuf->m_buf[m_readPos];
    m_readPos += TypeSize;
    }
    else
    {
    //数据跨越了两个数据块
    char tmp[sizeof(T)];
    char *ptr = tmp;
    unsigned int copySize = sizeRemain;
    memcpy(&m_readBuf->m_buf[m_readPos],ptr,copySize);
    ptr += copySize;
    copySize = TypeSize - copySize;
    m_readBuf = m_readBuf->m_next;
    m_readPos = 0;
    memcpy(&m_readBuf->m_buf[m_readPos],ptr,copySize);
    m_readPos += copySize;
    ret = *(T*)&tmp[0];
    }
    m_dataRemain -= TypeSize;
    Arrange();
    return ret;
    }

    RPacket(rptr<buffer> &buf,unsigned short head)
    :m_buf(buf),m_readBuf(buf),m_head(head),m_readPos(head+sizeOfLen+cmdlen)
    ,m_len(0),m_dataRemain(0),m_binBuffer(0),m_binBufferPos(0)
    {
    if(!m_buf._nil())
    {
    m_dataRemain = m_len = *(unsigned short*)&m_buf->m_buf[m_head];
    }
    }

    void Arrange()
    {
    if(m_readPos >= m_readBuf->m_bufSize && m_dataRemain > 0)
    {
    m_readPos = 0;
    m_readBuf = m_readBuf->m_next;
    }
    }

    private:
    unsigned short m_readPos;
    unsigned short m_head; //在buf中的起始下标
    unsigned short m_len; //packet的总长度
    unsigned short m_dataRemain;
    ////////用于处理ReadString,和ReadBin时数据跨越buffer的情况
    char * m_binBuffer;
    unsigned short m_binBufferPos;
    ////////


    rptr<buffer> m_readBuf;//当前readPos所在的buf
    rptr<buffer> m_buf;//存放packet的数据,可能由一组m_buf构成链表
    };

    如代码所示,数据存放在由m_buf组成的list中,m_head表明属于本RPacket的数据在m_buf中的起始位置.



    一个buf块的大小为65535字节。如果包的长度不大,则N个RPacket可以共享同一个buf,如果RPacket的数据
    大于65535,则一个RPacket的数据则可能会使用超过一块buf。由同一个套接口收到的所有RPacket,其实际数据
    被一个buf链表链接着。


    buf由基于引用计数的指针指向,当引用同一个buf块的RPacket都被释放之后,buf也将会被释放。


    下面介绍数据接收和解包处理过程:

    bool Connection::Recv()
    {
    if(m_tail._nil())
    {
    m_CurRecvBuf = m_head = m_tail = new buffer(MAX_PACKET_SIZE);
    m_pos = m_totalDataSize = m_writePos = 0;
    }
    unsigned short bufCount = 0;
    unsigned short RecvSize = MAX_PACKET_SIZE;
    unsigned short freeBufSize = m_CurRecvBuf->m_bufSize - m_writePos;
    m_WRecvBuf[bufCount].buf = &m_CurRecvBuf->m_buf[m_writePos];
    m_WRecvBuf[bufCount].len = freeBufSize;
    RecvSize -= freeBufSize;

    if(RecvSize > 0)
    {
    ++bufCount;
    //不够MAX_PACKET_SIZE
    rptr<buffer> tmp = new buffer(MAX_PACKET_SIZE);
    m_tail->m_next = tmp;
    m_tail = tmp;
    m_WRecvBuf[bufCount].buf = &m_tail->m_buf[0];
    m_WRecvBuf[bufCount].len = RecvSize;
    }
    return ConnectionBase::Recv(m_WRecvBuf,bufCount,(OVERLAPPED*)&m_RecvContext);
    }


    void Connection::OnRecvComplete(unsigned short dwBytesTransfered)
    {
    unsigned short freeBufSize = m_CurRecvBuf->m_bufSize - m_writePos;
    if(freeBufSize >= dwBytesTransfered)
    {
    m_writePos += dwBytesTransfered;
    freeBufSize -= dwBytesTransfered;
    m_CurRecvBuf->m_dataSize += dwBytesTransfered;
    if(freeBufSize <= 0)
    {
    //开辟新空间
    rptr<buffer> tmp = new buffer(MAX_PACKET_SIZE);
    m_tail->m_next = tmp;
    m_tail = tmp;
    m_CurRecvBuf = m_tail;
    m_writePos = 0;
    }
    }
    else
    {
    m_CurRecvBuf->m_dataSize = m_CurRecvBuf->m_bufSize;
    //开辟新空间
    rptr<buffer> tmp = new buffer(MAX_PACKET_SIZE);
    m_tail->m_next = tmp;
    m_tail = tmp;
    m_CurRecvBuf = m_tail;
    m_writePos += (dwBytesTransfered - freeBufSize);
    m_CurRecvBuf->m_dataSize += (dwBytesTransfered - freeBufSize);
    }

    m_totalDataSize += dwBytesTransfered;
    //Recv();
    }

    RPacket Connection::UnPack()
    {
    if(!m_head._nil())
    return RPacket(rptr<buffer>(0),0);

    unsigned short packetLen = ReadPacketLen();
    if(packetLen == 0)
    {
    return RPacket(rptr<buffer>(0),0);
    }

    if(m_totalDataSize < packetLen)
    return RPacket(rptr<buffer>(0),0);//没有足够的数据

    //OK,数据充足,返回封包
    RPacket rpk(m_head,(unsigned short)m_pos);

    m_totalDataSize -= packetLen;

    //调整m_head和m_pos
    m_pos += packetLen;
    while(m_pos >= MAX_PACKET_SIZE)
    {
    rptr<buffer> tmp = m_head;
    m_head = m_head->m_next;
    if(m_head._nil())
    {
    m_tail = 0;
    m_pos = 0;
    }
    else
    m_pos -= MAX_PACKET_SIZE;
    }
    return rpk;
    }

    完成例程在OnRecvComplete中,



    把接收到的数据拷贝到buf链表中,马上启动新的Recv操作.




    后续的解包过程是UnPack函数,其作用就是将buf链表中的数据解包,并将RPacket返回以供应用
    层处理,从代码中可以看出,解包过程是没有数据拷贝的,只需要正确设置RPacket中的字段就可以了.


  • 相关阅读:
    工业和信息化部“十二五”规划教材:阵列信号处理及MATLAB实现
    PHP 7从零基础到项目实战
    HTML+CSS网页设计详解
    新编 中文版Maya入门与提高
    Lua游戏AI开发指南
    这么设计能热卖:融入运营思维的电商设计进阶宝典(套装上下册)
    WCF初探-1:认识WCF
    用微信二维码登录自己的网站
    01-C语言基本知识
    「OC」内存管理
  • 原文地址:https://www.cnblogs.com/sniperHW/p/2429625.html
Copyright © 2011-2022 走看看