进程间通信 —— 管道(Interprocess Communications —— Pipes)

进程间通信 —— 管道(Interprocess Communications —— Pipes)

管道分为匿名管道(anonymous pipes)命名管道(named pipes.)两类, 其中匿名管道需要更少的开销, 但是其能提供的服务是有限的。
这里用到的管道(pipe)这个术语, 望文生义, 是一种信息通道。 从概念上讲, 管道有两个端。 单向(one-way)管道允许一端的进程写入管道, 并允许另一端的进程从管道中读取。 双向(two-way/duplex)管道允许进程从管道的端读取或写入。

匿名管道(anonymous pipes)

匿名管道是未命名的单向管道, 它通常在父进程和子进程间传输数据。匿名管道总是本地的, 不能用于网络通信。

匿名管道的操作(Anonymous Pipe Operations)

CreatePipe 用于创建一个匿名管道并返回两个句柄: 对管道的读句柄和写句柄。读句柄对管道有只读权限, 而写句柄具有对管道的只写权限。 为了通过管道通信, pipe server 必须传递管道句柄给另一个进程, 通常这是通过继承来完成的。因为进程可以允许子进程进程句柄。进程还可以用 DuplicateHandle 函数复制(duplicate)管道句柄, 并通过一些进程间通信形式如 DDE 和内存共享发送给不相关的进程。

pipe server 可以将读句柄或写句柄发送给 pipe client, 这取决于 client 是否需要用匿名管道发送或接收信息。从管道读数据需要用 ReadFile 函数在读句柄上读数据。当另一个进程已经写入管道时, ReadFile 调用返回。如果所以的写句柄都已关闭, 或者在读取操作完成前发生错误, ReadFile 调用返回。

写入数据到管道时, 要用 WriteFile 函数写入到写句柄。直到将指定字节数的数据全部写入管道或发生错误, WriteFile 函数才返回。如果管道缓冲区是满的, 并且还有更多的字节要写入管道, 那么 WriteFile 将会等到另一个进程从管道读取数据使缓冲区有更多的空间才会返回。pipe server 在调用 CreatePipe 函数时, 可以指定缓冲区的大小。

匿名管道不支持异步(Asynchronous)/重叠(overlapped)读写操作。即, 不可以对匿名管道使用 ReadFileExWriteFileEx 函数。此外, 当对匿名管道使用 ReadFile/WriteFile 函数时, 会自忽略 lpOverlapped 参数。

匿名管道在所有的管道句柄, 读句柄和写句柄, 都关闭之前, 都一直存在。进程可以通过 CloseHandle 函数来关闭管道句柄。 当进程终止的时候, 所有的管道句柄都会关闭。

匿名管道是由具有唯一名字的命名管道实现的。因此, 通常可以将匿名管道的句柄传给需要一个命名管道句柄的函数。

管道句柄的继承(Pipe Handle Inheritance)

Pipe server 可以通过以下方式控制其句柄的继承性:

  • 当传入 CreatePipe 函数的 SECURITY_ATTRIBUTES 结构的 bInheritHandle 成员设置为 TRUE 时, 通过该函数创建的句柄可以被继承。
  • Pipe server 可以使用 DuplicateHandle 函数来改变管道句柄的继承性。Pipe server 可以创建一个可继承管道句柄的不可继承副本,或者一个不可继承管道句柄的可继承副本。
  • CreateProcess 函数允许 pipe server 指定子进程继承或不继承其所有可继承的句柄。

当子进程继承管道句柄时, 系统允许进程访问管道。但是, 父进程必须将句柄值传给子进程。父进程通常用以下步骤将标准输出句柄重定向给子进程来实现句柄值的传递:

  1. 调用 GetStdHandle 函数来获取当前的标准输出句柄; 保存这个句柄以便在子进程创建之后恢复原始的标准输出句柄。
  2. 调用 SetStdHandle 函数设置标准输出句柄为管道的写句柄。现在, 父进程就可以创建子进程了。
  3. 调用 CloseHandle 函数关闭管道的写句柄。 在子进程继承写句柄之后, 父进程不再需要其副本。
  4. 调用 SetStdHandle 函数恢复原始的标准输出句柄。

