利用共享内存实现消息队列

利用共享内存实现消息队列  

2009-02-28 22:02:44|  分类: Windows |  标签: |举报 |字号 订阅

 源代码下载

在Windows中没有方便操作Message Queue(消息队列)的API(在VxWorks中有msgQCreate,msgQSend,msgQReceive等操作消息队列的 API),消息队列和油槽、管道技术类似,为进程间的通讯提供方便高效的机制。现简单介绍消息队列的机制:一个消息队列由一定数量的有着固定大小消息缓冲区组成,一个进程可以发送消息到消息队列里,也可以接收消息队列里的消息。为了在Windows下方便实现这样的消息队列,这里要利用到共享内存机制。

  共享内存的原理将一段(虚拟)内存分别映射到每个进程的地址空间里,然后每个进程都可以对自己的这段空间进行读写操作,这就相当于对同一个内存进行读写,达到了共享的目的。

  先认识一下Windows下用于创建共享内存的几个API:CreateFileMappingMapViewOfFile

1)HANDLE WINAPI
     CreateFileMapping(
         HANDLE hFile,
         LPSECURITY_ATTRIBUTES lpAttributes,
         DWORD flProtect,
         DWORD dwMaximumSizeHigh,
         DWORD dwMaximumSizeLow,
         LPCTSTR lpName
         );

该函数为一个文件创建一个未名或具名的文件映像,其中当hFile为INVALID_HANDLE_VALUE时,系统将为Paging file创建文件映像;lpName指定文件映像的名字,当该参数为NULL时,系统将创建一个“私有”的文件映像,别的进程将无法通过名字打开该文件映像,所以该参数需要指定一个全局统一的名字(一般可以通过GUID命名)。

2)LPVOID WINAPI 
     MapViewOfFile(
         HANDLE hFileMappingObject,
         DWORD dwDesiredAccess,
         DWORD dwFileOffsetHigh,
         DWORD dwFileOffsetLow,
         SIZE_T dwNumberOfBytesToMap
         );
该函数主要将一个文件映像映射到自己的地址空间里。hFileMappingObject 为之前调用 CreateFileMapping 或OpenFileMapping 后返回的句柄;dwNumberOfBytesToMap 指定文件映像中由偏移处dwFileOffsetHigh/Low 开始的字节数量,如果该参数为0,则映射指定偏移后的全部数据。

简单介绍了这2个API,接下来将介绍消息队列的实现。

1:既然共享内存可以由每个进程异步的读和写,所以需要创建一个锁,用于保护共享内存。这里使用Mutex来实现。

    当消息队列Send/Receive时,需要先获取该Mutex,当获取成功后,才可以对共享内存进行写操作。简单的伪码实现如下:

        if(WaitForSingleObject(m_hResMutex, INFINITE) == WAIT_OBJECT_0)
        {

            //写操作

            ReleaseMutex(m_hResMutex);
        }

2:当Send消息时,如果消息队列满,怎么办?当然要等了,但要等多少才知道有空余的消息Buffer呢?所以必须提供一个事件机制,告诉在等的Send操作;当Receive消息时,如果消息队列空,怎么办?当然也要等了,但要等多久才知道消息队列有消息呢?所以也得提供一个事件机制,告诉在等的Receive操作。这里使用Event里模拟,分别定义如下的两个Event:

    HANDLE  m_hSendEvent, m_hReceiveEvent;

3:虽然对共享内存进行了读写保护,但是却不知道往消息队列的什么地方读写,怎么办?当然得提供一个结构来保存消息队列的基本信息了,比如消息大小,消息数量等,所以定义如下结构:

//消息队列头信息结构
typedef struct _MSGQ_HEADER
{
    DWORD   Magic;                  //魔数'MSGQ'
    DWORD   MsgSize;              //单个消息大小:字节数
    int     MsgMaxCount;            //消息的总数
    int     MsgNum;                     //消息的个数
    int     ReadIndex;                  //可读消息索引
    int     WriteIndex;                  //可写消息索引
}MSGQ_HEADER, *PMSGQ_HEADER;

其中魔数用于判断该共享内存是否为消息队列;ReadIndex 和 WriteIndex 为读写的队列索引;该结构存放于共享内存的头部。

[本消息队列采用环形队列来模拟的]

4:根据以上条件定义如下的CMsgQ类:

class CMsgQ 
{
public:
 CMsgQ();
 virtual ~CMsgQ();

public:
    BOOL Create(LPCTSTR name, int msgSize, int msgCnt);
    BOOL Open(LPCTSTR name);
    BOOL Close();

    BOOL GetMsgQInfo(PDWORD msgSize, PDWORD msgCnt);

