您的位置:首页 > 其它

进程间通信详解 - 邮槽实现

2012-07-24 21:57 239 查看
引子

前面的一篇博文介绍了进程之间通信的一种最为简单的方式,

也就是在本地进程之间通过剪贴板来实现进程间通信,而剪贴板自有其缺陷,

很显然的是,剪贴板只能在本地机器上实现,

无法实现本地进程与远程服务器上的进程之间的通信,

那么有没有办法实现本地进程和远程进程的通信呢?

办法自然是有的,要是实在搞不出,

我拿Socket来实现本地进程和远程进程的通信来实现也是可以的,

但是你想啊,要用Socket来实现本地进程和远程进程之间的通信,

那不仅我要在本地进程中加一堆的Socket代码,

并且服务器上的进程中也是需要加一堆的Socket代码的,

那不搞死人去,也太麻烦了吧,所以不行不行,得换一种方案。

下面就来介绍一种超级无敌简单的方案,其可以用来实现本地进程与远程进程之间的通信,

那就是通过邮槽来实现。

邮槽定义

邮槽(Mailslot)也称为邮件槽,其是Windows提供的一种用来实现进程间通信的手段,

其提供的是基于不可靠的,并且是单向数据传输的服务。

邮件槽只支持单向数据传输,也就是服务器只能接收数据,而客户端只能发送数据,

何为服务端?何为客户端?

服务端就是创建邮槽的那一端,而客户端就是已存在的邮件槽的那一端。

还有需要提及的一点是,客户端在使用邮槽发送数据的时候只有当数据的长度<425字节时,

才可以被广播给多个服务器,如果消息的长度>425字节的话,那么在这种情形下,

邮槽是不支持广播通信的。

邮槽的实现

首先是服务端调用CreateMailslot函数,这个函数会将创建邮件槽的请求传递给内核的系统服务,

也就是NtCreateMailslot函数,而NtCreateMailslotFile这个函数会到达底层的邮槽驱动程序,

也就是msfs.sys,然后一些创建邮槽的工作就交给邮槽驱动程序来完成了,对于底层驱动,这里不作介绍,

而在高层,我们也就只需要调用CreateMailslot函数就可以实现创建邮槽了。

邮槽的创建

下面我们就来看看这个CreateMailslot函数了:

该函数利用指定的名称来创建一个邮槽,然后返回所创建的邮槽的句柄。
HANDLEWINAPICreateMailslot(

__inLPCTSTRlpName,

__inDWORDnMaxMessageSize,

__inDWORDlReadTimeout,

__in_optLPSECURITY_ATTRIBUTESlpSecurityAttributes

);


参数lpName指定了将要创建的邮槽的名称,该名称的格式必须为\\.\mailslot\MailslotName

在这里需要注意的是两个斜杠后的那个“.”,在这里使用圆点代表的是本地机器,

参数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往这个邮槽中写入数据就可以了。

也就是说,对于邮槽的话,也就那么点东西需要介绍,

但是通过前面的介绍我们也很容易知道,对于通过利用邮槽来实现本地进程和远程进程的通信还是有缺陷的,

缺陷就是对于邮槽来说,服务端只能接收来自客户端的数据,而不能给客户端发送数据,

而客户端的话,则只能给服务端发送数据,而不能接收服务端发送过来的数据(事实上,服务端也发送不了)。

如果要实现客户端可以发送数据给服务端,同时也能接收来自服务端的数据,

而服务端也可以发送数据给客户端,并且服务端也可以接收到来自客户端的数据的话,

那需要利用另外的进程间通信的手段了,对于这点,留到下一篇博文介绍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: