分享

邮槽

 MikeDoc 2011-02-23
Microsoft Windows NT, Windows 2000, Window 95 和Windows 98 ----但不包括Windows CE----提供了一种简单的单向的"进程间通信"(interprocess communication,  IPC)机制. 这个机制的名字非常古怪,叫做"邮槽(Mailslot).用最简单的话来说,通过邮槽,客户机进程可将消息传送或广播给一个或多个服务 器进程.

1.邮槽实施细节

    邮槽是围绕Windows文件系统接口设计出来的.客户机和服务器应用需要使用标准的Win32文件系统I/O(输入/输出)函数,比如ReadFile 和WriteFile等等,以便在邮槽上收发数据,同时利用Win32文件系统的命名规则.邮槽必须依赖Windows重定向器,通过一个"邮槽文件系 统"(Mailslot File System ,   MSFS), 来创建及标识邮槽.

1.1 邮槽的名字

      对邮槽进行标识时,需遵守下述命名规则:

      \\server\Mailslot\[path]name

     请将上述字串分为三段来看: \\server, \Mailslot和\[path]name.第一部分\\server对 应于服务器的名字,我们要在上面创建邮槽,并在上面运行服务器程序.第二部分\Mailslot是一个"硬编码"的固定字串,用于告诉系统这个文件名从属 于MSFS.而第三部分\[path]name则允许应用程序独一无二地定义及标识一个邮槽名.其中,"path"代表路径,可指定多级目录.举个例子来 说,对一个邮槽进行标识时,下面这些形式的名字都是合法的(注意Mailslot不得变化,必须原文照输).

     \\Oreo\Mailslot\Mymailslot

1.2 消息的长度

    邮槽通常用"数据报"(Datagram)在网络上传递消息.数据报实际是一些小数据包,只不过要以"无连接"的形式,通过网络进行传输."无连接"意味 着每个数据包在发给接收者之后,不要求对方提供包的收到确认信息.显然,这是一种"不可靠"的数据传输,因为无法保证消息肯定能正确送达.然而,通过无连 接传输,我们确实可将消息从一个客户机广播给多个服务器.当然,上述情况也有例外.在Windows NT 和Windows 2000中,假如消息的长度超过424个字节.

    若消息长度超过426字节,便必须在一个SMB会话之上,通过一种"面向连接"的协议进行传输,而不再采用无连接的"数据报"形式.这样一来,在消息较大 的情况下,便可保证它们的稳定,高效传输.然而,此时再也不能将一条消息从客户机广播给多个服务器.对于"面向连接"的传输来说,它必然是一种"一对一" 通信:一个客户对一个服务器,在不同的进程之间,使用"面向连接"的传输方式,往往可保证数据传输的可靠性.

1.3 应用程序的编译

    用Microsoft Visual C++编制一个邮槽客户机或服务器应用程序时,必须在程序文件中将Winbase.h这个包容文件包括在内.假如已经包含了Windows.h(大多数应 用程序都会这样), 那么也可将Winbase.h省去.此外,应用程序也要负责建立与Kernel32.lib的链接,这通常需要用Visual C++链接器标志进行配置.

 1.4错误代码

    开发邮槽客户机和服务器应用时,所有Win32 API函数(CreateFile和CreateMailslot除外)在调用失败的情况下,都会返回0值.CreateFile和 CreateMailslot这两个API却会返回INVALID_HANDLE_VALUE(无效句柄值)。若这些API函数调用失败,应用程序即应调 用GetLastError函数,来接收与此次失败有关的特殊信息。至于所有错误代码的一个完整列,可参考Winerror.h

2. 基本客户机/服务器

    邮槽建立了一个简单的客户机/服务器设计体系。在这个体系中,数据只能从客户机传到服务器,数据通信是单向进行的。服务器进程的职责是创建一个邮槽,而且 是能从邮槽读取数据的唯一一个进程。邮槽客户机进程则负责打开邮槽“实例”,该进程是能够向其中写入数据的唯一一种进程。

2.1 邮槽服务器的详情

    若想实现一个邮槽, 要求开发一个服务器应用,来负责邮槽的创建。下述步骤解释了如何编写一个基本的服务器应用:

    1)用CreateMailslot  API 函数创建一个邮槽句柄.

    2)调用ReadFile API函数,并使用现成的邮槽句柄,从任何客户机接收数据。

    3)用CloseHandle这个API函数,关闭邮槽句柄。

    可以看出,要开发一个邮槽服务器程序,只需使用极少的API调用。服务器进程是用CreateMailslot这个API调用来创建邮槽的。定义如下:

        HANDLE CreateMailslot(
                  LPCTSTR  lpName,
                  DWORD    nMaxMessageSize,
                  DWORD    lReadTimeout,
                  LPSECURITY_ATTRIBUTES  lpSecurityAttributes
           );

          其中, 第一个参数lpName指定邮槽的名字,名字的格式如下:
           \\.\Mailslot\[path]name


          要注意的是,服务器的名字用一个小数点来表示,亦即服务器就是本地机器。这样做是很有必要的, 因为我们不能在远程计算机上创建邮槽。在lpName参数中,名字必须以一种独一无二的形式表达。可将它设为一个独立的名字,也可以在它前面加上一个完整 的目录路径。

    nMaxMessageSize参数定义的是可写入邮槽的一条消息的最大长度(以字节为单位)。假如客户机写入的字节数多于nMaxMessageSize的设置,服务器便不会接收这条消息。若将它的值设为0,服务器便会接收任意长度的消息。

    在一个邮槽上,读操作可以等待或不等待这两种模式进行,具体由lReadTimeout参数决定。它以毫秒为单位,指定了读操作需要等候进入消息的时间。 若将它的值设为MAILSLOT_WAIT_FOREVER,那么在进入的数据可以读取之前,读操作便会无限期地等待下去。若设为0,读操作就会立即返 回。

    lpSecurityAttributes参数决定了为邮槽施加的访问控制权限。在Windows NT和Windows 2000中,这个参数只实现了一部分,所以同时还应指定一个null(空)参数。在邮槽上,唯一能够施加的安全措施是针对本地I/O进行的----客户机 试图将服务器的名字设为小数点(.),以打开一个邮槽。要想绕过这种安全机制,客户机可指定服务器的实际名字,而不是一个小数点,亦即相当于发出一个远程 I/O调用。在Windows NT和Windows 2000中,并未针对远程I/O而实现lpSecurityAttributes参数,因为假如每次发出一条消息时,都在客户机与服务器之间建立一个授权 会话,那么效率会显得十分低下。因此,邮槽仅一部分符合标准文件系统采用的Windows NT和Windows 2000安全模型。结果便是,网络中的任何邮槽客户机都可将数据发给服务器。

    用一个有效的句柄创建了邮槽之后,便可开始数据的实际读取。服务器是唯一能从邮槽读入数据的进程。服务器应使用ReadFile这个Win32函数,来进行数据读取。对ReadFile的定义如下:

         BOOL ReadFile(
                  HANDLE       hFile,
                  LPVOID         lpBuffer,
                  DWORD        nNumberOfBytesToRead,
                  LPDWORD    lpNumberOfBytesRead,
                  LPOVERLAPPED  lpOverlapped
          );

        CreateMailslot会返回一个句柄hFile. lpBuffer和nNumberOfBytesToRead参数决定了可从邮槽读入多少数据。特别值得注意的是,这个缓冲区的大小应该比来自 CreateMailslot API调用的nMaxMessageSize参数的设置大。此外,缓冲区应该大于邮槽上的进入消息; 如果不够大,ReadFile调用便会失败,并返回一个ERROR_INSUFFICIENT_BUFFER错误。 lpNumberOfBytesRead参数用于在ReadFile操作完成后,报告读入的实际字节数量。

     利用lpOverlapped参数,我们可以通过异步方式,进行数据的读取。该参数采用的是Win32重叠I/O机制,在默认情况下,ReadFile操 作会处于暂停状态,直到有数据可以读入为止。重叠I/O只能在Windows NT和windows 20000上完成;如果使用的操作系统是Windows 95或Windows 98,那么应将该参数设为NULL。在程序清单中我们进一步阐释。


    // Serverl.cpp
    #include <windows.h>
    #include <stdio.h>

    void main(void)
    {
         HANDLE   Mailslot;
         char            buffer[256];
         DWORD    NumberOfBytesRead;

          // Create the mailslot
          if ((Mailslot = CreateMailslot(\\\\.\\Mailslot\\Myslot,  0,
                MAILSLOT_WAIT_FOREVER, NULL)) == INVALID_HANDLE_VALUE)
          {
                      printf("Failed to create a mailslot %d\n", GetLastError());
                      return;
           }

           // Read data from the mailslot  forever!
           while (ReadFile(Mailslot, buffer, 256, &NumberOfBytesRead, NULL) != 0)
           {
                         printf("%.*s\n",   NumberOfBytesRead, buffer);
           }
      }