子进程通过使用 GetStdHandle 函数来获得已重定向为管道的写句柄的标准输出句柄。然后, 子进程使用 WriteFile 函数将输出发送给管道。当子进程用完管道后, 应该调用 CloseHandle 函数来关闭管道句柄, 程序终止后, 会自动关闭句柄。

父进程通过使用 ReadFile 函数接受来自管道的输入。数据是以字节流的形式写入匿名管道, 这就意味着, 除非父进程和子进程约定了表明写操作结束的协议, 否则, 父进程不能将在独立的几次写操作中写入的字节区分开。当所有的写句柄都关闭时, ReadFile 函数将会返回零。值得注意的是, 父进程需要在调用 ReadFile 函数之前先关闭其写句柄, 否则, ReadFile 永远不可能返回零, 因为父进程还有管道打开的写句柄。

重定向标准输入句柄的过程和重定向标准输出句柄的过程类似, 不过是把管道的读句柄设为子进程的标准输入句柄。在这种情况下, 父进程必须保证子进程没有进程该管道的写句柄, 否则子进程对管道读句柄调用 ReadFile 函数时, 将不能返回零, 因为子进程还有管道打开的写句柄。

关于使用匿名管道重定向到子进程的标准句柄的例子, 请参见 Creating a Child Process with Redirected Input and Output

匿名管道的安全性和访问权限(Anonymous Pipe Security and Access Rights)

Windows 安全性允许你控制匿名管道的访问。有关安全性的更多信息, 详见 Access Control Model

当调用 CreatePipe 函数时, 可以为管道指定安全描述符。安全描述符控制管道的读端和写端。当安全描述符指定为 NULL 时, 管道将得到一个默认的安全描述符。默认安全描述符中的 ACL 来自其创建者的主或模拟令牌(primary or impersonation token)。

可以通过调用 GetSecurityInfo 函数来获取管道的安全描述符, 而通过调用 SetSecurityInfo 函数来改变管道的安全描述符。

CreatePipe 函数返回匿名管道的两个句柄: 一个带有 GENERIC_READ 权限和 SYNCHRONIZE 权限的读句柄, 以及一个带有 GENERIC_WRITESYNCHRONIZE 权限的写句柄。命名管道的 GENERIC_READGENERIC_WRITE 权限使用相同的访问权限映射。

匿名管道的 GENERIC_READ 权限组合了从管道中读数据, 读取管道属性, 读取扩展属性以及读取管道的 DACL(Discretionary Access Control List) 的权限。

匿名管道的 GENERIC_WRITE 权限组合了向管道中写数据, 添加数据到管道, 写管道的属性, 写扩展属性以及读取管道的 DACL 的权限。

命名管道(Named Pipes)

命名管道是连接 pipe server 和一个或多个 pipe client 的命名的单向或双向管道。命名管道的所有实例共享同一个管道名, 但是每一个实例都有自己的缓冲区和句柄, 并为 client/server 通信提供单独的通道(conduit)。实例的使用允许多个 pipe client 使用同时使用同一个命名管道。

在符合安全性下, 任何进程都可以访问命名管道, 这就使得命名管道成为了进程间通信的简单方式。

任何进程都可以充当 server 和 client 的角色, 这使得对等(peer-to-peer)通信成为可能。 这里使用的术语 pipe server 指的是创建命名管道的进程, 而 pipe client 指的是连接到命名管道实例的进程。实例化一个命名管道的 server 端函数是 CreateNamedPipe, 接受连接的 server 端函数是 ConnectNamedPipe。客户端进程则使用 CreateFileCallNamedPipe 函数来连接到命名管道。

命名管道可以用于提供同一计算机进程之间的通信, 或是网络上的不同计算机的进程之间的通信。如果 server 服务正在运行, 所有命名管道都是可以远程访问的。如果要使用仅本地使用的命名管道, 可以拒绝访问 NT AUTHORITY\NETWORK 或切换到本地 RPC。

