Windows存在两种类型的管道: “匿名管道”(Anonymous pipes)和”命名管道”(Named pipes)。匿名管道相比命名管道需要较小的开销,同时它也只提供有限的系统服务。
术 语管道用在这里,毫无疑问地它是作为了一种信息交换的渠道。概念上一个管道有A、B两个端点。一个单路(“One-way”)管道允许在A端的进程向管道 写入数据,允许在B端的进程从管道读出数据。而一个双路(或双通道)管道允许进程在管道的A、B两端读、写数据。下面我们就从匿名管道和有名管道两方面来 介绍一下Windows的管道机制。
匿名管道一个匿名管道是一个没有名字、单路(“One-way”)管道,它典型的应用场景就是在父进程和子进程之间传输数据。匿名管道仅限用于本机通讯,它不能被用于跨网络通讯的场合。
匿名管道的操作CreatePipe 函数用于创建一个匿名管道并返回两个句柄:一个管道读句柄和一个管道写句柄。这个管道读并举拥有对管道的只读访问权限,而这个管道写句柄拥有对管道的只写 访问权限。为了通过管道进行通讯,管道服务端必须通过某种方法将管道句柄传给另一个进程。这通常通过进程间的继承性来完成的;即,进程允许将句柄继承给它 的子进程。进程也可以通过DuplicateHandle函数来复制管道句柄并将得到的管道句柄副本通过其它进程间通讯手段(例如众所周知的共享内存、 DDE – 动态数据交换或者消息)发送给一个无关进程。
一个管道服务端可以发送管道读句柄或管道写句柄给管道客户端,具体发送哪个句柄这 要取决于客户端使用管道是要发送信息还是接受信息而已!为了从管道读取数据,我们使用管道读句柄来调用ReadFile函数,当另外一个进程向管道中写入 了数据ReadFile调用就会返回,再者当所有的管道写句柄都已关闭或者当读操作完成前发生了错误都会导致ReadFile调用返回。
对 于管道的写操作,我们使用管道写句柄来调用WriteFile函数,WriteFile调用不会返回直到它向管道写入了指定数量的字节或者中途产生了异 常。如果管道缓冲区已经塞满并且还有更多的数据没有写入管道,那么WriteFile调用也不会返回直到另一个进程从管道中读出数据以释放出有效的管道缓 冲区(来盛放其余的数据)。管道缓冲区的大小由管道服务端在创建管道(CreatePipe)时指定。
匿名管道不支持异步(重叠)读、写操作。这意味着你不能使用ReadFileEx、WriteFileEx函数来操作匿名管道。另外,对于ReadFile、WriteFile函数在操作匿名管道时lpOverlapped参数都将被忽略。
一个匿名管道对象只有当所有的管道读、写句柄都被关闭后才会被释放,否则它将一直存在!进程可以使用CloseHandle函数来关闭它的管道句柄,当进程终止时所有管道句柄也将随之被(系统自动)关闭。
匿名管道机制是通过一个名字全局唯一的有名管道来实现的。因此,你可以将一个匿名管道句柄传递给一个需要一个有名管道句柄的函数(这听起来好像有点拗口)!
管道句柄的继承管道服务端控制着它的句柄是否能以如下方式被继承:
l CreatePipe函数接受一个SECURITY_ATTRIBUTES结构,如果管道服务端设置SECURITY_ATTRIBUTES结构的bInheritHandle成员为TRUE的话,CreatePipe创建句柄就是能被继承;
l 管道服务端可以使用DuplicateHandle函数来改变管道句柄的继承性。管道服务端可以创建一个“可继承管道句柄”的不可继承的副本或者一个“不可继承管道句柄”的可继承的副本。
l CreateProcess函数的bInheritHandles参数赋予了管道服务端是否使其子进程继承所有或不继承它的可继承句柄的能力。
当子进程继承了一个管道句柄,系统就赋予了进程访问这个管道的能力。然而,父进程必须以某种方式告知子进程关于这个管道句柄的值!父进程告知子进程的典型做法是通过重定向标准输出句柄到子进程,步骤如下(下面1~4都是针对父进程而言):
1. 调用GetStdHandle函数获取当前标准输出句柄并保存该句柄(后面当子进程创建结束后你可以使用该值来恢复原来的标准输出句柄);
2. 调用SetStdHandle函数把管道写句柄设置为标准输出句柄,现在父进程可以创建子进程了。
3. 调用CloseHandle函数关闭管道写句柄,当子进程继承这个管道写句柄后,父进程就不再需要它的那份(管道写句柄的)拷贝了。
4. 调用SetStdHandle函数来恢复原来的标准输出句柄。
当子进程创建成功以后,子进程就可以使用GetStdHandle函数来获取它的标准输出句柄了,而现在这个标准输出句柄就是指向管道写句柄!子进程接着就可以使用WriteFile函数来向管道写入数据(也就是发送数据)。当子进程使用完管道,它应该调用CloseHandle函数关闭管道句柄,或者进程终止时由系统自动关闭管道句柄。
父进程使用ReadFile函数从管道中接收数据,而数据是以字节流方式被写进匿名管道的。这就意味着父进程从管道中读取数据是无法区分写进去的字节是在单独不同的写操作中完成的,除非父进程和子进程之间使用一种协议来指示写操作在何处结束。当所有的管道写句柄都被关闭时,ReadFile函数就返回0,“在调用ReadFile之前先关闭管道写句柄”这一点对父进程来说至关重要的!如果这点你没有做,ReadFile函数是不会返回0的,这是因为父进程还有一个处于打开状态的管道写句柄未关闭!
注:重定向标准输入句柄和重定向标准输出句柄的过程十分相似,除了管道的读句柄被用作子进程的标准输入句柄外其它基本相同。用这种方式,父进程必须确保子进程没有继承管道写句柄。如果这点你没有做,ReadFile函数被子进程执行是不能返回0的,因为子进程还有一个处于打开状态的管道读句柄未关闭。
MSDN中有一篇关于利用匿名管道来重定向子进程标准句柄(输入、输出)的文章,并且附有代码 - 。
* 如果看以上两段内容有点飘的话可以参照文章例子里面的ReadFromPipe接口 *
匿名管道的安全和访问权限Windows NT安全机制使你可以控制匿名管道的访问权限。关于Windows NT的安全机制的内容请参照文章。
当 你调用函数创建匿名管道时你可以为管道指定一个安全描述符,这个安全描述控制着管道读、写两端的访问权限。如果你指定了空的(NULL)安全描述符,那么 管道将得到一个默认的安全描述符。管道默认安全描述符里的ACLs来自于管道创建者的主要的(primary)或模拟的(impersonation)令 牌。
为了获取管道的安全描述符,你可以调用GetSecurityInfo函数,而为了改变管道的安全描述符,你可以调用SetSecurityInfo函数。
函数一共返回两个到匿名管道的句柄:一个拥有GENERIC_READ和SYNCHRONIZE权限的读句柄;和一个拥有GENERIC_WRITE 和SYNCHRONIZE权限的写句柄。对命名管道而言GENERIC_READ 和GENERIC_WRITE使用了相同的访问权限映射。
l 对匿名管道而言GENERIC_READ访问权限混合了从管道读取数据、读取管道属性、读取管道扩展属性以及*读取*管道的DACL信息。
l 对匿名管道而言GENERIC_ WRITE访问权限混合了向管道写数据、追加数据、写管道属性、写管道扩展属性以及*读取*管道的DACL信息。