    BOOL Send(LPVOID buf, DWORD dataSize, DWORD waitTime=INFINITE);
    inline DWORD SendDataSize();       //获取上一次Send动作所发送的数据大小
    BOOL Receive(LPVOID buf, DWORD bufSize, DWORD waitTime=INFINITE);
    inline DWORD ReceiveDataSize();    //获取上一次Receive动作所接收的数据大小
   
private:
    DWORD CreateEventMutex(LPCTSTR mapName);
    BOOL CloseEventMutex();

private:
    HANDLE  m_hSendEvent, m_hReceiveEvent;  //发送、接收事件
    HANDLE  m_hResMutex;                                  //资源保护

    DWORD   m_dwLastSendSize, m_dwLastReceiveSize;

    HANDLE  m_hFileMap;     //文件映像句柄
    LPVOID  m_hViewBuf;      //文件映像映射到地址空间的首地址
};

下面简单说明几个重要函数的实现:

a:Create

  BOOL CMsgQ::Create(LPCTSTR name, int msgSize, int msgCnt)
  {

        //先创建文件映像

        m_hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE,
                            NULL,
                            PAGE_READWRITE,
                            0,              //Size高位不设
                            CalcBufferSize(msgSize, msgCnt),//请保证msgQ buffer大小和头不要超过DWORD的最大值
                            name);
        //检查m_hFileMap 是否为NULL,略

       //映射文件映像

       m_hViewBuf = MapViewOfFile(m_hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);//映射全部Buffer

       //检查m_hViewBuf 是否为NULL,略

       //创建2个 Event 和 Mutex 句柄

       //设置MsgQ头信息
        if(WaitForSingleObject(m_hResMutex, INFINITE) == WAIT_OBJECT_0)
        {
            PMSGQ_HEADER pMsgInfo = (PMSGQ_HEADER)m_hViewBuf;

            pMsgInfo->Magic = 'MSGQ';
            pMsgInfo->MsgMaxCount = msgCnt;
            pMsgInfo->MsgSize = msgSize;
            pMsgInfo->ReadIndex = 0;       
            pMsgInfo->WriteIndex = 0;      //从索引0开始写
            pMsgInfo->MsgNum = 0;

            ReleaseMutex(m_hResMutex);
        }
       return TRUE;

  }
b:Send

BOOL CMsgQ::Send(LPVOID buf, DWORD dataSize, DWORD waitTime)
{

    if(m_hViewBuf == NULL)  //如果共享内存没有映射,出错
    {
        return FALSE;
    }
    //判断队列是否满,如果满,则对待m_hSendEvent waitTime毫秒,代码略。

    if(WaitForSingleObject(m_hResMutex, waitTime) == WAIT_OBJECT_0)
    {
        if(pMsgInfo->MsgNum != pMsgInfo->MsgMaxCount)    //再次判断是否可写
        {
            if(dataSize > pMsgInfo->MsgSize)
                dataSize = pMsgInfo->MsgSize;

            CopyMemory(WRITE_ADDR(pMsgInfo), buf, dataSize);//将用户数据拷入共享内存里
            m_dwLastSendSize = dataSize;

            pMsgInfo->MsgNum++;
        
            if(++pMsgInfo->WriteIndex == pMsgInfo->MsgMaxCount)//环形Queue
                pMsgInfo->WriteIndex = 0;

            SetEvent(m_hReceiveEvent);        //通知ReceiveEvent
            ResetEvent(m_hReceiveEvent);    //不管有没有线程在等此信号,都将该信号置无信号状态
           
            bRet = TRUE;
        }
        ReleaseMutex(m_hResMutex);
    }
    return bRet;

}
c:Receive

BOOL CMsgQ::Receive(LPVOID buf, DWORD bufSize, DWORD waitTime)

{

    //判断队列是否为空,若为空,则等待m_hReceiveEvent waitTime毫秒,代码略。

    if(WaitForSingleObject(m_hResMutex, waitTime) == WAIT_OBJECT_0)
    {
        if(pMsgInfo->MsgNum != 0)       //再次判断是否可读
        {
            if(bufSize > pMsgInfo->MsgSize)
                bufSize = pMsgInfo->MsgSize;

            CopyMemory(buf, READ_ADDR(pMsgInfo), bufSize);
            m_dwLastReceiveSize = bufSize;
       
            pMsgInfo->MsgNum--;

            if(++pMsgInfo->ReadIndex == pMsgInfo->MsgMaxCount)//环形Queue
                pMsgInfo->ReadIndex = 0;
       
            SetEvent(m_hSendEvent);     //通知Receive
            ResetEvent(m_hSendEvent);   //不管有没有线程在等此信号,都将该信号置无信号状态
           
            bRet = TRUE;
        }
        ReleaseMutex(m_hResMutex);
    }
    return bRet;

}

 

消息队列的简单实现已经全部介绍完了。

源代码下载

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值