管道的名字(Pipe Names)

每个命名管道都有一个将其与系统命名对象列表中的其他命名管道区别开来的唯一的名字。pipe server 在调用 CreateNamedPipe 函数创建一个或多个命名管道的实例时, 为管道指定一个名字。而 pipe client 则是在调用 CreateFileCallNamedPipe 函数连接命名管道的实例时, 为命名管道指定名称。

在使用 CreateFile, WaitNamedPipe, 或 CallNamedPipe 函数时, 用下面的形式指定管道的名字:

\\ServerName\pipe\PipeName

其中 ServerName 可以是远程计算机的名字, 也可以是表示本地计算机的小点(“.”, period)。指定管道名称字符串的 PipeName 可以包括出了反斜杠(backslash)以外的任何字符, 包括数字和特殊字符。整个管道名称字符串最长可以达到 256 个字符。管道名称不区分大小写。

pipe server 不能在另一个计算机上创建管道, 因此, CreateNamedPipe 必须使用小圆点(“.”) 来表示其 ServerName, 如下:

\\.\pipe\PipeName

pipe server 提供管道名称给 pipe client, 因此, 它们才能连接到管道。pipe client 从一些 persistant source(如, 注册表项(registry entry), 文件或其他应用程序)中发现管道名称。 否则, client 必须在编译期指定管道的名字。

命名管道的打开模式(Named Pipe Open Modes)

Pipe server 在调用 CreateNamedPipe 函数时, 通过 dwOpenMode 参数指定管道的访问模式(access), 重叠模式(overlap), 直写模式(write-through)。

  • 访问模式(Access Mode)

    设置管道访问模式等价于指定 pipe server 的句柄相关的读写权限。下表显示了与 CreateNamedPipe 函数指定的每一个访问权限等价的通用访问权限(generic access right)。

Access modeEquivalent generic access right
PIPE_ACCESS_INBOUNDGENERIC_READ
PIPE_ACCESS_OUTBOUNDGENERIC_WRITE
PIPE_ACCESS_DUPLEXGENERIC_READ | GENERIC_WRITE

如果 pipe server 使用 PIPE_ACCESS_INBOUND 创建管道, 则该管道在 pipe server 端是只读的, 而在 pipe client 是只写的。如果 pipe server 使用 PIPE_ACCESS_OUTBOUND 创建管道, 则该管道在 pipe server 端是只写的, 而在 pipe client 是只读的。如果 pipe server 使用 PIPE_ACCESS_DUPLEX 创建管道, 则该管道在 pipe server 和 pipe client 端都是可读写的。

使用 CreateFile 连接命名管道的 pipe client 必须在 dwDesireAccess 参数中指定与 pipe server 指定的访问模式兼容的访问权。例如, client 必须指定 GENERIC_READ 权限来打开 pipe server 通过 PIPE_ACCESS_OUTBOUND 创建的管道句柄。对于所有的管道实例, 访问权限必须是相同的。

