• 消息队列的设计


    在网络服务器的设计中,经常使用多进程/多线程.这就涉及到在进程/线程间共享数据.

    现在我们假设一个场景,一个进程/线程负责处理网络收发,一个或多个进程/线程处理

    收到的网络数据包.

    显然,我们可以在每一对协作进程/线程间添加一个队列,将数据添加到队列中,以实现

    两个进程/线程的协作.

    我们的消息队列主要的设计目标有三个:

    1)要可以使用在进程与进程和线程与线程之间.当在进程之间通信时,我们的消息队列

    将会被放在共享内存中.

    2)避免使用锁机制,其一方面原因是锁的开销较大,另一方面是因为,对于在共享内存中

    使用消息队列时.如果一个进程获得锁之后崩溃,另一个进程将得不到任何的通知.当它

    要获得锁的时候,将会阻塞在永远不会被释放的锁上(unix like的系统).

    3)可向队列中加入变长的消息,减少memcopy的次数.

    基于以上目标,每一个消息队列只能有一个写者,一个读者,以避免锁的使用.

    消息队列用数组实现循环队列.数组中的每个元素是以下结构:

    template<int MsgSize>
    struct msgBlock
    {
        int next;     //下一个块的下标,如果是最后一个block,则==-1
        int totalSize;//整个消息的大小,仅对消息中的第一个块有效
        int size;     //本块有效数据大小
        char msg[MsgSize];
        int getAvaSize(char *p)
        {
            return MsgSize - int(p - msg);
        }
        msgBlock():next(-1),totalSize(0),size(0){}
    };

    每个完整的消息都是由1个或连续数个msgBlock组成.这样就可以实现在消息队列中传递

    变长的消息包.

      

    消息队列的接口如下: 

    struct wPos
    {
        int blk;
        char *pos;
        wPos():blk(-1),pos(NULL){}
    };
    template<int MsgSize = 1024,int QueueSize = 4096>  //MsgSize,每个block的大小,QueueSize,最大消息数
    class MsgQueue
    {
    public:
        template<typename T>
        int WriteNum(const T val);
        template<typename T>
        int ReWriteNum(wPos *pos,const T val);
        int WriteString(char *str);
        int WriteBin(void *bin,int len);
        wPos getWPos();
        /*
        * brief: 向队列提交一个完整的消息
        */
        void MsgPush();
        /*
        * brief : 读出一条完整的消息
        */
        int MsgPop(void *buf);
        //返回队列中第一个msg的大小
        int GetFirstMsgSize();
    };

    为了减少内存的考贝,可以通过write函数簇直接向消息队列中写入数值型,string,和二进制数据.

    当所有的数据都写完后,调用MsgPush将把这个消息包提交到队列中.

    通过MsgPop可以获取一个完成的消息.还提供了rewrite接口,以修改已经写入队列的数值型数据.

    以下是完整的程序:

    #ifndef _MSGQUEUE_H
    #define _MSGQUEUE_H
    #include <assert.h>
    /*
    * brief : 消息队列,作为线程/进程间通信的消息队列,实现单读单写,无需加锁.
    *         对于进程间通信,使用共享内存实现.
    *
    */
    template<int MsgSize>
    struct msgBlock
    {
        int next;
        int totalSize;//整个消息的大小,仅对消息中的第一个块有效
        int size;//本块有效数据大小
        char msg[MsgSize];
        int getAvaSize(char *p)
        {
            return MsgSize - int(p - msg);
        }
        msgBlock():next(-1),totalSize(0),size(0){}
    };
    struct wPos
    {
        int blk;
        char *pos;
        wPos():blk(-1),pos(NULL){}
    };
    template<int MsgSize = 1024,int QueueSize = 4096>
    class MsgQueue
    {
    public:
        MsgQueue():idx_read(0),idx_write(0),curblk(0),pCurWrite(msgQueue[0].msg),saveTotalSize(0){}
        template<typename T>
        int WriteNum(const T val)
        {
            return Write(&val,(int)sizeof(T));
        }
        template<typename T>
        int ReWriteNum(wPos *pos,const T val)
        {
            return ReWrite(pos,&val,(int)sizeof(T));
        }
        int WriteString(char *str)
        {
            return Write(str,(int)strlen(str)+1);
        }
        int WriteBin(void *bin,int len)
        {
            return Write(bin,len);
        }
        wPos getWPos()
        {
            wPos ret;
            if((curblk + 1)%QueueSize == idx_read)
                return ret;
            ret.blk = curblk;
            ret.pos = pCurWrite;
            return ret;
        }
        /*
        * brief: 一条完整的消息已经完成写入队列
        */
        void MsgPush()
        {
            if((idx_write+1)%QueueSize == idx_read)
                return;
            msgQueue[idx_write].totalSize = saveTotalSize;
            msgQueue[curblk].next = -1;
            idx_write = (curblk+1)%QueueSize;
            pCurWrite = msgQueue[idx_write].msg;
            curblk = idx_write;
            saveTotalSize = msgQueue[idx_write].size = 0;
            msgQueue[idx_write].next = -1;
        }
        /*
        * brief : 读出一条完整的消息
        */
        int MsgPop(void *buf)
        {
            assert(buf);
            if(idx_read == idx_write)
                return 0;
            int tmp_cur = idx_read;
            
            char *pWrite = (char*)buf;
            int totalSize = msgQueue[tmp_cur].totalSize;
            for( ; ; )
            {
                msgBlock<MsgSize> &curBlock = msgQueue[tmp_cur];
                memcpy(pWrite,curBlock.msg,curBlock.size);
                pWrite += curBlock.size;
                tmp_cur = (tmp_cur+1)%QueueSize;
                if(curBlock.next == -1)
                    break;
            }
            idx_read = tmp_cur;
            return totalSize;
        }
        //返回队列中第一个msg的大小
        int GetFirstMsgSize()
        {
            if(idx_read == idx_write)
                return 0;
            return msgQueue[idx_read].totalSize;
        }
    private:
        int ReWrite(wPos *pos,const void *buf,int size)
        {
            assert(buf);
            assert(size>0);
            int tSize = size;
            msgBlock<MsgSize> *msgBlk = &msgQueue[pos->blk];    
            char *pRead = (char *)buf;
            while(tSize)
            {        
                int avaSize = msgBlk->getAvaSize(pos->pos);
                //当前block空间已经用完,需要使用第二个block的空间
                if(avaSize == 0)
                {
                    pos->blk = (pos->blk + 1) % QueueSize;
                    msgBlk = &msgQueue[pos->blk];
                    avaSize = MsgSize;
                    pos->pos = msgBlk->msg;
                }
                int writesize = avaSize > size ? size : avaSize;
                
                memcpy(pos->pos,pRead,writesize);
                pos->pos += writesize;
                pRead += writesize;
                tSize -= writesize;
            }
            
            return size;
        
        }
        /*
        * brief: 向队列中写入数据,这些数据只是消息中的一部分,当所有数据都写完,调用MsgWrite.
        */
        int Write(const void *buf,int size)
        {
            assert(buf);
            assert(size>0);
            //已经没有空间可供写入
            if((curblk + 1)%QueueSize == idx_read)
                return 0;
            int tSize = size;
            msgBlock<MsgSize> *msgBlk = &msgQueue[curblk];    
            char *pRead = (char *)buf;
            while(tSize)
            {        
                int avaSize = msgBlk->getAvaSize(pCurWrite);
                //当前block空间已经用完,需要使用第二个block的空间
                if(avaSize == 0)
                {
                    int next = (curblk + 1) % QueueSize; 
                    if(next == idx_read)//空间用完了
                    {
                        curblk = idx_write;
                        pCurWrite = msgQueue[idx_write].msg;
                        saveTotalSize = 0;
                        return 0;
                    }
                    msgBlk->next = curblk = next;
                    msgBlk = &msgQueue[curblk];
                    avaSize = MsgSize;
                    msgBlk->size = 0;
                    pCurWrite = msgBlk->msg;
                }
                int writesize = avaSize > size ? size : avaSize;
                
                memcpy(pCurWrite,pRead,writesize);
                pCurWrite += writesize;
                pRead += writesize;
                saveTotalSize += writesize;
                msgBlk->size += writesize;
                tSize -= writesize;
            }
            return size;
        }
    private:
        int idx_read;//读下标
        int idx_write;//写下标
        
        char *pCurWrite;//当前写指针
        int   curblk;//当前写所在的块
        int   saveTotalSize;
        msgBlock<MsgSize> msgQueue[QueueSize];
    };
  • 相关阅读:
    VUE vue和element框架搭配实现导航跳转,底部导航跳转页面
    【HDFS篇14】HA高可用 --- Federation架构设
    【HDFS篇13】HA高可用 --- YARN-HA集群配置
    【HDFS篇12】HA高可用 --- HDFS-HA集群配置
    【HDFS篇11】HA高可用
    【HDFS篇10】DataNode相关概念
    【HDFS篇09】集群安全模式
    【HDFS篇08】NameNode故障处理
    【HDFS篇07】NameNode和SecondearyNameNode
    【HDFS篇06】HDFS数据读写流程
  • 原文地址:https://www.cnblogs.com/sniperHW/p/2607325.html
Copyright © 2020-2023  润新知