管道和命名管道

 “命名管道”或“命名管线”(Named Pipes )是一种简单的进程间通信( I P C)机制,
Microsoft Windows NT,Windows 2000、Windows 95以及Windows 98均提供了对它的支持
(但不包括Windows CE)。命名管道可在同一台计算机的不同进程之间,或在跨越一个网络的
不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。用命名管道来设计应用
程序实际非常简单,并不需要事先深入掌握基层网络传送协议(如T C P / I P或I P X)的知识。这
是由于命名管道利用了微软网络提供者( M S N P)重定向器,通过一个网络,在各进程间建立
通信。这样一来,应用程序便不必关心网络协议的细节。之所以要用命名管道作为自己的网
络通信方案,一项重要的原因是它们充分利用了Windows NT及Windows 2000内建的安全特
性。
这里有一个可采纳命令管道的例子。假定我们要开发一个数据管理系统,只允许一个指
定的用户组进行操作。想像在自己的办公室中,有一部计算机,其中保存着公司的秘密。我
们要求只有公司的管理人员,才能访问及处理这些秘密。假定在自己的工作站机器上,公司
内的每名员工都可看到网络上的这台计算机。然而,我们并不希望普通员工取得对机密材料
的访问权。在这种情况下,命名管道可发挥出很好的作用,因为我们可开发一个服务器应用
程序,令其以来自客户机的请求为准,对公司的秘密进行安全操作。服务器可将客户访问限
制在管理人员身上,用Windows NT或新版Windows 2000自带的安全机制,便可非常轻松地做
到这一点。
在此要记住的一个重点是,将命名管道作为一种网络编程方案使用时,它实际上建立一
个简单的客户机/服务器数据通信体系,可在其中可靠地传输数据。本章将介绍如何来开发
一个命名管道客户机及服务器应用。首先要解释的是命名管道的命名规范(约定),然后介绍
基本的管道类型。随后,将向大家展示如何实现一个简单的服务器应用。然后以它为基础,
深入探讨高级的服务器编程技术。接下来,讲解如何开发一个简单的客户机应用。到本章末,
我们会对命名管道已知的所有问题及限制进行总结。
4.1 命名管道的实施细节
命令管道是围绕Wi n d o w s文件系统设计的一种机制,采用“命名管道文件系统”(N a m e d
Pipe File System, NPFS)接口。因此,客户机和服务器应用可利用标准的Wi n 3 2文件系统A P I
函数(如R e a d F i l e和Wr i t e F i l e)来进行数据的收发。通过这些A P I函数,应用程序便可直接利
用Wi n 3 2文件系统命名规范,以及Windows NT/Windows 2000文件系统的安全机制。N P F S依
赖于M S N P重定向器在网上进行命名管道数据的发送和接收。这样一来,便可实现接口的“与
协议无关”特性:若在自己开发的应用程序中使用命名管道在网上不同的进程间建立通信,
程序员不必关心基层网络传送协议(如T C P和I P X等等)的细节。对N P F S来说,命名管道是
用“通用命名规范”(U N C)来标识的。在第2章,我们已比较深入地探讨了U N C、Wi n d o w s
重定向器以及安全机制。
4.1.1 命名管道命名规范
命名管道的标识是采用U N C格式进行的:
/ / s e r v e r / P i p e / [ p a t h ] n a m e
上述字串可分为三部分来观看: / / s e r v e r、/ P i p e和/ [ p a t h ] n a m e。第一部分/ / s e r v e r指定一个
服务器的名字。命名管道便是在那个服务器上创建的,而且要由它对进入的连接请求进行
“监听”。第二部分/ P i p e是一个不可变化的“硬编码”字串(必须原样照录,但不用区分大小
写),用于指出该文件从属于N P F S。而第三部分/ [ p a t h ] n a m e则使应用程序可以“唯一”定义
及标定一个命名管道的名字,而且可在这里设置多级目录。举个例子来说,下述字串均是合
法的命名管道名字:
/ / m y s e r v e r / P I P E / m y p i p e
/ / Te s t s e r v e r / p i p e / c o o l d i r e c t o r y / f u n t e s t / j i m
/ / . / P i p e / E a s y n a m e d p i p e
注意就服务器字串这一部分来说(第一部分),既可表达为一个小数点( .),亦可表达为
一个实际的服务器名字。
4.1.2 字节模式及消息模式
命名管道提供了两种基本通信模式:字节模式和消息模式。在字节模式中,消息以一个
连续的字节流的形式,在客户机与服务器之间流动。这意味着,对客户机应用和服务器应用
来说,在任何一个特定的时间段内,它们不能准确知道有多少字节从管道中读入或者写入管
道。因此,在一方写入某个数量的字节,并不表示在另一方会读出等量的字节。这样一来,
客户机和服务器在传输数据的时候,便不必关心数据的内容。而在消息模式中,客户机和服
务器则通过一系列不连续的数据单位,进行数据的收发。每次在管道上发出了一条消息后,
它必须作为一条完整的消息读入。在图4 - 1中,我们对这两种管道模式进行了总结。
图4-1 字节模式和消息模式
4.1.3 应用程序的编译
用Microsoft Visual C++构建一个命名管道或服务器应用程序时,必须在自己的程序文件
中加入Wi n b a s e . h这个头文件。但假如你的应用程序已经包括了Wi n d o w s . h—大多数情况下
第4章计命名管道计计67
下载
客户机字节模式服务器
消息模式
消息1 消息2 消息3
都会如此,便可将Wi n b a s e . h忽略。此外,你的程序还需负责建立与K e r n e l 3 2 . l i b的链接关系,
后者通常是用Visual C++的链接器标志来配置的。
4.1.4 错误代码
开发命名管道客户机和服务器应用时,所有Win32 API函数(C r e a t e F i l e和C r e a t e N a m e d P i p e
除外)都会在调用失败的前提下返回0值。C r e a t e F i l e和C r e a t e N a m e d P i p e返回的则是
I N VA L I D _ H A N D L E _ VA L U E。若这两个函数中任何一个调用失败,应用程序会调用
G e t L a s t E r r o r函数,取得与这一次失败有关的特定信息。在Wi n e r r o r. h这个头文件中,以及在
本书附录C中,均提供了完整的错误代码清单。
4.2 客户机与服务器的基础
命名管道最大的特点便是建立一个简单的客户机/服务器程序设计体系。在这个体系结
构中,在客户机与服务器之间,数据既可单向传递,亦可双向流动。这一点相当重要,因为
我们可以自由地收发数据,无论应用程序是一个客户机还是一个服务器。对命名管道服务器
和客户机来说,两者最大的区别在于:服务器是唯一一个有权创建命名管道的进程,也只有
它才能接受管道客户机的连接请求。对一个客户机应用来说,它只能同一个现成的命名管道
服务器建立连接。在客户机应用和服务器应用之间,一旦建好连接,两个进程都能对标准的
Wi n 3 2函数,在管道上进行数据的读取与写入。这些函数包括R e a d F i l e和Wr i t e F i l e等等。要注
意的是,命名管道服务器应用只能在Windows NT或Windows 2000上工作——Windows 95和
Windows 98 不允许应用程序创建命名管道!正是由于存在这一限制,我们无法在两台
Windows 95或Windows 98计算机之间直接建立通信。然而, Windows 95和Windows 98客户机
可建立与Windows NT及Windows 2000计算机的正常连接。
4.2.1 服务器的细节
要想实现一个命名管道服务器,要求必须开发一个应用程序,通过它创建命名管道的一
个或多个“实例”,再由客户机进行访问。对服务器来说,管道实例实际就是一个句柄,用于
从本地或远程客户机应用接受一个连接请求。按下述步骤行事,便可写出一个最基本的服务
器应用:
1) 使用A P I函数C r e a t e N a m e d P i p e,创建一个命名管道实例句柄。
2) 使用A P I函数C o n n e c t N a m e d P i p e,在命名管道实例上监听客户机连接请求。
3) 分别使用R e a d F i l e和Wr i t e F i l e这两个A P I函数,从客户机接收数据,或将数据发给客户
机。
4) 使用A P I函数D i s c o n n e c t N a m e d P i p e,关闭命名管道连接。
5) 使用A P I函数C l o s e H a n d l e,关闭命名管道实例句柄。
首先,我们的服务器进程需要使用C r e a t e N a m e d P i p e这个A P I调用,创建一个命名管道实
例。该调用的定义如下:
68计计第一部分附传统网络API
下载
第一个参数是l p N a m e,用于指定一个命名管道的名字。这个名字采用遵守下述U N C格
式:
注意服务器的名字在此是用一个小数点表示的。换言之,它对应于本地机器(本机)。注
意不可在一台远程计算机上创建命名管道。参数的[ p a t h ] n a m e部分必须指定一个独一无二的名
字。它可以是一个单独的文件名,亦可在文件名前面加上完整的目录路径。
d w O p e n M o d e参数用于指示一个管道创建好之后,它的传输方向、I / O控制以及安全模式。
在表4 - 1中,我们总结了可以选用的所有标志设定。创建一个管道时,需要将这些标志通过
O R(或)运算组合到一起。
其中, P I P E _ A C C E S S _标志决定了在客户机与服务器之间,数据在管道上的流动方向。
可用P I P E _ A C C E S S _ D U P L E X标志以双向传输方式打开一个管道。也就是说,在客户机与服
务器之间,数据可以双向传输。除此以外,亦可使用P I P E _ A C C E S S _ I N B O U N D或者
P I P E _ A C C E S S _ O U T B O U N D标志, 以单向传输方式打开一个管道。也就是说,数据只能从
客户机传向服务器,或从服务器传向客户机。图4 - 2向大家进一步阐述了标志组合的情况,指
出数据在客户机与服务器之间如何流动。
表4-1 命名管道打开模式标志
打开模式标志说明
双向P I P E _ A C C E S S _ D U P L E X 双向式管道:服务器和客户机进程都能在管道上读
写数据
P I P E _ A C C E S S _ O U T B O U N D 数据在管道中只能从服务器朝客户机流动
P I P E _ A C C E S S _ I N B O U N D 数据在管道中只能从客户机朝服务器流动
I / O控制F I L E _ F L A G _ W R I T E _ T H R O U G H 只适用于字节模式下的管道。对那些用来向命名管
道写入数据的函数来说,除非写入的数据通过网络传
送出去,而且进入远程计算机的管道缓冲区内,否则
不会返回
F I L E _ F L A G _ O V E R L A P P E D 允许执行读、写和连接操作的函数使用重叠式I / O
安全模式W R I T E _ D A C 应用程序可对命名管道的D A C L进行写操作
A C C E S S _ S Y S T E M _ S E C U R I T Y 应用程序可对命名管道的S A C L进行写操作
W R I T E _ O W N E R 应用程序可对命名管道的“所有人”及“组” S I D,
进行写操作
接下去的一系列d w O p e n M o d e标志用于从服务器的角度出发,对一个命名管道的I / O行为
加以控制。F I L E _ F L A G _ W R I T E _ T H R O U G H标志控制着写操作,除非写入的数据通过网络实
际传送出去,而且进入远程计算机的管道缓冲区,否则负责数据写入的函数不会返回。要注
意的是,该标志只对字节模式下的命名管道有用。此时,客户机和服务器分别位于不同的计
算机上。F I L E _ F L A G _ O V E R L A P P E D标志则允许执行读、写和连接操作的函数立即返回,即
使那个函数可能会花较长的时间来完成操作。本章稍后设计一个高级的服务器程序时,还会
对这种重叠式的I / O进行深入探讨。
第4章计命名管道计计69
下载
图4-2 模式标志以及数据流向
在表4 - 1中,最后一组d w O p e n M o d e标志负责控制服务器访问安全描述符的能力,那些安全
描述符是由命名管道创建的。一个管道建好之后,假如应用程序需要修改或更新管道的安全描
述符,便应将这些标志相应地设为“允许访问”。其中,W R I T E _ D A C标志使我们的程序能够更
新管道的授权访问控制列表(D A C L);而A C C E S S _ S Y S T E M _ S E C U R I T Y允许访问管道的系统
访问控制列表(S A C L);最后,W R I T E _ O W N E R标志允许我们更改管道的所有人及组安全I D
(S I D)。例如,对于已拥有管道访问权的一名用户,假定我们现在打算拒绝他的访问,便可使
用安全A P I函数,修改管道的D A C L。在本书第2章,我们已详细讨论了D A C L、S A C L以及S I D。
在C r e a t e N a m e d P i p e这个A P I调用中,它的d w P i p e M o d e参数指定了一个管道的读、写以及等待
模式。在表4 - 2中,我们对可以选用的所有模式标志进行了总结。实际使用时,需要从每种模式类
别中拿出一个标志,再将它们通过O R(或)运算合并到一起。假如当初使用P I P E _ R E A D M O D E _
BYTE | PIPE_TYPE_BYTE模式标志,以“面向字节”的形式来打开一个管道,那么数据只能
以“字节流”的形式,在管道上进行读取和写入。也就是说,在管道上进行数据的读写时,不
必保证读和写操作一一对应关系—因为数据并不包含任何消息边界。举个例子来说,假设一
个发送者向管道写入5 0 0个字节,那么作为接收者,或许希望一次只读入1 0 0字节,直到收到所
有数据为止。要想为消息建立明确的边界,对各条消息加以区分,便需事先将管道置为“面向
消息”的模式,这是用PIPE_READMODE_MESSAGE | PIPE_TYPE_MESSAGE标志来做到的。
处在这种模式下,每一次读必须有一次写与之对应!例如,假定发送者向管道写入5 0 0字节的
一条消息,那么在接收数据时,接收者必须为R e a d F i l e函数提供一个5 0 0字节甚或更大的缓冲区。
假如接收者没有这样做, R e a d F i l e函数调用便会失败,并返回E R R O R _ M O R E _ D ATA错误。亦
可将P I P E _ T Y P E _ M E S S A G E和P I P E _ R E A D M O R E _ B Y T E组合使用,允许发送者以消息形式向
管道写入数据,同时让接收者一次可以读取任意数量的字节。在数据流中,消息的定界符会被
忽略。然而,千万不可混合使用P I P E _ T Y P E _ B Y T E标志以及P I P E _ R E A D M O R E _ M E S S A G E标
志。如果这样做,会造成C r e a t e N a m e d P i p e函数调用失败,并返回一个E R R O R _ I N VA L I D _
PA R A M E T E R(参数无效)错误。这是由于数据以字节形式写入管道时, I / O流中不存在消息定
界符的缘故。P I P E _ WA I T或P I P E _ N O WA I T标志亦可同读与写模式标志组合使用。其中,
P I P E _ WA I T标志用于将管道置为“锁定”或“等待”模式,而P I P E _ N O WA I T标志将管道置为
“非锁定”或“不等待”模式。在锁定模式中,像R e a d F i l e这样的I / O(输入/输出)操作会暂
70计计第一部分附传统网络API
下载
P I P E _ A C C E S S _ D U P L E X G E N E R I C _ R E A D I
G E N E R I C _ W R I T E
G E N E R I C _ R E A D
G E N E R I C _ W R I T E
P I P E _ A C C E S S _ O U T B O U N D
P I P E _ A C C E S S _ I N B O U N D
服务器客户机
单向
单向
双向
停,直至I / O请求完成。假如不指定任何标志,这也是一种默认的行为。而使用非锁定模式标志
(P I P E _ N O WA I T),I / O操作无论如何都会立即返回。然而,在Wi n 3 2应用程序中,不可用它来
实现异步形式的I / O。之所以提供了这个标志,只是为了保证与早期Microsoft LAN Manager 2.0
应用的向后兼容。使用R e a d F i l e和Wr i t e F i l e函数,应用程序可通过Wi n 3 2重叠式I / O,来实现异
步I / O。本章后面还会对此详述。
表4-2 命名管道的读写模式标志
模式标志说明
写P I P E _ T Y P E _ B Y T E 数据以字节流的形式,写入管道
P I P E _ T Y P E _ M E S S A G E 数据以消息流的形式,写入管道
读P I P E _ R E A D M O D E _ B Y T E 数据以字节流的形式,从管道中读入
P I P E _ R E A D M O D E _ M E S S A G E 数据以消息流的形式,从管道中读入
等待P I P E _ WA I T 允许“锁定”模式
P I P E _ N O WA I T 允许“非锁定”模式
注意PIPE_NOWAIT标志现已废弃不用,务必不要在Win32环境中用它来实现异步I/O。
它之所以仍在本书出现,只是为了保证与早期的Microsoft LAN Manager 2.0软件的“向
后兼容”。
n M a x I n s t a n c e s参数指定对一个命名管道来说,最多可创建多少个实例或管道句柄。所谓
管道的“实例”,其实就是从本地或远程客户机应用到创建那个命名管道的服务器应用程序的
一个连接。在此可接受的取值范围在1到P I P E _ U N L I M I T E D _ I N S TA N C E S(无限实例)之间。
例如,假定我们想设计一个服务器应用,令其一次最多只为五个客户连接提供服务,那么该
参数便应设为5。假如将该参数设为P I P E _ U N L I M I T E D _ I N S TA N C E S,管道实例的数量便基
本上是“无限”的,当然,仍然要受可用的系统资源的限制。
C r e a t e N a m e d P i p e函数的n O u t B u ff e r S i z e和n I n B u ff e r S i z e参数分别指定了为内部输入及输出
缓冲区长度保留的字节数量。每次创建一个命名管道实例时,都会动态决定这些长度。系统
会使用未分页的内存池(由操作系统使用的物理内存)来设置“进入”以及(或者)外出”
缓冲区。指定的缓冲区长度应当合理。首先不能过大,不至于将未分页的内存池都用光了。
但另一方面,也不应过小,造成没有足够的空间来满足标准I / O请求的需要。假如应用程序试
图写入的数据量大于指定的缓冲区长度,系统就会自己试着扩充缓冲区,用未分页的内存池
来装下数据。在实际使用中,应用程序应将这些内部缓冲区的长度设为一个合适的值,使之
与调用R e a d F i l e和Wr i t e F i l e时的发送/接收缓冲区大小相符。
n D e f a u l t Ti m e O u t参数用于指定默认的超时时间(客户机等待同一个命令管道建立连接的
最长时间),以毫秒为单位。注意这一设置只对特定的客户机应用才会产生影响—该应用通
过Wa i t N a m e P i p e函数,判断在什么时候,可用一个命名管道的实例来接受连接。本章稍后,
在开发一个命名管道客户机应用时,还会更深入地讲述这一概念。
l p S e c u r i t y A t t r i b u t e s参数允许应用程序为命名管道指定一个安全描述符,并决定一个子进
程是否能够继承新建的句柄。假如将该参数设为N U L L(空),命名管道便会获得一个默认的
安全描述符,同时句柄不可继承。默认的安全描述符允许命名管道拥有与创建它的进程相同的
安全限制以及访问控制—与第2章讲述的Windows NT及Windows 2000安全模型相符。使用
某些安全A P I函数,我们可在一个S E C U R I T Y _ D E S C R I P TO R结构中为特定的用户及用户组设
第4章计命名管道计计71
下载
置访问权限,从而向一个管道施加访问控制的限制。假如服务器希望打开对任何客户机的访问
权限,那么应该为S E C U R I T Y _ D E S C R I P TO R结构分配一个空的授权访问控制列表( D A C L)。
从C r e a t e N a m e d P i p e调用成功接收到一个句柄之后(亦即一个管道实例),便必须等待来
自一个命名管道客户机的连接。可使用C o n n e c t N a m e d P i p e这个A P I函数,来建立这个连接。
该函数的定义如下:
其中, h N a m e d P i p e参数指定自C r e a t e N a m e d P i p e调用返回的一个有效管道实例句柄。
l p O v e r l a p p e d参数则使这个A P I函数能以异步方式工作,或者说能将它置为“非锁定”模式,
前提是当初使用F I L E _ F L A G _ O V E R L A P P E D标志来创建管道。这正是大家所熟知的Wi n 3 2重
叠式I / O。假如将该参数设为N U L L,C o n n e c t N a m e d P i p e便会暂时进入“锁定”或“等候”状
态,直到客户机建立了同服务器的连接。至于重叠式I / O进一步的情况,还会在本章后面讲到
如何创建一个更高级的命名管道服务器时讨论。
一个命名管道客户机成功建立了与服务器的连接之后,C o n n e c t N a m e d P i p e这个A P I调用便会
结束。随后,服务器可用Wr i t e F i l e函数,向客户机自由地发送数据;或者使用R e a d F i l e函数,从
客户机那里接收数据。服务器完成了与一个客户机的通信之后,便应调用D i s c o n n c t N a m e d P i p e函
数,以关闭此次通信会话。在程序清单4 - 1中,我们向大家展示了如何编写一个简单的服务器
应用,令其与客户机通信。
程序清单4-1 简单的命名管道服务器
72计计第一部分附传统网络API
下载
如何构建空的授权访问控制列表(NULL DACL)
在Windows NT或Windows 2000操作系统中,应用程序使用Win32 API函数创建诸如文件
及命名管道这样的、要求安全保护的对象时,操作系统会赋予应用程序设置访问控制列表的
权力,这是通过指定一个SECURITY_ATTRIBUTES结构来进行的。该结构的定义如下:
其中,l p S e c u r i t y D e s c r i p t o r字段用于在一个S E C U R I T Y _ D E S C R I P TO R(安全描述符)
结构中,为一个对象设定访问权限。在S E C U R I T Y _ D E S C R I P TO R结构中,包含了一个
D A C L字段,它定义了哪些用户和用户组有权访问对象。假如将该字段设为N U L L,那么
任何用户及用户组均能访问我们的资源。
要注意的是,应用程序不能直接访问一个S E C U R I T Y _ D E S C R I P TO R结构,必须通过
相关的Wi n 3 2安全A P I函数来进行。如果想为S E C U R I T Y _ D E S C R I P TO R结构分配一个空的
D A C L,便需采取下述操作:
1) 创建并初始化一个S E C U R I T Y _ D E S C R I P TO R结构,这是用A P I函数I n i t i a l i z e S e c u r i t y
D e s c r i p t o r来进行的。
2) 为S E C U R I T Y _ D E S C R I P TO R结构分配一个空的D A C L , 这是用A P I函数
S e t S e c u r i t y D e s c r i p t o r D a c l来进行的。
成功建立一个新的S E C U R I T Y _ D E S C R I P TO R 结构后,必须将其分配给一个
S E C U R I T Y _ AT T R I B U T E S结构。到这时为止,我们便已作好了准备,可开始调用像
C r e a t e N a m e d P i p e这样的Wi n 3 2函数,同时使用新建的S E C U R I T Y _ AT T R I B U T E S结构,其中包含
了一个空的D A C L。下述代码片断向大家展示了如何调用必要的安全A P I函数,来做到这一点:
第4章计命名管道计计73
下载
4.2.2 高级服务器的细节
在前面的程序清单4 - 1中,我们展示了如何设计一个命名管道服务器应用,令其只负责对
一个管道实例的控制。所有A P I调用都采用同步模式工作。在这种模式下,每个调用都会一直
等到I / O请求完成,才会返回。命名管道服务器也能拥有多个管道实例,所以客户机能够建立
同服务器的两个或更多的连接;管道实例的数量要受到C r e a t e N a m e d P i p e这个A P I调用之
n M a x I n s t a n c e s参数指定的数字的限制。要想同时控制不止一个的管道实例,服务器必须考虑
使用多个线程,或者使用异步Win32 I/O机制(比如重叠式I / O以及完成端口等),分别为每个
管道实例提供服务。采用异步I / O机制,服务器可从单独一个应用程序线程中,同时为所有管
道实例提供服务。在此,我们将解释如何使用线程以及重叠式I / O,来开发更高级的服务器应
用。要想了解如何将“完成端口”应用于Wi n d o w s套接字,请参考本书第8章。
1 . 线程
要想开发一个高级服务器,令其使用线程,同时支持多个管道实例,整个过程是非常简
单的。我们要做的唯一事情便是为每个管道实例都创建一个线程,并用前面(介绍简单服务
器的开发时)讨论过的技术,为每个实例提供服务。在程序清单4 - 2中,我们阐述了如何让一
个服务器应用同时为五个管道实例提供服务。这个应用程序实际也是一个“反射”或“回应”
(E c h o)服务器,可从客户机读取数据,并将数据原封不动地回送给客户机。
程序清单4-2 在Wi n 3 2环境中使用线程技术开发的高级命名管道服务器
74计计第一部分附传统网络API
下载
第4章计命名管道计计75
下载
要想使自己的服务器应用能够同时控制五个管道实例,首先要调用A P I函数C r e a t e T h r e a d
(创建线程)。可令C r e a t e T h r e a d同时启动五个执行线程,每个都负责执行一个P i p e I n s t a n c e P r o c
函数(五个同时执行)。P i p e I n s t a n c e P r o c函数的工作原理和程序清单4 - 1展示的那个基本服务器
应用大致相同,只是它会调用D i s c o n n e c t N a m e d P i p e这个A P I函数,来重复使用一个命名管道句
柄。该函数会关闭客户机同服务器的会话。应用程序调用了D i s c o n n e c t N a m e d P i p e后,便可腾
出手来,使用同样的管道实例句柄,调用C o n n e c t N a m e P i p e函数,为另一个客户提供服务。
2. 重叠式I / O
重叠式I / O是一种特殊的输入/输出机制,允许Win32 API函数(如R e a d F i l e和Wr i t e F i l e)在发
出I / O请求之后,以异步方式工作。具体的工作原理是:向这些A P I函数传递一个O V E R L A P P E D
(重叠式)结构,然后使用A P I函数G e t O v e r l a p p e d R e s u l t,从原来那个O V E R L A P P E D结构中,
取得一次I / O请求的结果。如果在使用重叠式结构的前提下,调用一个Win32 API函数,那么
调用无论如何都会立即返回!
要想通过重叠I / O机制, 开发一个高级的命名服务器,令其同时负责对多个命名管道实例
的管理,首先需要调用C r e a t e N a m e d P i p e函数,同时将它的n M a x I n s t a n c e s参数设为大于1的一
个值。此外,还必须将d w O p e n M o d e标志设为F I L E _ F L A G _ O V E R L A P P E D。在程序清单4 - 3中,
76计计第一部分附传统网络API
下载
我们展示了具体如何开发这样的一个高级命名管道服务器。注意该应用实际是一个“反射”
或“回应”服务器,用于从客户机读取数据,并将其原封不动地打回。
程序清单4-3 使用Wi n 3 2重叠式I / O技术开发的高级命名管道服务器
第4章计命名管道计计77
下载
78计计第一部分附传统网络API
下载
第4章计命名管道计计79
下载
服务器应用要想同时为五个管道实例提供服务,必须调用五次C r e a t e N a m e d P i p e函数,取
得每个管道的实例句柄。服务器取得所有实例句柄后,便需针对每个管道,使用一个重叠式
I / O结构,以异步方式调用五次C o n n e c t N a m e d P i p e函数,开始对客户机连接请求的“监听”。
客户机建好与服务器的连接后,所有I / O都会以异步方式进行处理。若客户机断开连接,服务
器便会先调用D i s c o n n e c t N a m e d P i p e函数,再重新执行一个C o n n e c t N a m e d P i p e调用,实现每一
个管道实例句柄的重复利用。
3. 安全模拟
之所以会选择命名管道作为自己的网络编程方案,一个最好的理由便是它们依赖于
Windows NT及Windows 2000的安全机制,在客户机试图建立同服务器的连接时,实现对访问
的控制。Windows NT和Windows 2000安全机制具有“模拟”能力,允许一个命名管道服务器
应用在客户机的安全环境中执行。执行一个命名管道服务器应用时,它通常会在用于启动该
应用的那个进程的安全环境许可级别上工作。例如,假如拥有管理员权限的某人启动了一个
命名管道服务器,服务器便有权访问Windows NT或Windows 2000系统上的几乎任何资源。此
时,假如在C r e a t e N a m e d P i p e中指定的S E C U R I T Y _ D E S C R I P TO R结构允许所有用户访问这个
命名管道,就会埋下极大的安全隐患。
若服务器用C o n n e c t N a m e d P i p e函数接受一个客户机连接请求,便可调用A P I函数
I m p e r s o n a t e N a m e d P i p e C l i e n t,令自己的执行线程在客户机的安全环境下工作。该函数的定义
如下:
其中,h N a m e d P i p e参数对应于自C r e a t e N a m e d P i p e返回的管道实例句柄。调用了这个函数
之后,操作系统便会将服务器的线程安全环境变成客户机的安全环境。整个过程显得非常便
利:假如设计自己的服务器时,目的便是访问像文件这样的资源,便会使用客户机的访问权
限来进行。这样一来,服务器便能保持对资源的访问控制,无论由谁启动了这个进程。
若服务器的线程在客户机的安全环境中执行,它需要设置一个安全模拟级别。共有四个
基本的模拟级别:匿名( A n o n y m o u s)、验证( I d e n t i f i c a t i o n)、模拟( I m p e r s o n a t i o n)以及委
派(D e l e g a t i o n)。通过设置不同的级别,便可控制服务器在多大的程度上“类似”于一个客
户机,或者说,模拟的程度有多大。至于这些模拟级别更深入的情况,本章稍后在谈到一个
80计计第一部分附传统网络API
下载
客户机应用的开发时,还会进一步地讲解。服务器完成了对一个客户机会话的处理之后,便
应调用R e v e r t To S e l f,恢复成自己最初的线程执行安全环境。对R e v e r t To S e l f这个A P I函数的定
义如下:
注意该函数无任何参数。
4.2.3 客户机的细节
实现一个命名管道客户机时,要求开发一个应用程序,令其建立与某个命名管道服务器
的连接。注意客户机不可创建命名管道实例。然而,客户机可打开来自服务器的、现成的实
例。下述步骤讲解了如何编写一个基本的客户机应用:
1) 用A P I函数Wa i t N a m e d P i p e,等候一个命名管道实例可供自己使用。
2) 用A P I函数C r e a t e F i l e,建立与命名管道的连接。
3) 用A P I函数Wr i t e F i l e和R e a d F i l e,分别向服务器发送数据,或从中接收数据。
4) 用A P I函数C l o s e H a n d l e,关闭打开的命名管道会话。
建立一个连接之前,客户机需要用Wa i t N a m e d P i p e函数,检查是否存在一个现成的命名管
道实例。对该函数的定义如下:
其中,l p N a m e d P i p e N a m e参数指定了试图与之建立连接的那个命名管道。n Ti m e O u t(超
时)参数指定客户机需要等待一个管道的服务器进程多久的时间,让它在管道上完成一个待
决的C o n n e c t N a m e d P i p e操作。
Wa i t N a m e d P i p e成功完成后,客户机需要用C r e a t e F i l e这个A P I函数,打开指向服务器命名
管道实例的一个句柄。C r e a t e F i l e的定义如下:
其中, l p F i l e N a m e(文件名)参数指定希望打开的那个管道的名字;这个名字必须遵守
第4章开头讲到的管道命名规范。
d w D e s i r e d A c c e s s参数定义了访问模式,应将其设为G E N E R I C _ R E A D(用于从管道上读
取数据),或设为G E N E R I C _ W R I T E(用于将数据写入管道)。这些标志亦可统一起来设定,
方法是对两个标志进行O R(或)运算,得到最后的结果。注意访问模式必须兼容于管道当初
在服务器上的创建方式。换言之,这个模式必须与C r e a t e N a m e d P i p e的d w O p e n M o d e参数中指
定的那个相符(参见前文)。例如,假定服务器用P I P E _ A C C E S S _ I N B O U N D创建了一个管道,
客户机便应指定G E N E R I C _ W R I T E。
d w S h a r e M o d e 参数应设为0 , 因为一次只能有一个客户机访问一个管道实例。
第4章计命名管道计计81
下载
l p S e c u r i t y A t t r i b u t e s参数应设为N U L L,除非需要子进程继承客户机的句柄。之所以不能用这
个参数来指定安全控制选项,完全是由于C r e a t e F i l e不能创建命名管道实例的缘故。
d w C r e a t i o n D i s p o s i t i o n参数则应设为O P E N _ E X I S T I N G,意味着C r e a t e F i l e函数会在命名管道不
存在的情况下调用失败。
d w F l a g s A n d A t t r i b u t e s无论如何都应设为F I L E _ AT T R I B U T E _ N O R M A L。另外,该参数本来
还可以选择F I L E _ F L A G _ W R I T E _ T H R O U G H 、F I L E _ F L A G _ O V E R L A P P E D以及
S E C U R I T Y _ S Q O S _ P R E S E N T标志,并将它们同F I L E _ AT T R I B U T E _ N O R M A L标志通过O R(或)
运算合并到一起。其中, F I L E _ F L A G _ W R I T E _ T H R O U G H和F I L E _ F L A G _ O V E R L A P P E D的工
作方式类似于在表4 - 1总结的服务器模式标志。S E C U R I T Y _ S Q O S _ P R E S E N T标志则用于控制在
一个命名管道服务器上,客户机的模拟安全级别。根据安全模拟级别,我们便可知道一个服务
器进程与客户机进程在多大程度上类似。客户机可在建立与服务器的连接时,指定这一信息。
若客户机设定了S E C U R I T Y _ S Q O S _ P R E S E N T标志,那么必须使用下文总结的一个或多个安全
标志:
■ S E C U R I T Y _ A N O N Y M O U S
指定在“匿名”(A n o n y m o u s)安全级别上,对客户机加以模拟。服务器进程不可取得与
客户机有关的身份验证信息,而且不能在客户机的安全环境中执行。
■ S E C U R I T Y _ I D E N T I F I C AT I O N
指定在“验证”(I d e n t i f i c a t i o n)安全级别上,对客户机加以模拟。服务器进程可获取与
客户机有关的一些信息,比如安全标识符以及优先权限等等。然而,却不能在客户机的安全
环境中执行。假如命名管道客户机希望让服务器对客户机进行验证,但却不想让它完全扮演
客户机,这一设定便非常恰当。
■ S E C U R I T Y _ I M P E R S O N AT I O N
指定在“模拟”(I m p e r s o n a t i o n)安全级别上,对客户机加以模拟。此时,客户机希望允
许服务器进程获取与客户机有关的信息,并可在自己的本地系统中,在客户机的安全环境中
执行。使用这一标志,服务器便能以客户机的身份,访问服务器上的任何本地资源。在这种
情况下,服务器完全“模拟”或“扮演”了一个客户机。
■ S E C U R I T Y _ D E L E G AT I O N
指定在“委派”(D e l e g a t i o n)安全级别上,对客户机加以模拟。服务器进程可取得与客
户机有关的信息,并可同时在本地系统以及远程系统上,使用客户机的安全场景执行。
注意只有服务器进程在Windows 2000上运行的时候,SECURITY_DELEGATION才可
产生作用。Windows NT 4(包括最新的SP6)并未实现委派安全机制。
■ S E C U R I T Y _ C O N T E X T _ T R A C K I N G
指出安全追踪模式是“动态”的。若未设定该标志,安全追踪模式便是“静态”的。
■ S E C U R I T Y _ E F F E C T I V E _ O N LY
指出在客户机安全环境中,只有已启用的那些部分才可由服务器使用。假如未设定该标
志,客户机安全环境的所有部分均可使用,无论是否已经启用。
命名管道安全模拟的情况已在本章4 . 2 . 1节“服务器的细节”介绍过了。
C r e a t e F i l e的最后一个参数是h Te m p l a t e F i l e,它对命名管道无效,应设为N U L L。如
C r e a t e F i l e成功完成,未产生错误,客户机应用便可开始通过R e a d F i l e和Wr i t e F i l e函数,在命
82计计第一部分附传统网络API
下载
名管道上发送及接收数据。应用程序完成了数据的处理之后,可用C l o s e H a n d l e函数,将连接
关闭(断开)。
程序清单4 - 4向大家展示了一个简单的命名管道客户机程序,阐述了成功开发一个基本命
名管道客户机应用所需的各种A P I调用。一旦该应用程序成功建立了同一个命名管道的连接,
便会向服务器写入这样的一条消息: This is a test(这是一次测试)。
程序清单4-4 简单的命名管道客户机
4.3 其他API调用
迄今为止,尚有几个特殊的命名管道函数是我们一直没有提及的。C a l l N a m e d P i p e和
第4章计命名管道计计83
下载
Tr a n s a c t N a m e d P i p e这两个A P I调用可有效减轻一个应用程序编码的复杂性。这两个函数均能
在一次调用中,同时执行读和写操作。其中, C a l l N a m e d P i p e函数允许客户机应用建立与一个
消息类型的管道的连接(假如当时没有可用的管道实例,便会一直等候下去),然后在管道上
读写数据,最后关闭这个管道。事实上,这几乎是一个完整的客户机应用,只是已在一个调
用中全部写好了!对C a l l N a m e d P i p e的定义如下:
其中, l p N a m e d P i p e N a m e参数包含的是一个字串,采用U N C名字格式,指定了一个命名
管道。l p I n B u ff e r和n I n B u ff e r S i z e参数分别指定一个缓冲区的地址与大小,应用程序打算用这
个缓冲区将数据写入服务器。l p O u t B u ff e r和n O u t B u ff e r S i z e则指定了另一个缓冲区的地址和大
小,不过应用程序用它从服务器那里接收数据。 在l p B y t e s R e a d参数中,包含了从管道读回的
字节数。而n Ti m e O u t指定的是一个时间长度,以毫秒为单位,规定了在一个命名管道可用之
前,最长能等待多久的时间。
Tr a n s a c t N a m e d P i p e函数既可在客户机应用中使用,亦可在服务器应用中使用。设计它的
目的是为了将读操作与写操作整合到一个A P I调用之中。 这样一来,由于减轻了M S N P重定向
器收发数据的负担,所以能在一定程度上优化网络I / O的性能。对Tr a n s a c t N a m e d P i p e的定义如
下:
其中,h N a m e d P i p e参数指定的是由C r e a t e N a m e d P i p e或C r e a t e F i l e这两个A P I函数返回的命
名管道。l p I n B u ff e r和n I n B u ff e r S i z e参数指定一个缓冲区的地址和大小,应用程序用这个缓冲
区将数据写入管道。l p O u t B u ff e r和n O u t B u ff e r S i z e参数指定的则是另一个缓冲区的地址和大小,
应用程序利用这个缓冲区从管道取得数据。l p B y t e s R e a d参数用于接收自管道读回的实际字节
数量。l p O v e r l a p p e d参数允许这个Tr a n s a c t N a m e d P i p e使用重叠式I / O,以异步形式工作。
接下来的三个函数( G e t N a m e d P i p e H a n d l e S t a t e, S e t N a m e d P i p e H a n d l e S t a t e以及
G e t N a m e d P i p e I n f o)用于使命名管道客户机以及服务器通信在运行时间显得更加灵活。例如,
可利用这些函数在运行时间更改一个管道的运行模式,从消息模式变成字节模式,或从字节
模式变成消息模式。G e t N a m e d P i p e H a n d l e S t a t e用于接收与一个指定命名管道对应的信息,比
如运行模式(消息或字节模式)、管道实例数以及缓冲区信息等等。在命令管道的一个实例的
84计计第一部分附传统网络API
下载
“存在时间”内,不同时间由G e t N a m e d P i p e H a n d l e S t a t e函数返回的信息也可能会发生变化。
对G e t N a m e d P i p e H a n d l e S t a t e函数的定义如下:
其中,h N a m e d P i p e参数指定的是由C r e a t e N a m e d P i p e或C r e a t e F i l e这两个A P I函数返回的一个命
名管道。l p S t a t e参数是一个变量指针,那个变量负责接收管道句柄的当前工作模式。l p S t a t e参数可
能返回两个值,一个是P I P E _ N O WA I T,另一个是P I P E _ R E A D M O D E _ M E S S A G E。l p C u r I n s t a n c e s
参数也是一个变量指针,那个变量负责当前的管道实例数量。l p M a x C o l l e c t i o n C o u n t参数负责接
收实际发送给服务器之前,打算在客户机上收集的最大字节数。l p C o l l e c t D a t a Ti m e o u t参数接
收的则是一个时间值,以毫秒为单位。超过这个时间,一个远程命名管道便必须通过网络传
出信息。l p U s e r N a m e和n M a x U e r N a m e S i z e参数定义的是一个缓冲区,它负责接收一个“空中
止”的字串,其中包含了客户机应用的用户名字串。
利用S e t N a m e d P i p e H a n d l e S t a t e函数,我们可更改由G e t N a m e d P i p e H a n d l e S t a t e函数了解到
的管道特征。S e t N a m e d P i p e H a n d l e S t a t e函数的定义如下:
其中,h N a m e d P i p e参数指定的是由C r e a t e N a m e d P i p e或C r e a t e F i l e这两个A P I函数返回的一
个命名管道。l p M o d e参数设置管道的工作(运行)模式。l p M a x C o l l e c t i o n C o u n t参数指定的是
客户机上收集的最大字节数量,随后数据便需发给服务器。l p C o l l e c t D a t a Ti m e o u t参数以毫秒
为单位指定了一个时间值,该值指明一个远程命名管道客户机通过网络传送信息之前,最多
需等待的时间。
G e t N a m e d P i p e I n f o这个A P I函数用于获得缓冲区大小以及管道实例最大数量信息。对它的
定义如下:
其中,h N a m e d P i p e参数指定的是由C r e a t e N a m e d P i p e或C r e a t e F i l e这两个A P I函数返回的一
个命名管道。l p F l a g s参数取得命名管道的类型,并判断它到底是一个服务器,还是一个客户
机,以及管道工作于字节模式,还是消息模式。l p O u t B u ff e r S i z e参数以字节为单位,指定了
第4章计命名管道计计85
下载
用来保存外出数据的内部缓冲区的大小; l p I n B u ff e r S i z e参数则以字节为单位,取得用于保存
进入数据的内部缓冲区的大小。l p M a x I n s t a n c e用于取得可以创建的管道实例的最大数量。
最后一个A P I函数是P e e k N a m e d P i p e,可用它对命令管道内的数据进行浏览,同时毋需将
其从管道的内部缓冲区挪出。假如应用程序希望对进入的数据进行“轮询”,以免进行
R e a d F i l e这个A P I调用时发生“锁定”现象(执行暂停),便可考虑使用这一函数。另外,假
若应用程序需要在数据实际接收之前,先作一番检查,这个函数也是相当有用的。例如,应
用程序可能希望根据进入消息的长度,先对应用程序的缓冲区进行一番调节。 搞好后,再正
式接收数据。P e e k N a m e d P i p e函数的定义如下:
其中,h N a m e d P i p e参数用于指定由C r e a t e N a m e d P i p e或C r e a t e F i l e这两个A P I函数返回的一
个命名管道。l p B u ff e r和n B u ff e r S i z e参数分别定义的是一个接收专用缓冲区的地址及大小,用
于从管道取得数据。l p B y t e s R e a d 参数负责接收从管道实际读入缓冲区的字节数量。
l p To t a l B y t e s Av a i l参数用于接收可从管道发出的字节总数。l p B y t e s L e f t T h i s M e s s a g e参数用于接
收消息内尚存的字节数量(前提是管道用消息模式打开)。假如一条消息的实际长度大于由
l p B u ff e r参数指定的那个缓冲区的长度,消息内剩下的字节便会返回。对于在字节模式下工作
的命名管道而言,该参数则无论如何都会返回0。
4.4 平台和性能问题
在微软知识库中,可找到下述问题及限制定义。这个知识库可在网上访问到,地址是
h t t p : / / s u p p o r t . m i c r o s o f t . c o m / s u p p o r t。下面是对每一个问题的简要说明:
■ Q 1 0 0 2 9 1:命名管道名字的限制
假如创建了名为/ / . / P i p e / M y p i p e s的管道,以后便不能创建名为/ / . P i p e / M y p i p e s / P i p e 1的管
道,因为/ / . / P i p e / M y p i p e s已经是一个管道名,不可作为子目录使用。
■ Q 11 9 2 1 8:命名管道写操作限制在6 4 K之内
若A P I函数Wr i t e F i l e试图用一个大于6 4 K B的缓冲区,向一个处于消息模式的命名管道写
入数据,该函数便会返回FA L S E,而G e t L a s t E r r o r调用会返回E R R O R _ M O R E _ D ATA。
■ Q 11 0 1 4 8:由Wr i t e F i l e或R e a d F i l e函数返回E R R O R _ I N VA L I D _ PA R A M E T E R错误
假如在一个命名管道上工作,并使用重叠式I / O,那么Wr i t e F i l e或R e a d F i l e函数调用都有
可能失败,并返回E R R O R _ I N VA L I D _ PA R A M E T E R错误。这种失败一项可能的促因是
O V E R L A P P E D结构的O ff s e t以及O ff s e t H i g h这两个成员未设为0。
■ Q 1 8 0 2 2 2:Windows 95的Wa i t N a m e d P i p e和2 5 3号错误
在Windows 95中,假如将一个无效管道名作为第一个参数传递,那么在Wa i t N a m e d P i p e
函数调用失败以后,用G e t L a s t E r r o r会返回一个错误2 5 3。对这个函数来说, 2 5 3并非一个标准
的(事先定义的)错误代码。而假如换到Windows NT 4上运行同样的代码,返回的错误代码
86计计第一部分附传统网络API
下载
是1 6 1(E R R O R _ B A D _ PAT H N A M E)。要想解决这种不一致的问题,请将2 5 3号错误解释成标
准的1 6 1号错误:E R R O R _ B A D _ PAT H N A M E(路径名错误)。
■ Q 1 4 1 7 0 9:单台工作站最多只能建立4 9个命名管道连接
假如命名管道服务器应用创建了4 9个以上的直接命名管道,那么在远程计算机上,一个
客户机最多只能建立前面的4 9个命名管道连接,多余的只好忽略。
■ Q 1 2 6 6 4 5:从某项服务打开一个命名管道时,出现访问被拒的情况
假如一项服务采用“本地系统”帐号运行,同时试图打开在Windows NT上运行的一个命
名管道,那么操作便会失败,并返回“拒绝访问”错误信息,亦即错误代码5。

你可能感兴趣的:(管道和命名管道)