要读取管道的属性(如, 读取模式或阻塞模式), 管道的句柄必须具有 FILE_READ_ATTRIBUTES 访问权限。要写管道属性, 管道必须具有 FILE_WRITE_ATTRIBUTES 访问权限。这些访问权限可以与适合管道的通用访问权限结合: GENERIC_READ, FILE_WRITE_ATTRIBUTES 与只读管道, 或者, GENERIC_WRITE, FILE_READ_ATTRIBUTES 与只写管道。以这种方式限制访问权限为管道提供了更好的安全性。

  • 重叠模式(Overlapped Mode)

    在重叠模式中, 执行冗长的读, 写和连接操作函数的函数时, 可以立即返回。这使得该线程可以执行其他操作, 耗时的操作则在后台运行。可以使用 FILE_FLAG_OVERLAPPED 来指定重叠模式。详见 Synchronous and Overlapped Pipe I/O

  • 直写模式(Write-Through Mode)

    FILE_FLAG_WRITE_THROUGH 指定直写模式。这种模式只影响连通不同计算机的 pipe server 和 server client 的字节类型管道的写操作。在直写模式下, 写入命名管道的函数直到数据通过网络传送到远端计算机的管道缓冲区才会返回。直写模式对需要对每个写操作进行同步的应用程序很有用。

    如果不启用直写模式, 系统将通过缓冲数据提高网络操作的效率, 直到积累到某个最少的字节数, 或过去了某个最大时间周期。缓冲能使系统将多次的写操作通过一次网络传输操作发送出去。这就意味着在系统将数据放入出站(outbound)缓冲区之后, 系统通过网络传输之前, 写操作可能已经成功完成。

    CreateFile 函数允许 pipe client 通过 dwFlagsAndAttributes 参数将其管道句柄设置为直写模式(FILE_FLAG_WRITE_THROUGH)。在管道句柄被创建以后, 管道句柄的直写模式将不能被改变。同一个管道实例的 pipe server 句柄 和 pipe client 句柄的直写模式可以不同。

    当禁用直写模式时, pipe client 可以使用 SetNamedPipeHandleState 函数来控制传输前的字节数和超时时间。对于只读管道, 必须使用 GENERIC_READFILE_WRITE_ATTRIBUTES 访问权限打开其管道句柄。

命名管道的类型模式, 读取模式和等待模式(Named Pipe Type, Read, and Wait Modes)

Pipe server 通过 CreateNamedPipe 函数的 dwPipeMode 参数指定管道的类型模式, 读取模式和等待模式。Pipe client 通过 CreateFile 函数来指定管道句柄的模式。

  • 类型模式(Type Mode)

    管道的类型模式决定数据是如何写入命名管道的。数据可能作为字节流或消息流在命名管道中传输。pipe server 在调用 CreateNamedPipe 函数创建命名管道的实例时指定管道类型。对于同一管道的所有实例, 类型模式必须一样。

    用默认值或 PIPE_TYPE_BYTE 创建字节类型的管道。数据以字节流的形式写入管道, 系统无法区别不同的写操作写入的字节。

    PIPE_TYPE_MESSAGE 创建消息类型管道。系统将每次写操作写入管道的字节看作是一个消息单元。系统在消息类型管道上执行写操作就像启用了直写模式一样。

  • 读取模式(Read Mode)

    管道的读取模式决定如何从命名管道中命名管道中读取数据。pipe server 在调用 CreateNamedPipe 时, 指定管道句柄最初的读取模式。数据可以以字节模式或是消息模式读取。字节类型的管道句柄, 只能只能是字节读取模式。消息类型的管道句柄, 可以是字节读取模式或信息读取模式。消息类型的管道, server 和 client 的同一管道的实例的句柄可以有不同的读取模式。

    用默认值或 PIPE_READMODE_BYTE 创建字节读取模式的管道句柄。它以字节流的形式从管道中读取数据。当管道中的所有字节都被读取或读取到指定的字节数, 读操作才完成。

    PIPE_READMODE_MESSAGE 创建消息读取模式的管道句柄。它以消息流的形式从管道中读取数据。当整个消息读取完成后, 一次读操作才完成。如果指定待读取的字节数小于下一条消息的大小,在返回零之前, 函数读取尽可能多的数据(GetLastError 函数返回 ERROR_MORE_DATA)。消息剩余的部分可以用另一个写操作来读取。

    对于 pipe client 来说, 通过 CreateFile 函数返回的管道句柄总是初始为字节读取模式。在管道句柄拥有 FILE_WRITE_ATTRIBUTES 权限的情况下, pipe client 和 pipe server 都可以调用 SetNamedPipeHandleState 函数来改变管道句柄的读取模式。

  • 等待模式(Wait Mode)

    管道句柄的等待模式决定 ReadFile, WriteFileConnectNamedPipe 函数如何处理冗长操作。在阻塞等待(blocking-wait)模式下, 函数一直等待管道另一端的进程完成操作才返回。在非阻塞等待(nonblocking-wait)模式下, 除非需要永久等待, 否则函数将立即返回。

    当管道为空时, ReadFile 操作会受管道句柄的等待模式影响。使用阻塞等待句柄时, 操作直到从某个线程写入数据到该管道的另一端。, 才会返回。用非阻塞等待句柄时, 函数会立即返回零, 并且 GetLastError 函数会返回 ERROR_NO_DATA

    默认情况下, 所有由 CreateNamedPipeCreateFile 函数返回的命名管道句柄, 都是阻塞等待模式的。 想要创建非阻塞模式的管道, pipe server 需要在调用 CreateNamedPipe 函数时, 传入 PIPE_NOWAIT

    pipe client 和 pipe server 都可以通过 PIPE_WAITPIPE_NOWAIT 调用 SetNamedPipeHandleState 函数来改变管道句柄的等待状态。

    Note 非阻塞等待模式被 Microsoft LAN Manager version 2.0 支持。不应该用这种模式的命名管道来实现重叠输入和输出。 而应该使用 Overlapped I/O, 因为它在函数返回后, 让耗时的操作在后台运行。更多关于 Overlapped I/O 的信息, 详见 Synchronous and Overlapped Pipe I/O

