//摘自【Win32 行程通讯的观念与技术】
作者: 钱达智 from VCL-Team
本文原发表於微电脑传真, 1997/11
MailSlot
执行DemoSMem时如果让你有广播的感觉,接下来要说的MailSlot会让你更有广播的感觉,而且它是可以跨越机器边界向网路广播的。从字面上看来,这像是与寄信有关的通讯机制,实际上它的行为也的确与其名称相符合。MailSlot就像是你的信箱,只要知道地址,任何人都可以寄信给你,不过,只有你才可以打开信箱读信。
MailSlot是一种由系统维护的虚拟档案,建立并拥有Mailslot的行程扮演Server.的角色,其他的行程包含MailSlot Server本身的行程均可以开启MailSlot写入讯息,不过,只有MailSlot Server可以读取资料的内容。这是个单一Server多个Client的机制,同时,资料只允许由Client对Server单向传送。
我想你可能也习惯了,要产生一个MailSlot物件大概也需要一个识别名称吧!
说不定连CreateMailSlot()函数名称都猜得一字不差。不过,这次的名称可不像先前那样可以随便高兴取什麽就取什麽的,它具有以下的固定格式:
file://ServerName/mailslot/[path]name
我第一次看到时心想: 天哪! 这该怎麽填呀? 边举例边说明会比较容易懂
//./mailslot/MyMailSlotName MailSlot的识别名称一定从「//」双倒斜线开始。接下来的是机器的名称或组群网域的名称,这 的「.」句号代表的是行程所在的那部机器。再来是「/mailslot」,对於MailSlot,一定是这个单字照抄就是了。最後则是你自订的MailSlot名字。先前提到MailSlot实际上是特殊的虚拟档案,所以,要当它是档名应该也是说得通的。
的确,援引我们对於档案系统的概念,MailSlot的识别名称就像路径档名一样,可以经过适当的阶层加分类管理,例如: //./mailslot/Account/Note。最後再看一个例子: //*/mailslot/MyMailSlotName,其中「*」指的是群组内的所有机器。
说得够多了,让我们动手做做看吧! 首先是建立MailSlot Server的例子,取自本文所附的ChienIPC这个程式单元
procedure TMailSlotServer.Open;
var
ASlotName: AnsiString;
begin
if FActive then Exit;
// 构成 Mailslot 识别名称
ASlotName := '//' + FServerName + '/mailslot/' + FSlotName;
FHandle := CreateMailslot(
pchar(ASlotName), // MailSlot 识别名称
0, // 讯息长度的最大值,设为零表示不限
MAILSLOT_WAIT_FOREVER, // read time-out
nil); // 安全属性,先暂时采用预设值
if FHandle = INVALID_HANDLE_VALUE then
FActive := False
else
begin
FActive := True;
FWaitThread.Resume;
end;
end;
再强调一次,只有MailSlolt Server才可以读取资料,读取的方法是先以GetMailslotInfo()侦测讯息的长度与数量,然後以回圈逐一配置记忆体并以ReadFile()读出资料(别忘了MailSlot也是档案),以下是一则范例:
procedure TMailSlotServer.ReadFromMailSlot;
var
NextSize: DWORD;
MessageCount: DWORD;
Result: BOOL;
Buffer: pchar;
begin
if FHandle = INVALID_HANDLE_VALUE then Exit;
// 侦测 MailSlot 中是否有资料
Result := GetMailslotInfo(Fhandle, nil,
NextSize, @MessageCount, nil);
if not Result or (NextSize = MAILSLOT_NO_MESSAGE) then
Exit;
// 如果还有资料 (MessageCount <> 0),逐一读出资料
while Result and (MessageCount <> 0) do
begin
// 资料的长度
Buffer := AllocMem(NextSize + 1);
try
// 读出资料
FileRead(Fhandle, Buffer^, NextSize);
if Assigned(FOnDataAvailable) then
FOnDataAvailable(Self, StrPas(Buffer));
finally
FreeMem(Buffer, NextSize + 1);
end;
// 继续看看 MailSlot 中还有没有资料
Result := GetMailslotInfo(Fhandle, nil,
NextSize, @MessageCount, nil);
end;
end;
至於MailSlot的Client程式则没有什麽好说的,就当是档案迳行开启与写入即可:
procedure TMailSlotClient.Open;
var
ASlotName: string;
begin
if FActive then Exit;
// MailSlot 的识别名称
ASlotName := '//' + FServerName + '/mailslot/' + FSlotName;
// 开启 MailSlot(档案)
FHandle := CreateFile(pchar(ASlotName),
GENERIC_WRITE, // Client 端对於 MailSlot 只能写入
FILE_SHARE_READ, // 设定为可供分享读取
Nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
FActive := FHandle <> INVALID_HANDLE_VALUE;
end;
function TMailSlotClient.WriteIntoMailSlot(
const Data: string): integer;
begin
Result := 0;
if FHandle = INVALID_HANDLE_VALUE then Exit;
Result := FileWrite(Fhandle, Data[1], Length(Data));
end;
稍早提到MailSlot适合於跨越机器边界的网路广播, 可是我也说明了只有MailSlot Server才可以读取资料,那要怎麽广播啊?答案在於MailSlot的名称。别的机器如果也用相同的名称建立MailSlot Server,一旦任一个Client对某一个MailSlot(也是经由名称来叁考)送出讯息,这份讯息会游向网路节点上各个指定同名的MailSlot,这样子就达成广播的效果。至於讯息是怎麽流来流去的,就留给系统与网路底层去伤脑筋了,程式只管以档案写入资料的方式送出资料即可。
使用MailSlot时很可能你会遇到讯息重覆的问题;也就是说,虽然MailSlot Client端只写了一个讯息,但相同的讯息MailSlot Server却可能收到两份。原因是这样的:由於Win32多重通讯协定的缘故,MailSlot在广播时,并不知道到底该采用哪一条路径,於是便各种可能的通路都传了一份。情况有点像在发布台风警报,我们在电视,广播与网路都同时会晓得有台风要来的消息。解决的方法是在资料开头处加上一些控制用的编号代码,Server据以判断是否是相同的资料。
像MailSlot这样的通讯机制可以应用在哪些场合呢? 着名的例子是WinPopup,刚才我也写了一支阳春的, 次图是MyWinpop.exe 执行的情况。由於MailSlot广播的特性,十分适合网管时用来知会使用者重要的讯息,此外,MIS系统也可以用它适时的报告异常状况,各使用者如果在「开始┃启动」中都放置这支小程式,彼此便可以之交换讯息,当讯号进来时,也会立即显示讯息的内容。
图: MyWinPop.exe执行情形
当然,你还可以想得到其他的应用。像我就觉得它很适合用来作为程式除错工具,不仅可以将程式执行的过程与情况记录下来,而且程式在网路上各节点的执行状况也将源源而来,这是一般的测试方法所不容易达成的效果。