2.2 邮槽客户机的详情

    要想实现一个客户机,需要开发一个应用程序,对一个现有的邮槽进行引用和写入.下述步骤解释了如何编写一个基本的客户机应用:

    1)使用CreateFile这个API函数,针对想向其传送数据的邮槽,打开指向它的一个引用句柄.

    2)调用WriteFile这个API函数, 向邮槽写入数据.

    3) 完成了数据的写入后,用CloseHandle这个API函数,关闭打开的邮槽句柄.

      如前所述, 采用一种"无连接"的形式,邮槽客户机同邮槽服务器通信.客户机打开指向邮槽的一个引用句柄时,实际并不建立同邮槽服务器的一个连接.要想对一个邮槽进行引用需要使用CreateFile这个API调用.对它的定义如下:

              HANDLE CreateFile(
                    LPCTSTR         lpFileName,
                    DWORD            dwDesiredAccess,
                    DWORD            dwShareMode,
                    LPSECURITY_ATTRIBUTES   lpSecurityAttributes,
                    DWORD            dwCreatingDisposition,
                    DWORD            dwFlagsAndAttributes,
                    HANDLE           hTemplateFile
                );

    lpFileName参数用于描述一个或多个邮槽,我们可用本章早些时候介绍的邮槽命名格式,向其写入数据.dwDesiredAccess参数必须设为 GENERIC_WRITE, 因为客户机只能向服务器写入数据.dwShareMode参数必须设为FILE_SHARE_READ, 允许服务器在邮槽上打开和进行读操作.lpSecurityAttributes参数对于邮槽不会有什么效果,应将其设为NULL. dwCreatiionDisposition标志应设为OPEN_EXISTING。若一台机器既是客户机,也是服务器,这一设置便显得尤其重要 ----如果服务器没有创建邮槽,对API函数CreateFile的调用便会失败.如果服务器在远程工作,那 么.dwCreationDisposition参数便没什么意义dwFlagsAndAttributes参数应定义成 FILE_ATTRIBUTE_NORMAL. hTemplateFile参数应设为NULL.

    成功创建一个句柄后,便可开始向邮槽写入数据.请记住,作为客户机,只能将数据写入邮槽.这可以用Win32函数WriteFile来做到,定义如下:

        BOOL WriteFile(
                      HANDLE   hFile,
                      LPCVOID  lpBuffer,
                      DWORD    nNumberOfBytesToWrite,
                      LPDWORD lpNumberOfBytesWritten,
                      LPOVERLAPPED lpOverlapped
          );

    其中,hFile参数是由CreateFile返回的一个引用句柄。lpBuffer和nNumberOfBytesToWrite参数决定了有多少字节 从客户机发向服务器。一条消息的最大长度为64KB。如果当初是用一个域或星号(*)格式来创建邮槽句柄,那么在Windows NT和Windows 2000中,消息的长度限制在424字节之内; 而在Windows 95和Windows 98中,限制在64KB之内。如客户机试图发送的消息超出了这一长度限制,WriteFile函数便会失败,而且GetLastError函数会返回 ERROR_BAD_NETPATH错误。之所以会出现这一情况,是由于需要以广播数据报的形式,把消息发送给网络中的所有服务器。 lpNumberOfBytesWritten参数返回的是当WriteFile操作完成后,传给服务器的实际字节数量。

    通过lpOverlapped参数,我们可采用异步形式,将数据写入一个邮槽。由于邮槽最大的特点便是“无连接”的数据传输,所以WriteFile函数不会在I/O调用的时候暂停等候。在客户机上,这个参数应设为NULL。在程序中我们进一步阐述。

   


          // Client.cpp
          
          #include <windows.h>
          #include <stdio.h>
              
          void main(int argc,    char  *argv[])
          {
                    HANDLE   Mailslot;
                    DWORD   BytesWritten;
                    CHAR        ServerName[256];
       
                     // Accept a command line argument for the server to send
                    //   a  message to 
                    if  (argc < 2)
                    {
                              printf("Usage: client <server name>\n");
                              return;
                    }
                    
                    sprintf(ServerName,   \\\\%s\\Mailslot\\Myslot,  argv[1]);
                     
                     if ((Mailslot = CreateFile(ServerName,   GENERIC_WRITE,
                           FILE_SHARE_READ,    NULL,   OPEN_EXISTING,    FILE_ATTRIBUTE_NORMAL,
                           NULL))  == INVALID_HANDLE_VALUE)
                     {
                                   printf("CreateFile failed with error %d\n",  GetLastError());
                                   return;
                     }
                   
                     if (WriteFile(Mailslot,  "This is a test", 14,  &BytesWritten, NULL)  == 0)
                     {
                                    printf("WriteFile failed with error %d\n",  GetLastError());
                                     return;
                     }
                     printf("Wrote %d bytes \n", BytesWritten);
                     CloseHandle(Mailslot);
             }