命名管道实例(Named Pipe Instances)

最简单的 pipe server 创建一个管道的实例, 连接到一个 pipe client, 与 client 通信, 从 client 断开连接, 关闭管道句柄, 并终止。然而, 一个 pipe server 通常与多个 pipe client 通信。 pipe server 可以使用一个管道实例, 通过连接并断开一系列的 pipe client 来实现与多个 pipe client 通信, 但是性能会很差。pipe server 必须创建多个管道实例, 才能有效地同时处理多个 client。

为多个管道实例提供服务有三种基本策略:

多线程的 pipe server 是最容易编写的, 因为每一个实例句柄的线程处理单个 pipe client 的通信。系统根据需要为每个线程分配处理器时间。但是每一个线程都使用系统资源, 对处理大量 client 的 pipe server 是不利的。

使用单线程 server, 可以更容易协调多个 client 的操作, 更容易保护多个 client 同时访问的共享资源。 单线程的 server 的挑战在于, 它需要协调重叠操作, 以分配处理器时间来处理 client 同时的需求。

命名管道的操作(Named Pipe Operations)

pipe server 第一次调用 CreateNamedPipe 时, 用 nMaxInstances 参数指定可以同时存在的最大管道实例数。server 可以重复调用 CreateNamedPipe 来创建不超过管道最大实例数的额外的管道实例。如果函数调用成功, 则每次调用都会返回一个命名管道实例 server 端的句柄。

Pipe server 创建了管道实例之后, pipe client 可以通过调用 CreateFileCallNamedPipe 函数连接。如果管道实例空闲, 则 CreateFile 返回 client 端的管道实例句柄。如果没有管道实例空闲, 则 pipe client 可以通过调用 WaitNamedPipe 来等待管道空闲。

Pipe server 可以通过调用 ConnectNamedPipe 函数来决定什么时候 pipe client 可以连接到管道实例。如果该管道句柄是阻塞等待模式, 则直到有 client 连接 ConnectNamedPipe 才返回。

Pipe client 和 server 可以调用一些函数来从管道读取或写入数据。这些函数的表现取决于管道的类型和管道句柄的模式:

  • ReadFileWriteFile 函数可以用于字节类型或消息类型的管道。
  • ReadFileExWriteFileEx 函数可以用于管道句柄以重叠操作打开的字节类型或消息类型的管道。
  • PeekNamedPipe 函数可以用于读取字节类型或消息类型的管道却不将其从缓冲区移除。
  • TransactNamedPipe 函数可以用于消息类型的双向管道。该函数每次操作写入一条请求消息或读取一条回复消息, 从而增强网络性能。

