Chinaunix首页 | 论坛 | 博客
  • 博客访问: 593558
  • 博文数量: 98
  • 博客积分: 4045
  • 博客等级: 上校
  • 技术积分: 1157
  • 用 户 组: 普通用户
  • 注册时间: 2006-12-31 16:56
文章分类

全部博文(98)

文章存档

2010年(7)

2009年(15)

2007年(73)

2006年(3)

我的朋友

分类: WINDOWS

2009-09-07 15:11:25

在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
阅读(4386) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~