3. 其他邮槽API

    对邮槽服务器应用来说,它可使用另外两个API函数同邮槽打交道:GetMailslotInfo和SetMailslotInfo.其中,一旦邮槽上有 消息可以传递,GetMailslotInfo函数便可负责获取消息的长度信息.利用这个函数,程序可针对长度不定的进入消息,动态地调节其缓冲 区.GetMailslotInfo函数亦可用来对进入数据进行"轮询".对GetMailslotInfo函数的定义如下:

        BOOL GetMailslotInfo(
             HANDLE    hMailslot,
              LPDWORD   lpMaxMessageSize,
             LPDWORD    lpNextSize,
             LPDWORD    lpMessageCount,
            LPDWORD     lpReadTimeout
          );

         其中, hMailslot参数指定自CreateMailslot API调用返回的一个邮槽.  lpMaxMessageSize参数设置可将多大的一条消息写入邮槽(以字节为单位).    lpNextSize参数则以字节为单位, 指出下一条消息的长度. GetMailslotInfo可能会返回一个MAILSLOT_NO_MESSAGE值,指出在邮槽之上,目前没有等待接收的消息。利用这个参数,服务 器便可在邮槽上轮询(不断地查询)是否有进入的消息,防止应用程序在ReadFile函数调用过程中“冻结”, 傻乎乎地一直等候下去.然而,用这种方式对数据进行轮询并不是一种很好的习惯.因为应用程序会连续不停地消耗宝贵的CPU资源,以检查进入的数据---- 即使根本没有需要处理的消息.这样一来,便会降低系统的总体性能.如果想防止ReadFile"冻结"或暂停执行,建立你使用Win32的重叠I/O. lpMessageCount参数指定一个缓冲区,用于接收等候读入的消息的总量.lpReadTimeout参数则指定了另一个缓冲区,它以毫秒为单 位,返回了一次读操作最多能等候多久的时间,让一条消息写入邮槽.

    SetMailslotInfo函数用于设置一个邮槽的超时值.超过这个时间,读操作便不再等候进入消息.因此,应用程序完全有能力将读操作从"冻结"状态转变成"非冻结"状态;或者相反.对SetMailslotInfo的定义如下:

          BOOL   SetMailslotInfo(
                         HANDLE         hMailslot,
                         DWORD         lReadTimeout
            );

     bMailslot参数指定一个自CreateMailslot API调用返回的邮槽.   lReadTimeout参数以毫秒为单位,指定一次读操作等候一条消息写入邮槽的最长时间.若将其设为0, 在不存在消息的前提下,读操作便会立即返回; 若设为MAILSLOT_WAIT_FOREVER, 读操作便会永远等待下去。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多