在Windows环境下,实现进程间的通信方式很多,如消息、命名管道(Name Pipes)、剪贴板(ClipBoard)等,邮槽(MailSlot)也是其中一种,这里简单的讨论一下邮槽的原理以及如何在Delphi中实现。
对于邮槽,我们并不陌生,在Windows 95/98下,使用Winpopup可以在不同计算机间进行通信,在Windows NT/2000/XP下,可以使用 Net Send或者使用微软控制台(MMC 即Microsoft Management Console)发送控制台消息。这就是邮槽的简单应用。
那么,邮槽是什么呢?邮槽是由系统来维护的一个虚拟档案,可以形象的理解为一个支持路径和文件名的虚拟文件系统。
利用邮槽的实现通信原理是什么呢?其实很简单,如果将邮槽理解为文件系统,那么其实就是创建文件、写入文件和读取文件的关系。
两台计算机使用邮槽通信,其实就是目的计算机在自己计算机上创建一个文件供写入,当其他计算机(或进程)需要给它发送消息时,就是打开这个文件,将内容写入。当目的计算机进程发现文件内容非空,就读取文件进行分析显示。
好了,弄清楚了这些,现在问题就很简单了。我们需要知道的就是:
a、如何给邮槽文件命名;
b、写入内容的格式;
c、如何创建邮槽文件;
d、如何向邮槽文件中写入内容;
e、如何从邮槽的文件中读取内容。
下面我们就一一来剖析
a、邮槽的文件名。对于邮槽文件,格式为\\ComputerName\mailslot\[path]name
对于本地邮槽文件,ComputerName使用“.”来代替,就是“\\.\MailSlot\路径\文件名”
对于写入的目标计算机的邮槽文件,就是\\目标计算机\MailSlot\路径\文件名
邮槽支持群发,如果向默认工作组/域发送(就是当前发送计算机所在工作组/域),可以使用\\*\MailSlot\路径\文件名。如果向指定工作组/域发送,可以使用\\工作组/域名\MailSlot\路径\文件名
对于Windows的信使服务,使用的邮槽文件是\\.\MailSlot\ messngr,当然我们可以任意起名,如:\\.\MailSlot\Delphi\Delphibbs,但是自定义的名称,使用Windows的信使就不能接收到了,必须得自己写程序进行接收。
b、邮槽文件的内容。我们接收到的消息类似下面的。
在 2003-9-21 16:00:00 从yzhshi到Server的消息
Hello World!
包含:接收日期、发送人、接收人、具体内容。在一条具体的消息中,包含发送人、接收人、具体内容三项内容,中间使用#0间隔。
c、创建文件。
function CreateMailslot(lpName: PChar; nMaxMessageSize: DWORD;lReadTimeout: DWORD; lpSecurityAttributes: PSecurityAttributes): THandle; stdcall;
d、向邮槽写入内容。
这个和读写普通文件一样,使用CreateFile打开文件,使用WriteFile将文件内容写入。
function CreateFile(lpFileName: PChar; dwDesiredAccess, dwShareMode: DWORD; lpSecurityAttributes: PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: DWORD;
hTemplateFile: THandle): THandle; stdcall;
function WriteFile(hFile: THandle; const Buffer; nNumberOfBytesToWrite: DWORD;
var lpNumberOfBytesWritten: DWORD; lpOverlapped: POverlapped): BOOL; stdcall;
e、读取邮槽内容
在读取邮槽内容以前,可以使用GetMailSlotInfo来判断邮槽内是否有内容,当发现有内容的时候,可以使用ReadFile来进行读取。
function GetMailslotInfo(hMailslot: THandle; lpMaxMessageSize: Pointer; var lpNextSize: DWORD; lpMessageCount, lpReadTimeout: Pointer): BOOL; stdcall;
function ReadFile(hFile: THandle; var Buffer; nNumberOfBytesToRead: DWORD; var lpNumberOfBytesRead: DWORD; lpOverlapped: POverlapped): BOOL; stdcall;
好了,了解了这些知识,我们就可以自己编写程序来实现信使服务的功能了。
a、发送消息例子:
function SendMailSlotMessage(ASender, AReceiver, AText: string): Boolean;
var
lFileHandle: THandle;
lSendText: String;
lWriteLength: DWord;
begin
lFileHandle := CreateFile(PChar('\\' + AReceiver + '\mailslot\messngr'), GENERIC_WRITE,
FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if lFileHandle <> INVALID_HANDLE_VALUE then
begin
lSendText := ASender + #0 + AReceiver + #0 + AText + #0;
Result := WriteFile(lFileHandle, Pointer(lSendText)^, Length(lSendText), lWriteLength, nil);
CloseHandle(lFileHandle);
end
else
Result := False;
end;
调用时,使用 SendMailSlotMessage('yzhshi', 'Server', 'Hello World!'); 就可以了。
b、创建邮槽:
var
FSlotHandle: THandle;
begin
FSlotHandle := CreateMailSlot('\\.\mailslot\messngr', 0, MAILSLOT_WAIT_FOREVER, nil);
if FSlotHandle = INVALID_HANDLE_VALUE then
raise Exception.Create('TMailSlotServer:邮槽创建失败!');
end;
c、从邮槽中读取内容:
procedure GetMailSlotMessage;
var
NextSize, lMsgCount, ByteRead: DWord;
ReceiveBuffer: PChar; {接收的Buffer}
i: Integer;
RecvText: String;
begin
{判断邮槽内是否存在消息}
if not GetMailSlotInfo(FSlotHandle, nil, DWORD(NextSize), @lMsgCount, nil) then
raise Exception.Create('TMailSlotServer:获取邮槽信息失败!');
if NextSize <> MAILSLOT_NO_MESSAGE then
begin
ReceiveBuffer := AllocMem(NextSize);
try
{从邮槽中读取消息}
if ReadFile(FSlotHandle, ReceiveBuffer[0], NextSize, ByteRead, nil) then
begin
RecvText := '';
for i := 0 to ByteRead - 1 do
begin
{将发送的间隔#0替换成#13}
if ReceiveBuffer[i] <> #0 then
RecvText := RecvText + ReceiveBuffer[i]
else
RecvText := RecvText + #13;
end;
ShowMessage(RecvText);
end
else
raise Exception.Create('TMailSlotServer:读取邮槽信息失败!');
finally
FreeMem(ReceiveBuffer);
end;
end;
{如果消息数据不止一条,则再次读取}
if lMsgCount > 1 then
GetMailSlotMessage;
end;
最后,再补充说明几点:
1、邮槽是基于Udp的数据连接,是一种不可靠连接,发送者不能确认接收者是否收到消息。
2、每次发送的字节数有一定长度限制,当超长的时候,发送端一般不会触发异常,导致数据丢失。
3、对于Windows NT/2000/XP,已经自身内置了信使服务,如果要使自己的程序能够接收到邮槽名称为“\\.\MailSlot\messngr”的消息,就必须首先停掉Windows自己的信使服务。办法为:打开计算机管理的服务,选择Messenger,然后停止就可以了。使用其他名称的邮槽服务则不必停止该服务。
4、细心的朋友也许会注意到。发送消息其实就是简单的写文件,那么如果我们在目标计算机建立一个共享名称为MailSlot的文件夹会出现什么呢?大家可以自己试验一下。
5、在Windows NT/2000/XP中,提供了NetMessageBufferSend这个函数,可以直接实现发送的功能。
function NetMessageBufferSend(ServerName: PWideChar; MsgName: PWideChar; FromName: PWideChar; Buf: PWideChar; BufLen: integer): Integer; stdcall; external 'netapi32.dll'
function SendNetMessage(const FromComputer, ToComputer, FromName, Text: WideString): Boolean;
begin
Result := NetMessageBufferSend(
PWideChar(FromComputer),
PWideChar(ToComputer),
PWideChar(FromName),
PWideChar(Text),
Length(Text) * SizeOf(PWideChar)
) = 0;
end;
对邮槽的操作很容易封装成控件的形式,我根据上面写的内容,形成了两个控件TMailSlotServer和TMailSlotClient,里面默认的邮槽文件名为messngr。可以实现基本的数据收发。同时附带了一个测试程序,使用Delphi5编译通过。
后记:从Borland公司联合大富翁论坛( )推出Borland ALM大赛以来,我就一直想写点东西参加,但是苦于水平有限,同时时间又很紧迫,所以一拖再拖,直到几天前看到Borland China发的帖子( http:///delphibbs/dispq.asp?lid=2185886 ),才感觉到时间的紧迫性。本来想写点软件工程、以及软件配置管理方面的心得,但是刚刚起了一个头,又放下,因为自己感觉在这些方面还有很多欠缺的。于是就只好将前些阶段摸索的关于MailSlot的知识写出来,网络上关于邮槽的介绍已经不少了,但是这个总算是自己的研究心得,重在参与嘛,于是下定决心,写出了这些。欢迎大家多提宝贵意见。
本文以及控件和例子程序下载: http:///keylife/images/u56277/MailSlot.rar
阅读(4345) | 评论(0) | 转发(0) |