消息队列的设计
在网络服务器的设计中,经常使用多进程/多线程.这就涉及到在进程/线程间共享数据.
现在我们假设一个场景,一个进程/线程负责处理网络收发,一个或多个进程/线程处理
收到的网络数据包.
显然,我们可以在每一对协作进程/线程间添加一个队列,将数据添加到队列中,以实现
两个进程/线程的协作.
我们的消息队列主要的设计目标有三个:
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]; };