Pipe server 应该在 pipe cient 启动之后才执行阻塞读操作。否则, 可能会产生竞态条件(race condition)。 这通常发生在初始化代码, 例如, C运行时库需要锁定和检查继承的句柄。

当 client 和 server 使用完一个管道实例后, server 首先应该调用 FlushFileBuffers 以确保所有写入管道的字节流或消息已全部被 client 读取。 FlushFileBuffers 直到 cleint 从管道读取所有的数据, 才返回。 然后, Server 调用 DisconnectNamedPipe 函数关闭与 pipe client 的连接。如果 client 的句柄还没关闭, 则这个函数就会使之失效, 任何在管道中的未读数据都会被丢弃。在 client 断开连接后, server 可以调用 CloseHandle 函数来关闭管道实例的句柄, server 也可以通过调用 ConnectNamedPipe 函数使得新的 client 可以连接到这个管道实例。

可以通过调用 GetNamedPipeInfo 函数来获取命名管道的信息, 包括管道的类型, 输入缓冲区和输出缓冲区的大小, 以及可以创建的管道实例的最大数目。通过 GetNamedPipeHandleState 函数可以获取管道句柄的读取模式和等待模式, 当前管道实例数目, 以及跨网络通信管道的额外信息。可以通过 SetNamedPipeHandleState 函数 设置管道句柄的阅读模式和等待模式。对于和远端 server 通信的 pipe client 来说, 这个函数也可以控制在消息传送前能收集的最大字节数或最大等待时间(假定client 的句柄没有启用直写模式)。

Synchronous and Overlapped Pipe I/O

ReadFile, WriteFile, TransactNamedPipeConnectNamedPipe 函数可以对管道执行同步(synchronously)或异步(asynchronously)地输入输出操作。当函数以同步的方式执行时, 直到操作完成才会返回。这就意味着调用该函数的线程会无限期的阻塞, 直到这个消耗时间的操作执行完成。当函数异步执行时, 即使该操作还没有完成, 它也会立即返回。这使得调用的线程可以执行其他的任务, 而让耗时操作在后台执行。

通过使用异步的输入输出,管道服务器可以使用一个执行以下步骤的循环:

  1. 在调用等待函数时, 可以指定多个事件对象(event object), 等待某个对象被设置为 signaled 状态。
  2. 根据等待函数的返回值来确定哪个重叠操作完成。
  3. 执行清理已完成的操作所需的任务,并为该管道句柄启动下一个操作。这可能涉及到对同一个管道句柄启动另一个重叠操作。

