进程间通信详解 - 邮槽实现
2012-07-24 21:57
239 查看
引子
前面的一篇博文介绍了进程之间通信的一种最为简单的方式,
也就是在本地进程之间通过剪贴板来实现进程间通信,而剪贴板自有其缺陷,
很显然的是,剪贴板只能在本地机器上实现,
无法实现本地进程与远程服务器上的进程之间的通信,
那么有没有办法实现本地进程和远程进程的通信呢?
办法自然是有的,要是实在搞不出,
我拿Socket来实现本地进程和远程进程的通信来实现也是可以的,
但是你想啊,要用Socket来实现本地进程和远程进程之间的通信,
那不仅我要在本地进程中加一堆的Socket代码,
并且服务器上的进程中也是需要加一堆的Socket代码的,
那不搞死人去,也太麻烦了吧,所以不行不行,得换一种方案。
下面就来介绍一种超级无敌简单的方案,其可以用来实现本地进程与远程进程之间的通信,
那就是通过邮槽来实现。
邮槽定义
邮槽(Mailslot)也称为邮件槽,其是Windows提供的一种用来实现进程间通信的手段,
其提供的是基于不可靠的,并且是单向数据传输的服务。
邮件槽只支持单向数据传输,也就是服务器只能接收数据,而客户端只能发送数据,
何为服务端?何为客户端?
服务端就是创建邮槽的那一端,而客户端就是已存在的邮件槽的那一端。
还有需要提及的一点是,客户端在使用邮槽发送数据的时候只有当数据的长度<425字节时,
才可以被广播给多个服务器,如果消息的长度>425字节的话,那么在这种情形下,
邮槽是不支持广播通信的。
邮槽的实现
首先是服务端调用CreateMailslot函数,这个函数会将创建邮件槽的请求传递给内核的系统服务,
也就是NtCreateMailslot函数,而NtCreateMailslotFile这个函数会到达底层的邮槽驱动程序,
也就是msfs.sys,然后一些创建邮槽的工作就交给邮槽驱动程序来完成了,对于底层驱动,这里不作介绍,
而在高层,我们也就只需要调用CreateMailslot函数就可以实现创建邮槽了。
邮槽的创建
下面我们就来看看这个CreateMailslot函数了:
该函数利用指定的名称来创建一个邮槽,然后返回所创建的邮槽的句柄。
参数lpName指定了将要创建的邮槽的名称,该名称的格式必须为\\.\mailslot\MailslotName。
在这里需要注意的是两个斜杠后的那个“.”,在这里使用圆点代表的是本地机器,
参数nMaxMessageSize用来指定可以被写入到邮槽的单一消息的最大尺寸,
为了可以发送任意大小的消息,需要将该参数设置为0。
参数lReadTimeOut指定读取操作的超时时间间隔,以毫秒作为单位。
读取操作在超时之前可以等待一个消息被写入到邮槽中,如果将这个值设置为0,那么若没有消息可用的话,该函数将立即返回。
如果将该值设置为MAILSLOT_WAIT_FOREVER,则该函数会一直等待,直到有消息可用。
参数lpSecurityAttributes一般设置为NULL即可,即采用Windows默认的针对于邮槽的安全性。
示例:邮槽实现进程间通信
服务端实现:(简单MFC程序)
项目结构:
消息以及成员函数和成员变量的声明:
消息映射表定义:
消息处理函数:
客户端实现:(简单MFC程序)
项目结构:
消息以及成员函数和成员变量的声明:
消息映射表定义:
消息处理函数:
效果展示:
首先启动服务端进程并单击创建按钮:
然后启动客户端进程,并在客户端程序文本框中输入数据,然后单击发送按钮:
然后回到服务端程序中,并且单击接收按钮:
从上面的截图中可以看出,通过邮槽确实实现了从客户端进程向服务端进程发送数据。
当然上面的Demo中的服务端和客户端都是在本地机器上实现的,
如果想要实现本地进程和远程进程通信的话,
只需在客户端调用CreateFile打开邮槽时,将下面截图中标记的圆点置换为远程服务器的名称即可以实现了。
结束语
对于邮槽呢,其实还是蛮简单的,
在服务端的话,也就只需要在服务端调用CreateMailslot创建一个邮槽,
然后再在服务端调用ReadFile来等待读取数据即可以了,
而在客户端的话,也就只需要调用CreateFile来打开一个已经在服务端创建好的邮槽,
然后再调用WriteFile往这个邮槽中写入数据就可以了。
也就是说,对于邮槽的话,也就那么点东西需要介绍,
但是通过前面的介绍我们也很容易知道,对于通过利用邮槽来实现本地进程和远程进程的通信还是有缺陷的,
缺陷就是对于邮槽来说,服务端只能接收来自客户端的数据,而不能给客户端发送数据,
而客户端的话,则只能给服务端发送数据,而不能接收服务端发送过来的数据(事实上,服务端也发送不了)。
如果要实现客户端可以发送数据给服务端,同时也能接收来自服务端的数据,
而服务端也可以发送数据给客户端,并且服务端也可以接收到来自客户端的数据的话,
那需要利用另外的进程间通信的手段了,对于这点,留到下一篇博文介绍。
前面的一篇博文介绍了进程之间通信的一种最为简单的方式,
也就是在本地进程之间通过剪贴板来实现进程间通信,而剪贴板自有其缺陷,
很显然的是,剪贴板只能在本地机器上实现,
无法实现本地进程与远程服务器上的进程之间的通信,
那么有没有办法实现本地进程和远程进程的通信呢?
办法自然是有的,要是实在搞不出,
我拿Socket来实现本地进程和远程进程的通信来实现也是可以的,
但是你想啊,要用Socket来实现本地进程和远程进程之间的通信,
那不仅我要在本地进程中加一堆的Socket代码,
并且服务器上的进程中也是需要加一堆的Socket代码的,
那不搞死人去,也太麻烦了吧,所以不行不行,得换一种方案。
下面就来介绍一种超级无敌简单的方案,其可以用来实现本地进程与远程进程之间的通信,
那就是通过邮槽来实现。
邮槽定义
邮槽(Mailslot)也称为邮件槽,其是Windows提供的一种用来实现进程间通信的手段,
其提供的是基于不可靠的,并且是单向数据传输的服务。
邮件槽只支持单向数据传输,也就是服务器只能接收数据,而客户端只能发送数据,
何为服务端?何为客户端?
服务端就是创建邮槽的那一端,而客户端就是已存在的邮件槽的那一端。
还有需要提及的一点是,客户端在使用邮槽发送数据的时候只有当数据的长度<425字节时,
才可以被广播给多个服务器,如果消息的长度>425字节的话,那么在这种情形下,
邮槽是不支持广播通信的。
邮槽的实现
首先是服务端调用CreateMailslot函数,这个函数会将创建邮件槽的请求传递给内核的系统服务,
也就是NtCreateMailslot函数,而NtCreateMailslotFile这个函数会到达底层的邮槽驱动程序,
也就是msfs.sys,然后一些创建邮槽的工作就交给邮槽驱动程序来完成了,对于底层驱动,这里不作介绍,
而在高层,我们也就只需要调用CreateMailslot函数就可以实现创建邮槽了。
邮槽的创建
下面我们就来看看这个CreateMailslot函数了:
该函数利用指定的名称来创建一个邮槽,然后返回所创建的邮槽的句柄。
HANDLEWINAPICreateMailslot(
__inLPCTSTRlpName,
__inDWORDnMaxMessageSize,
__inDWORDlReadTimeout,
__in_optLPSECURITY_ATTRIBUTESlpSecurityAttributes
);
参数lpName指定了将要创建的邮槽的名称,该名称的格式必须为
在这里需要注意的是两个斜杠后的那个“.”,在这里使用圆点代表的是本地机器,
参数nMaxMessageSize用来指定可以被写入到邮槽的单一消息的最大尺寸,
为了可以发送任意大小的消息,需要将该参数设置为0。
参数lReadTimeOut指定读取操作的超时时间间隔,以毫秒作为单位。
读取操作在超时之前可以等待一个消息被写入到邮槽中,如果将这个值设置为0,那么若没有消息可用的话,该函数将立即返回。
如果将该值设置为MAILSLOT_WAIT_FOREVER,则该函数会一直等待,直到有消息可用。
参数lpSecurityAttributes一般设置为NULL即可,即采用Windows默认的针对于邮槽的安全性。
示例:邮槽实现进程间通信
服务端实现:(简单MFC程序)
项目结构:
消息以及成员函数和成员变量的声明:
//实现
protected:
HICONm_hIcon;
//生成的消息映射函数
virtualBOOLOnInitDialog();
afx_msgvoidOnPaint();
afx_msgHCURSOROnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msgvoidOnBnClickedBtnExit();
afx_msgvoidOnBnClickedBtnRecv();
afx_msgvoidOnBnClickedBtnCreate();
//定义一个用来创建线程的成员函数
HANDLECreateRecvThread(LPVOIDlpParameter,DWORDthreadFlag,LPDWORDlpThreadID);
//控件变量:用来接收用户输入的数据
CEditm_RecvEdit;
//成员变量:用来保存创建的邮件槽句柄
HANDLEm_hMailslot;
消息映射表定义:
//用来定义邮槽发送和接收的最大数据字节数
constintmaxDataLen=424;
//用来接收由客户端发送过来的数据
char*pStrRecvData;
CMailSlotServerDlg::CMailSlotServerDlg(CWnd*pParent/*=NULL*/)
:CDialogEx(CMailSlotServerDlg::IDD,pParent)
{
m_hIcon=AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_hMailslot=NULL;
//给用来接收数据的指针变量分配内存并清为0
pStrRecvData=newchar[maxDataLen];
memset(pStrRecvData,0,maxDataLen);
}
voidCMailSlotServerDlg::DoDataExchange(CDataExchange*pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX,IDC_EDIT_MAILSLOT,m_RecvEdit);
}
BEGIN_MESSAGE_MAP(CMailSlotServerDlg,CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(ID_BTN_EXIT,&CMailSlotServerDlg::OnBnClickedBtnExit)
ON_BN_CLICKED(ID_BTN_RECV,&CMailSlotServerDlg::OnBnClickedBtnRecv)
ON_BN_CLICKED(ID_BTN_CREATE,&CMailSlotServerDlg::OnBnClickedBtnCreate)
END_MESSAGE_MAP()
消息处理函数:
//退出按钮的消息处理例程
voidCMailSlotServerDlg::OnBnClickedBtnExit()
{
CDialogEx::OnOK();
}
//创建按钮的消息处理
voidCMailSlotServerDlg::OnBnClickedBtnCreate()
{
//创建名为ZacharyMailSlot的邮槽
this->m_hMailslot=CreateMailslot(TEXT("\\\\.\\mailslot\\ZacharyMailSlot"),0,
MAILSLOT_WAIT_FOREVER,NULL);
if(INVALID_HANDLE_VALUE==this->m_hMailslot)
{
MessageBox(TEXT("创建邮槽失败..."),TEXT("提示"),MB_ICONERROR);
return;
}
}
//接收按钮的消息处理
voidCMailSlotServerDlg::OnBnClickedBtnRecv()
{
CStringcStrRecvData;
DWORDdwRead;
//创建接收数据的线程,将邮槽句柄传递给线程
CreateRecvThread((LPVOID)this->m_hMailslot,0,NULL);
cStrRecvData=pStrRecvData;
this->m_RecvEdit.SetWindowText(cStrRecvData);
UpdateData(FALSE);
}
//线程处理函数
DWORDWINAPIRecvThreadProc(LPVOIDlpPrameter)
{
HANDLEhRecvMailSlot;
DWORDdwRead;
hRecvMailSlot=(HANDLE)lpPrameter;
//利用传进来的邮槽句柄接收收据,并将数据存放到pStrRecvData中
if(!ReadFile(hRecvMailSlot,pStrRecvData,maxDataLen,&dwRead,NULL))
{
returnNULL;
}
//关闭邮槽
CloseHandle(hRecvMailSlot);
returnNULL;
}
HANDLECMailSlotServerDlg::CreateRecvThread(LPVOIDlpParameter,DWORDthreadFlag,LPDWORDlpThreadID)
{
//创建一个线程
returnCreateThread(NULL,0,RecvThreadProc,lpParameter,threadFlag,lpThreadID);
}
客户端实现:(简单MFC程序)
项目结构:
消息以及成员函数和成员变量的声明:
//实现
protected:
HICONm_hIcon;
//生成的消息映射函数
virtualBOOLOnInitDialog();
afx_msgvoidOnPaint();
afx_msgHCURSOROnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msgvoidOnBnClickedBtnExit();
afx_msgvoidOnBnClickedBtnSend();
CEditm_SendEdit;
消息映射表定义:
constintmaxDataLen=424;
CMailSlotClientDlg::CMailSlotClientDlg(CWnd*pParent/*=NULL*/)
:CDialogEx(CMailSlotClientDlg::IDD,pParent)
{
m_hIcon=AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
voidCMailSlotClientDlg::DoDataExchange(CDataExchange*pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX,IDC_EDIT_SEND,m_SendEdit);
}
BEGIN_MESSAGE_MAP(CMailSlotClientDlg,CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(ID_BTN_EXIT,&CMailSlotClientDlg::OnBnClickedBtnExit)
ON_BN_CLICKED(ID_BTN_SEND,&CMailSlotClientDlg::OnBnClickedBtnSend)
END_MESSAGE_MAP()
消息处理函数:
//退出按钮的消息处理例程
voidCMailSlotClientDlg::OnBnClickedBtnExit()
{
CDialogEx::OnOK();
}
//发送数据的消息处理例程
voidCMailSlotClientDlg::OnBnClickedBtnSend()
{
UpdateData();
if(this->m_SendEdit.GetWindowTextLength()>0&&
this->m_SendEdit.GetWindowTextLength()<maxDataLen)
{
HANDLEhSendMailSlot;
CStringcStrSendData;
DWORDdwWrite;
char*pSendBuf;
//打开由服务端创建的邮件槽
hSendMailSlot=CreateFile(TEXT("\\\\.\\mailslot\\ZacharyMailSlot"),
GENERIC_WRITE,FILE_SHARE_READ,NULL,
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(INVALID_HANDLE_VALUE==hSendMailSlot)
{
MessageBox(TEXT("打开邮槽失败..."),TEXT("提示"),MB_ICONERROR);
return;
}
this->m_SendEdit.GetWindowText(cStrSendData);
//需要将Unicode字符转换为ASCII字符发送
pSendBuf=newchar[cStrSendData.GetLength()+1];
memset(pSendBuf,0,sizeof(cStrSendData.GetLength()+1));
for(inti=0;i<cStrSendData.GetLength();i++)
{
pSendBuf[i]=cStrSendData.GetAt(i);
}
//通过邮件槽向服务端发送数据
if(!WriteFile(hSendMailSlot,pSendBuf,cStrSendData.GetLength(),&dwWrite,NULL))
{
MessageBox(TEXT("写入数据失败..."),TEXT("提示"),MB_ICONERROR);
CloseHandle(hSendMailSlot);
return;
}
MessageBox(TEXT("写入数据成功..."),TEXT("提示"),MB_ICONINFORMATION);
}
}
效果展示:
首先启动服务端进程并单击创建按钮:
然后启动客户端进程,并在客户端程序文本框中输入数据,然后单击发送按钮:
然后回到服务端程序中,并且单击接收按钮:
从上面的截图中可以看出,通过邮槽确实实现了从客户端进程向服务端进程发送数据。
当然上面的Demo中的服务端和客户端都是在本地机器上实现的,
如果想要实现本地进程和远程进程通信的话,
只需在客户端调用CreateFile打开邮槽时,将下面截图中标记的圆点置换为远程服务器的名称即可以实现了。
结束语
对于邮槽呢,其实还是蛮简单的,
在服务端的话,也就只需要在服务端调用CreateMailslot创建一个邮槽,
然后再在服务端调用ReadFile来等待读取数据即可以了,
而在客户端的话,也就只需要调用CreateFile来打开一个已经在服务端创建好的邮槽,
然后再调用WriteFile往这个邮槽中写入数据就可以了。
也就是说,对于邮槽的话,也就那么点东西需要介绍,
但是通过前面的介绍我们也很容易知道,对于通过利用邮槽来实现本地进程和远程进程的通信还是有缺陷的,
缺陷就是对于邮槽来说,服务端只能接收来自客户端的数据,而不能给客户端发送数据,
而客户端的话,则只能给服务端发送数据,而不能接收服务端发送过来的数据(事实上,服务端也发送不了)。
如果要实现客户端可以发送数据给服务端,同时也能接收来自服务端的数据,
而服务端也可以发送数据给客户端,并且服务端也可以接收到来自客户端的数据的话,
那需要利用另外的进程间通信的手段了,对于这点,留到下一篇博文介绍。
相关文章推荐
- 进程间通信详解 - 邮槽实现
- 进程间通信详解 - 命名管道实现
- 进程间通信详解 - 剪贴板实现
- Mailslot,利用邮槽实现进程间通信
- 进程间通信 - 邮槽实现
- 进程间通信详解 - 命名管道实现
- 博文进程间通信详解 - 命名管道实现(转)中 NamedPipeServer.cpp文件总结
- 进程间通信 - 邮槽实现
- 进程间通信详解 - 剪贴板实现
- 进程间通信详解 - 匿名管道实现
- 进程间通信详解 - 动态链接库实现
- 进程间通信 - 邮槽实现
- 进程间通信详解 - 动态链接库实现
- 进程间通信详解 - 剪贴板实现
- 进程间通信详解 - 命名管道实现
- Linux消息队列实现进程间通信实例详解
- 详解Android 进程间通信的几种实现方式
- 进程间通信 - 邮槽实现
- 详解Android 进程间通信的几种实现方式
- 使用邮槽实现进程间通信的技巧