在Windows中没有方便操作Message Queue(消息队列)的API(在VxWorks中有msgQCreate,msgQSend,msgQReceive等操作消息队列的 API),消息队列和油槽、管道技术类似,为进程间的通讯提供方便高效的机制。现简单介绍消息队列的机制:一个消息队列由一定数量的有着固定大小消息缓冲区组成,一个进程可以发送消息到消息队列里,也可以接收消息队列里的消息。为了在Windows下方便实现这样的消息队列,这里要利用到共享内存机制。
共享内存的原理将一段(虚拟)内存分别映射到每个进程的地址空间里,然后每个进程都可以对自己的这段空间进行读写操作,这就相当于对同一个内存进行读写,达到了共享的目的。
先认识一下Windows下用于创建共享内存的几个API:CreateFileMapping、MapViewOfFile。
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;
}
消息队列的简单实现已经全部介绍完了。