消息队列的设计

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

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

收到的网络数据包.

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

两个进程/线程的协作.

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

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];
};
原文地址:https://www.cnblogs.com/sniperHW/p/2607325.html