重叠操作使得一个管道可以同时读写数据,并且一个线程可以在多个管道句柄上同时执行 I/O 操作。这使得单线程的 pipe server 能够有效地处理与多个管道客户端的通信。例如,Named Pipe Server Using Overlapped I/O
对于使用同步操作与多个 client 进行通信的 pipe server, 它必须为每个 pipe client 创建一个单独的线程,这样, 在其他线程等待时, 也会有线程在运行。对于使用同步操作的多线程 pipe server,请参阅 Multithreaded Pipe Server

  • 启动异步操作(Enabling Asynchronous Operation)

    当开启了管道句柄的重叠模式, 并且指定了指向 OVERLAPPED 结构的有效指针, ReadFile, WriteFile, TransactNamedPipeConnectNamedPipe 函数可以异步执行。如果指向 OVERLAPPED 结构的指针为 NULL, 则函数返回值可能错误地指示操作已经完成。因此,强烈建议,如果您创建了一个指定为 FILE_FLAG_OVERLAPPED 的句柄,并且想要其表现为异步行为,那么应该总是为其指定一个有效的重叠结构。

    OVERLAPPED 结构的 hEvent 成员必须包含一个手动重置(manual-reset)事件对象的句柄。这是一个由 CreateEvent 函数创建的同步对象(synchronization object)。启动重叠操作的线程使用事件对象来确定操作何时完成。不应该使用管道句柄来进行同步, 因为不知道是同时运行的哪一个操作完成使得管道句柄被设置为 signaled 状态。在同一个管道句柄上同时执行操作唯一可靠的技术是为每一个操作使用单独的 OVERLAPPED 结构和事件对象。详见 Synchronization

    同样, 也可以使用 GetQueuedCompletionStatusGetQueuedCompletionStatusEx 函数来接受重叠操作完成时的通知。在这种情况下, 不需要在 OVERLAPPED 的结构中设置手动重置事件, 并且在管道句柄上完成的操作与异步读或写操作是一样的。 详见 I/O Completion Ports

    ReadFile, WriteFile, TransactNamedPipeConnectNamedPipe 函数异步执行时, 下列之一发生:

    a. 如果操作在函数返回时完成,则返回值表示操作的成功或失败。如果发生错误, 则返回值为零, GetLastError 函数会返回除了 ERROR_IO_PENDING 之外的其他错误。
    b. 如果函数返回时, 操作还没有完成, 则返回值为零, 并且 GetLastError 函数会返回 ERROR_IO_PENDING 错误。 在这种情况下,调用线程必须等待操作完成。调用的线程必须调用 GetOverlappedResult 函数来确定结果。

  • 使用完成例程(Using Completion Routines)

    ReadFileExWriteFileEx 函数提供其他形式的重叠 I/O。和重叠的 ReadFileWriteFile 函数使用事件对象监控完成信号不同, 扩展函数指定了一个完成例程(completion routine)。完成例程是在读写操作完成时排队等待执行的函数。完成例程直到调用 ReadFileExWriteFileEx 的线程通过将 fAlertable 参数设置为 TRUE 调用 alertable 等待函数函数, 从而启动一个 alertable 等待操作才会启动。在 alertable 等待操作中,当 ReadFileExWriteFileEx 完成例程排队等待执行时,函数也会返回。pipe server 可以使用扩展函数来对连接到它的 client 执行一系列读写操作。每一个读写操作都指定一个完成例程,并且每个完成例程将在序列中启动下一步。详见 Named Pipe Server Using Completion Routines

命名管道的安全性和访问权限(Named Pipe Security and Access Rights)

Windows 安全性允许控制命名管道的访问, 详见 Access Control Model

当调用 CreateNamedPipe 函数时, 可以为命名管道指定安全描述符(Security Descriptors)。 安全描述符控制 client 和 server 端的命名管道的访问权限。如果将其指定为 NULL, 则命名管道获得默认安全描述符。 指定管道的缺省安全描述符中的 ACL 对LocalSystem 帐户、管理员和创建者所有者进行完全控制。他们还授予每个小组成员和匿名账户的阅读权限。

可以通过调用 GetSecurityInfo 函数来获取管道的安全描述符, 而通过调用 SetSecurityInfo 函数来改变管道的安全描述符。

当线程调用 CreateNamedPipe 函数打开某个已存在的命名管道 server 端的句柄, 系统在返回句柄之前执行访问检查。访问检查将线程的访问令牌和请求的访问权限与命名管道的安全描述符中的 DACL 进行比较。除了请求的访问权限外, DACL 还必须允许调用线程 FILE_CREATE_PIPE_INSTANCE 访问命名管道。

同样地, 当 client 调用 CreateFileCallNamedPipe 函数来连接命名管道 client 端, 在授予客户端访问权限之前,系统执行访问检查。

CreateNamedPipe 函数返回的句柄总是 SYNCHRONIZE 访问权限。根据其打开模式,可以有 GENERIC_READGENERIC_WRITE 权限。下面是每一种打开模式对应的访问权限。

Open modeAccess rights
PIPE_ACCESS_DUPLEX (0x00000003)FILE_GENERIC_READ, FILE_GENERIC_WRITE, and SYNCHRONIZE
PIPE_ACCESS_INBOUND (0x00000001)FILE_GENERIC_READ and SYNCHRONIZE
PIPE_ACCESS_OUTBOUND (0x00000002)FILE_GENERIC_WRITE and SYNCHRONIZE
posted @ 2017-08-06 16:58  杨领well  阅读(121)  评论(0编辑  收藏  举报