“命名管道”(Named Pipes)是一种简单的进程间通信(IPC)机制,Windows NT,Windows 2000、Windows 95以及Windows 98(但不包括
Windows CE)均支持。命名管道可在同一台计算机的不同进程之间,或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或
双向的数据通信。用命名管道来设计应用程序实际非常简单,并不需要事先深入掌握基层网络传送协议(如TCP/IP或IPX)的知识。这是由于
命名管道利用了微软网络提供者(MSNP)重定向器,通过一个网络,在各进程间建立通信。这样一来,应用程序便不必关心网络协议的细节。
命名管道作为网络通信方案时,充分利用了 Windows NT及Windows 2000内建的安全特性。
命令管道是围绕Windows文件系统设计的一种机制,采用“命名管道文件系统”(Named Pipe File System, NPFS)接口。因此,客户机和服
务器应用可利用标准的Win32文件系统API函数(如ReadFile和WriteFile)来进行数据的收发。通过这些API函数,应用程序便可直接利用
Win32文件系统命名规范,以及Windows NT/Windows 2000文件系统的安全机制。对NPFS来说,命名管道是用“通用命名规范”(UNC)来标识的。
命名管道命名规范:
\\server\pipe[\path]\name
第一部分\\server指定一个服务器的名字,命名管道便是在那个服务器上创建的,而且要由它对进入的连接请求进行“监听”。第二部分
\Pipe是一个不可变化的“硬编码”字串(不区分大小写),用于指出该文件从属于NPFS。而第三部分[\path]\name则使应用程序可以“唯一”
定义及标定一个命名管道的名字,而且可在这里设置多级目录。
字节模式及消息模式:
命名管道提供了两种基本通信模式:字节模式和消息模式。在字节模式中,数据以一个连续的字节流的形式,在客户机与服务器之间流动。
这意味着,对客户机应用和服务器应用来说,在任何一个特定的时间段内,它们不能准确知道有多少字节从管道中读入或者写入管道。因
此,在一方写入某个数量的字节,并不表示在另一方会读出等量的字节。这样一来,客户机和服务器在传输数据的时候,便不必关心数据
的内容。而在消息模式中,客户机和服务器则通过一系列不连续的数据单位,进行数据的收发。每次在管道上发出了一条消息后,它必须
作为一条完整的消息读入。
命名管道编程:
命名管道编程接口需要winbase.h头文件(该头文件已经包含在windows.h中了)和kernel32.lib库文件。开发命名管道应用时,所有Win32
API函数(CreateFile和CreateNamedPipe除外)都会在调用失败的前提下返回 0值。CreateFile和CreateNamedPipe返回的则是
INVALID_HANDLE_VALUE。若这两个函数中任何一个调用失败,应用程序可调用GetLastError函数,取得与这一次失败有关的特定信息。在
Winerror.h头文件中提供了完整的错误代码清单。
注意:命名管道服务器应用只能在 Windows NT或Windows 2000上工作——Windows 95和Windows 98不允许应用程序创建命名管道。
命名管道服务器:
创建服务器的步骤如下:
1)使用API函数CreateNamedPipe,创建一个命名管道实例句柄。
2)使用API函数ConnectNamedPipe,在命名管道实例上监听客户机连接请求。
3)分别使用ReadFile和WriteFile这两个API函数,从客户机接收数据,或将数据发给客户机。
4)使用API函数DisconnectNamedPipe,关闭命名管道连接。
5)使用API函数CloseHandle,关闭命名管道实例句柄。
CreateNamedPipe定义如下:
HANDLE CreateNamedPipe(
LPCTSTR lpName, //命名管道的名字,必须是\\.\pipe[\path]\name,因为不能在一台远程机器上创建命名管道。
DWORD dwOpenMode, //指示命名管道创建好后,它的传输方向、I/O控制以及安全模式。在表4-1中,总结了可以选用的所有标志设定。创建一个管道时,需要将这些标志通过OR(或)运算组合到一起。
DWORD dwPipeMode, //指定了命名管道的读、写以及等待模式。在表4-2中,总结了可以选用的所有标志设定。创建一个管道时,需要将这些标志通过OR(或)运算组合到一起。
DWORD nMaxInstances, //最多可以接受多少个从客户机到服务器的连接,取值范围在1到PIPE_UNLIMITED_INSTANCES(无限实例)之间。
DWORD nOutBufferSize, //
DWORD nInBufferSize, //nOutBufferSize和nInBufferSize分别指定了为内部输入及输出缓冲区长度保留的字节数。应设为一个合适的值,使之与调用ReadFile和WriteFile时的发送/接收缓冲区大小相符,但如果实际写入的数据大于这个值,系统会自动扩大缓冲区。
DWORD nDefaultTimeout, //客户机等待同一个命令管道建立连接的最长时间,单位毫秒。
LPSECURITY_ATTRIBUTES lpSecurityAttributes //允许应用程序为命名管道指定一个安全描述符,并决定一个子进程是否能够继承新建的句柄。假如将该参数设为NULL(空),命名管道便会获得一个默认的安全描述符,同时句柄不可继承。默认的安全描述符允许命名管道拥有与创建它的进程相同的安全限制以及访问控制
);
表4-1 命名管道打开模式标志
------------------------------------------------------------------------------------------------------------------
标 志 说 明
------------------------------------------------------------------------------------------------------------------
数据传送方向 PIPE_ACCESS_DUPLEX 双向式管道:服务器和客户机进程都能在管道上读写数据。
PIPE_ACCESS_OUTBOUND 数据在管道中只能从服务器朝客户机流动。
PIPE_ACCESS_INBOUND 数据在管道中只能从客户机朝服务器流动。
------------------------------------------------------------------------------------------------------------------
I/O控制 FILE_FLAG_WRITE_THROUGH 只适用于字节模式下的管道。对那些用来向命名管道写入数据的函数来说,除非写入
的数据通过网络传送出去,而且进入远程计算机的管道缓冲区内,否则不会返回。
FILE_FLAG_OVERLAPPED 允许执行读、写和连接操作的函数使用重叠式I/O。
------------------------------------------------------------------------------------------------------------------
安全模式 WRITE_DAC 应用程序可对命名管道的DACL进行写操作。
ACCESS_SYSTEM_SECURITY 应用程序可对命名管道的SACL进行写操作。
WRITE_OWNER 应用程序可对命名管道的“所有人”及“组”SID进行写操作。
------------------------------------------------------------------------------------------------------------------
表4-2 命名管道的读写模式标志
---------------------------------------------------------------------
模 式 标 志 说 明
---------------------------------------------------------------------
写 PIPE_TYPE_BYTE 数据以字节流的形式,写入管道
PIPE_TYPE_MESSAGE 数据以消息流的形式,写入管道
读 PIPE_READMODE_BYTE 数据以字节流的形式,从管道中读入
PIPE_READMODE_MESSAGE 数据以消息流的形式,从管道中读入
等待 PIPE_WAIT 允许“锁定”模式
---------------------------------------------------------------------
ConnectNamedPipe的定义如下:
BOOL ConnectNamedPipe(
HANDLE hNamedPipe, //由CreateNamedPipe函数返回的句柄
LPOVERLAPPED lpOverlapped //在使用FILE_FLAG_OVERLAPPED标志创建命名管道的情况下,可以使用这个参数让这个连接以异步方式工作,如果这个参数为NULL,则以锁定模式工作.
);
一个命名管道客户机成功建立了与服务器的连接之后,ConnectNamedPipe的调用便会结束。随后,服务器可用WriteFile函数,向客户机自由地
发送数据;或者使用ReadFile函数,从客户机那里接收数据。服务器完成了与一个客户机的通信之后,便应调用DisconnctNamedPipe函数,以
关闭此次通信会话。
以下是一个简单的服务器例子:
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <stdio.h>
void main(void)
{
HANDLE hPipe;
DWORD dwBytesRead;
CHAR buffer[256];
if((hPipe=CreateNamedPipe("\\\\.\\pipe\\Jim",PIPE_ACCESS_DUPLEX,PIPE_BYTE | PIPE_READMODE_BYTE,1,0,0,1000,NULL))==INVALID_HANDLE_VALUE)
{
printf("ERROR: CreateNamedPipe: %d\n",GetLastError());
return ;
}
printf("Server is now running...\n");
if(ConnectNamedPipe(hPipe,NULL)==0)
{
printf("ERROR: ConnectNamePipe: %d\n",GetLastError());
CloseHandle(hPipe);
return ;
}
if(ReadFile(hPipe,buffer,sizeof(buffer),&dwBytesRead,NULL)<=0)
{
printf("ERROR: ReadFile: %d\n",GetLastError());
CloseHandle(hPipe);
return ;
}
printf("%.*s\n",dwBytesRead,buffer);
if(DisconnectNamedPipe(hPipe)==0)
{
printf("ERROR: DisconnectNamedPipe: %d\n",GetLastError());
return ;
}
CloseHandle(hPipe);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
如何构建空的授权访问控制列表(DACL):
在Windows NT或Windows 2000操作系统中,应用程序使用Win32 API函数创建诸如文件及命名管道这种要求安全保护的对象时,操作系统会赋
予应用程序设置访问控制列表的权力,这是通过指定一个SECURITY_ATTRIBUTES结构来进行的。该结构的定义如下:
typedef struct _SECURITY_ATTRIBUTES{
DWORD nLength; //结构的大小,设为sizeof(SECURITY_ATTRIBUTES)。
LPVOID lpSecurityDescriptor; //指向一个SECURITY_DESCRIPTOR(安全描述符)结构,为一个对象设置访问权限。
BOOL bInheritHandle //是否继承父对象的安全属性句柄。
} SECURITY_ATTRIBUTES;
在SECURITY_DESCRIPTOR结构中,包含了一个DACL字段,它定义了哪些用户和用户组有权访问对象,如果该字段为NULL,那么任何用户及用户组
都可以访问对象。要访问SECURITY_DESCRIPTOR结构,必须通过API函数来实现,步骤如下:
1)创建并初始化一个SECURITY_DESCRIPTOR结构,需要调用InitializeSecurityDescriptor() API函数。
2)为SECURITY_DESCRIPTOR结构分配一个空的DACL,需要调用SetSecurityDescriptorDacl() API函数。
3)将新建的SECURITY_DESCRIPTOR结构的指针赋值给SECURITY_ATTRIBUTES结构的lpSecurityDescriptor字段。
下面是例子:
//////////////////////////////////////////////////////////////////////////////////////////////////
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd;
if(InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION)==0)
{
printf("Error: InitializeSecurityDescriptor: %d", GetLastError());
return ;
}
if(SetSecurityDescriptorDacl(&sd,TRUE,NULL,FALSE)==0)
{
printf("Error: SetSecurityDescriptorDacl: %d\n",GetLastError());
return ;
}
sa.nLength=sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor=&sd;
sa.bInheritHandle=TRUE;
/////////////////////////////////////////////////////////////////////////////////////////////////
高级服务器:
命名管道服务器能拥有多个管道实例,以使客户机能够建立同服务器的两个或更多的连接;管道实例的数量要受到CreateNamedPipe调用的
nMaxInstances参数指定的数字的限制。要想同时控制不止一个的管道实例,服务器必须考虑使用多个线程,或者使用异步Win32 I/O机制
(比如重叠式I/O以及完成端口等),分别为每个管道实例提供服务。采用异步I/O机制,服务器可从单独一个应用程序线程中,同时为所
有管道实例提供服务。
1、线程方式:
/////////////////////////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#define NUM_PIPES 5
DWORD WINAPI PipeInstanceProc(LPVOID lpParam);
int main(int argc, char* argv[])
{
HANDLE hThread;
DWORD dwThreadId;
for(int i=0;i<NUM_PIPES;++i)
{
if((hThread=CreateThread(NULL,0,PipeInstanceProc,NULL,0,&dwThreadId))==INVALID_HANDLE_VALUE)
{
printf("Error: CreateThread: %d\n",GetLastError());
return 1;
}
CloseHandle(hThread);
}
printf("Server is now running...\nPress any key to stop the server.\n");
_getch();
return 0;
}
DWORD WINAPI PipeInstanceProc(LPVOID lpParam)
{
HANDLE hPipe;
CHAR buffer[256];
DWORD dwBytesRead;
DWORD dwBytesWritten;
if((hPipe=CreateNamedPipe("\\\\.\\pipe\\MyPipe",PIPE_ACCESS_DUPLEX,PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,NUM_PIPES,0,0,1000,NULL))==INVALID_HANDLE_VALUE)
{
printf("Error: CreateNamedPipe: %d\n",GetLastError());
return 1;
}
while(1)
{
if(ConnectNamedPipe(hPipe,NULL)==0)
{
printf("Error: ConnectNamedPipe: %d\n",GetLastError());
break;
}
while(ReadFile(hPipe,buffer,sizeof(buffer),&dwBytesRead,NULL)>0)
{
printf("Read %d bytes from client, content is: %s",dwBytesRead,buffer);
if(WriteFile(hPipe,buffer,dwBytesRead,&dwBytesWritten,NULL)==0)
{
printf("Error: WriteFile: %d\n",GetLastError());
break;
}
}
if(DisconnectNamedPipe(hPipe)==0)
{
printf("Error: DisconnectNamedPipe: %d\n",GetLastError());
break;
}
}
CloseHandle(hPipe);
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
2. 重叠式I/O:
重叠式I/O是一种特殊的输入/输出机制,允许Win32 API函数(如ReadFile和WriteFile)在发出I/O请求之后,以异步方式工作。具体的工
作原理是:向这些API函数传递一个OVERLAPPED(重叠式)结构,然后使用API函数GetOverlappedResult,从原来那个OVERLAPPED结构中,取
得一次I/O请求的结果。如果在使用重叠式结构的前提下,调用一个Win32 API函数,那么调用无论如何都会立即返回!
要想通过重叠I/O机制,开发一个高级的命名管道服务器,令其同时负责对多个命名管道实例的管理,首先需要调用CreateNamedPipe函数,
同时将它的nMaxInstances参数设为大于1的一个值,将dwOpenMode标志设为FILE_FLAG_OVERLAPPED。
下面是使用重叠式I/O的服务器例子:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NamedPipe_TestServer3.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#define NUM_PIPES 5
#define BUFFER_SIZE 256
int main(int argc, char* argv[])
{
HANDLE arr_hPipe[NUM_PIPES];
DWORD dwBytesTransferred;
CHAR arrBuffer[NUM_PIPES][BUFFER_SIZE];
OVERLAPPED arrOverlapped[NUM_PIPES];
HANDLE arr_hEvent[NUM_PIPES];
// For each pipe handle instance, the code nust maintain the pipe's current state, whic determines if a
// ReadFile or WriteFile is posted on the named pipe. This is done using the arr_bDataRead variable array.
// By knowing each pipe's current state, the code can determine what the next I/O operation should be.
BOOL arr_bDataRead[NUM_PIPES];
DWORD dwRet;
DWORD dwIndex;
for(int i=0;i<NUM_PIPES;++i)
{
if((arr_hPipe[i]=CreateNamedPipe("\\\\.\\pipe\\MyPipe",PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,NUM_PIPES,0,0,1000,NULL))==INVALID_HANDLE_VALUE)
{
printf("Error: CreateNamedPipe: \nPipe index: %d.\nError code: %d\n",i,GetLastError());
return 1;
}
//Create an event handle for each pipe instance. This will be used to monitor overlapped I/O activity
//on each pipe.
if((arr_hEvent[i]=CreateEvent(NULL,TRUE,FALSE,NULL))==NULL)
{
printf("Error: CreateEvent: \nPipe index: %d.\nError code: %d\n",i,GetLastError());
continue;
}
//Maintain a state flag for each pipe to determine when data is to be read from or written to the pipe.
arr_bDataRead[i]=FALSE;
ZeroMemory(&arrOverlapped[i],sizeof(OVERLAPPED));
arrOverlapped[i].hEvent=arr_hEvent[i];
//Listen for client connections using ConnectNamedPipe
if(ConnectNamedPipe(arr_hPipe[i],&arrOverlapped[i])==0)
{
if(GetLastError()!=ERROR_IO_PENDING)
{
printf("Error: ConnectNamedPipe: \nPipe index: %d.\nError code: %d\n",i,GetLastError());
continue;
}
}
}
printf("Server is now running...\n");
//Read and echo data back to Named Pipe clients forever
while(1)
{
if((dwRet=WaitForMultipleObjects(NUM_PIPES,arr_hEvent,FALSE,INFINITE))==WAIT_FAILED)
{
printf("Error: WaitForMultipleObjects: Error code: %d\n",GetLastError());
return 2;
}
dwIndex=dwRet-WAIT_OBJECT_0;
ResetEvent(arr_hEvent[dwIndex]);
//Check overlapped results, and if they fail, reestablish communication for a new client; otherwise
//process read and write operations with the client.
if(GetOverlappedResult(arr_hPipe[dwIndex],&arrOverlapped[dwIndex],&dwBytesTransferred,TRUE)==0)
{
printf("Error: GetOverlappedResult: Error code:%d\n",GetLastError());
if(DisconnectNamedPipe(arr_hPipe[dwIndex])==0)
{
printf("Error: DisconnectNamedPipe: Error code: %d\n",GetLastError());
continue;
}
if(ConnectNamedPipe(arr_hPipe[dwIndex],&arrOverlapped[dwIndex])==0)
{
if(GetLastError()!=ERROR_IO_PENDING)
{
printf("Error: ConnectNamedPipe: \nPipe index: %d.\nError code: %d\n",dwIndex,GetLastError());
}
}
arr_bDataRead[dwIndex]=FALSE;
}
else
{
//Check the state of the pipe. If arr_bDataRead equals FALSE, post a read on the pipe for incoming
//data, else prepare to echo data back to the client.
if(arr_bDataRead[dwIndex]==FALSE)
{
//Prepare to read data from a client by posting a ReadFile operation
ZeroMemory(&arrOverlapped[dwIndex],sizeof(OVERLAPPED));
arrOverlapped[dwIndex].hEvent=arr_hEvent[dwIndex];
if(ReadFile(arr_hPipe[dwIndex],arrBuffer[dwIndex],BUFFER_SIZE,NULL,&arrOverlapped[dwIndex])==0)
{
if(GetLastError()!=ERROR_IO_PENDING)
{
printf("Error: ReadFile: Error code: %d\n",GetLastError());
continue;
}
}
arr_bDataRead[dwIndex]=TRUE;
}
else
{
//Write received data back to the client by posting a WriteFile operation
printf("Received %d bytes, echo bytes back\n",dwBytesTransferred);
ZeroMemory(&arrOverlapped[dwIndex],sizeof(OVERLAPPED));
arrOverlapped[dwIndex].hEvent=arr_hEvent[dwIndex];
if(WriteFile(arr_hPipe[dwIndex],arrBuffer[dwIndex],dwBytesTransferred,NULL,&arrOverlapped[dwIndex])==0)
{
if(GetLastError()!=ERROR_IO_PENDING)
{
printf("Error: WriteFile: Error code: %d\n",GetLastError());
continue;
}
}
arr_bDataRead[dwIndex]=FALSE;
}
}
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
安全模拟:
选择命名管道作为网络编程方案,一个最大的好处是它依赖于Windows NT及Windows 2000的安全机制,在客户机试图建立同服务器的连接时,
实现对访问的控制。Windows NT和Windows 2000安全机制具有“模拟”能力,允许一个命名管道服务器应用在客户机的安全环境中执行。执
行一个命名管道服务器应用时,它通常会在启动该应用的那个进程的安全环境许可级别上工作。例如,假如拥有管理员权限的某人启动了一
个命名管道服务器,服务器便有权访问Windows NT或Windows 2000系统上的几乎任何资源。此时,假如在CreateNamedPipe中指定的
SECURITY_DESCRIPTOR结构允许所有用户访问这个命名管道,就会埋下极大的安全隐患。服务器调用ConnectNamedPipe函数接受一个客户机连
接请求后,便可调用API函数ImpersonateNamedPipeClient,令自己的执行线程在客户机的安全环境下工作。该函数的定义如下:
BOOL ImpersonateNamedPipe(HANDLE hNamedPipe);
其中,hNamedPipe参数对应于由CreateNamedPipe返回的管道实例句柄。调用了这个函数之后,操作系统便会将服务器的线程安全环境变成客
户机的安全环境。这样一来,服务器便能保持对资源的访问控制,而无论由谁启动了这个进程。
若服务器的线程在客户机的安全环境中执行,它需要设置一个安全模拟级别。共有四个基本的模拟级别:匿名(Anonymous)、验证
(Identification)、模拟(Impersonation)以及委派(Delegation)。通过设置不同的级别,便可控制服务器在多大的程度上“类似”于
一个客户机,或者说,模拟的程度有多大。
服务器完成了对一个客户机会话的处理之后,便应调用RevertToSelf,恢复成自己最初的线程执行安全环境。RevertToSelf的定义如下:
BOOL RevertToSelf(VOID);
命名管道客户机:
步骤如下:
1)调用API函数WaitNamedPipe,等候一个命名管道实例可供使用。
2)调用API函数CreateFile,打开命名管道实例并建立连接。
3)调用API函数WriteFile和ReadFile,分别向服务器发送数据和从中接收数据。
4)调用API函数CloseHandle,关闭打开的命名管道会话。
WaitNamedPipe的定义如下:
BOOL WaitNamedPipe(
LPCTSTR lpNamedPipeName, //要检查的命名管道的名字
DWORD nTimeout //等待超时的时间值。
);
WaitNamedPipe调用成功后,就得调用CreateFile来打开命名管道以便建立与服务器的连接,函数定义如下:
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
参数说明:
1)lpFileName参数指定希望打开的那个管道的名字。
2)dwDesiredAccess参数定义了访问模式,应将其设为GENERIC_READ(用于从管道上读取数据),或设为GENERIC_WRITE(用于将数据写入管
道),或者用OR操作组合起来。
3)dwShareMode参数应设为0,因为一次只能有一个客户机访问一个管道实例。
4)lpSecurityAttributes参数应设为NULL,因为CreateFile不能创建命名管道。
5)dwCreationDisposition参数则应设为OPEN_EXISTING,意味着CreateFile函数会在命名管道不存在的情况下调用失败。
6)dwFlagsAndAttributes无论如何都要有标志FILE_ATTRIBUTE_NORMAL。另外,该参数还可以选择FILE_FLAG_WRITE_THROUGH、
FILE_FLAG_OVERLAPPED以及SECURITY_SQOS_PRESENT标志,并将它们同FILE_ATTRIBUTE_NORMAL标志通过OR运算合并到一起。其中,
FILE_FLAG_WRITE_THROUGH和FILE_FLAG_OVERLAPPED的工作方式类似于在表4-1总结的服务器模式标志。SECURITY_SQOS_PRESENT标志则用于
控制在一个命名管道服务器上,客户机的模拟安全级别。根据安全模拟级别,我们便可知道一个服务器进程与客户机进程在多大程度上类似。
客户机可在建立与服务器的连接时,指定这一信息。若客户机设定了SECURITY_SQOS_PRESENT标志,那么必须使用下文总结的一个或多个安
全标志:
■ SECURITY_ANONYMOUS
指定在“匿名”(Anonymous)安全级别上,对客户机加以模拟。服务器进程不可取得与客户机有关的身份验证信息,而且不能在客户机的
安全环境中执行。
■ SECURITY_IDENTIFICATION
指定在“验证”(Identification)安全级别上,对客户机加以模拟。服务器进程可获取与客户机有关的一些信息,比如安全标识符以及优
先权限等等。然而,却不能在客户机的安全环境中执行。假如命名管道客户机希望让服务器对客户机进行验证,但却不想让它完全扮演客户
机,这一设定便非常恰当。
■ SECURITY_IMPERSONATION
指定在“模拟”(Impersonation)安全级别上,对客户机加以模拟。此时,客户机希望服务器进程获取与客户机有关的信息,并可在服务
器的本地系统中,在客户机的安全环境中执行。使用这一标志,服务器便能以客户机的身份,访问服务器上的任何本地资源。在这种情况下,
服务器完全“模拟”或“扮演”了一个客户机。
■ SECURITY_DELEGATION
指定在“委派”(Delegation)安全级别上,对客户机加以模拟。服务器进程可取得与客户机有关的信息,并可同时在本地系统以及远程系
统上,使用客户机的安全场景执行。注意:只有服务器进程在Windows 2000上运行的时候,SECURITY_DELEGATION才可产生作用。
Windows NT 4(包括最新的SP6)并未实现委派安全机制。
■ SECURITY_CONTEXT_TRACKING
指出安全追踪模式是“动态”的。若未设定该标志,安全追踪模式便是“静态”的。
■ SECURITY_EFFECTIVE_ONLY
指出在客户机安全环境中,只有已启用的那些部分才可由服务器使用。假如未设定该标志,客户机安全环境的所有部分均可使用,无论是否
已经启用。
7)hTemplateFile对命名管道无效,应设为 NULL。
下面是客户机的例子:
////////////////////////////////////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <stdio.h>
#define PIPE_NAME "\\\\.\\pipe\\MyPipe"
int main(int argc, char* argv[])
{
HANDLE hPipe;
DWORD dwBytesWritten;
if(WaitNamedPipe(PIPE_NAME,NMPWAIT_WAIT_FOREVER)==0)
{
printf("Error: WaitNamedPipe: Error code: %d\n",GetLastError());
return 1;
}
if((hPipe=CreateFile(PIPE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL))==INVALID_HANDLE_VALUE)
{
printf("Error: CreateFile: Error code: %d\n",GetLastError());
return 2;
}
if(WriteFile(hPipe,"This is a test string",21,&dwBytesWritten,NULL)==0)
{
printf("Error: WriteFile: Error code: %d\n",GetLastError());
CloseHandle(hPipe);
return 3;
}
printf("Wrote %d bytes\n",dwBytesWritten);
CloseHandle(hPipe);
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
其他命名管道API调用:
■CallNamedPipe和TransactNamedPipe这两个API调用可有效减轻一个应用程序编码的复杂性。这两个函数均能在一次调用中,同时执行读和写
操作。其中,CallNamedPipe函数允许客户机应用建立与一个消息类型的管道的连接(假如当时没有可用的管道实例,便会一直等候下去),
然后在管道上读写数据,最后关闭这个管道。事实上,这几乎是一个完整的客户机应用,只是已在一个调用中全部写好了!CallNamedPipe的
定义如下:
BOOL CallNamedPipe(
LPCTSTR lpNamedPipeName, //命名管道的名字
LPVOID lpInBuffer, //指向发送数据缓冲区
DWORD nInBufferSize, //发送数据缓冲区的大小
LPVOID lpOutBuffer, //指向接收数据缓冲区
DWORD NOutBufferSize, //接收数据缓冲区的大小
LPDWORD lpBytesRead, //从管道读回的字节数。
DWORD nTimeout //在一个命名管道可用之前,最长能等待多久的时间。
);
■TransactNamedPipe函数既可在客户机应用中使用,亦可在服务器应用中使用。设计它的目的是为了将读操作与写操作整合到一个API调用之中。
这样一来,由于减轻了MSNP重定向器收发数据的负担,所以能在一定程度上优化网络I/O的性能。TransactNamedPipe的定义如下:
BOOL TransactNamedPipe(
HANDLE hNamedPipe, //由CreateNamedPipe或CreateFile返回的命名管道的句柄
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesRead, //以上参数同CallNamedPipe
LPOVERLAPPED lpOverlapped //指向一个OVERLAPPED结构以便让该函数以重叠I/O方式工作。
);
■GetNamedPipeHandleState 用于获得命名管道的信息,如:运行模式(消息或字节模式)、管道实例数和缓冲区信息等。在一个管道实例
的“存在时间”内,不同时间由该函数返回的信息可能会发生变化。该函数的定义如下:
BOOL GetNamedPipeHandleState(
HANDLE hNamedPipe, //命名管道句柄。
LPDWORD lpState, //工作模式,返回值可能有两个,一个是PIPE_NOWAIT,另一个是PIPE_READMODE_MESSAGE。
LPDWORD lpCurInstances, //管道实例的数量。
LPDWORD lpMaxCollectionCount, //实例发送给服务器之前,打算在客户机上收集的最大字节数。
LPDWORD lpCollectDataTimeout, //收集数据的超时值,超过这个值,命名管道就会通过网络付出信息。
LPTSTR lpUserName,
DWORD nMaxUserNameSize //lpUserName和nMaxUserNameSize定义了一个缓冲区,用来接收一个“空终止”字符串,它是客户机应用的用户名。
);
■SetNamedPipeHandleState 可以更改命名管道的状态信息,定义如下:
BOOL SetNamedPipeHandleState(
HANDLE hNamePipe, //命名管道的句柄
LPDWORD lpMode, //工作模式
LPDWORD lpMaxCollectionCount, //同GetNamedPipeHandleState
LPDWORD lpCollectDataTimeout //同GetNamedPipeHandleState
);
■GetNamedPipeInfo 也可以获得命名管道的信息,定义如下:
BOOL GetNamedPipeInfo(
HANDLE hNamedPipe, //命名管道的句柄
LPDWORD lpFlags, //命名管道的类型,可判断它是服务器还是客户机以有工作模式(字节或消息模式)
LPDWORD lpOutBufferSize, //输出缓冲区的大小。
LPDWORD lpInBufferSize, //输入缓冲区的大小。
LPDWORD lpMaxInstances //可以创建的命名管道实例的最大数量
);
■PeekNamedPipe 可对命名管道内的数据进行浏览,而不会将管道内的数据移出来。假如应用程序希望对进入的数据进行“轮询”,以免
调用ReadFile时发生锁定,便可使用这个函数;另外,如果应用程序需要在数据实际接收之前,先作一番检查,这个函数也是很有用的,
例如:应用程序可以先检查进入的消息的长度,再调节接收数据的缓冲区大小,然后再正式接收数据。PeekNamedPipe的定义如下:
BOOL PeekNamedPipe(
HANDLE hNamedPipe, //命名管道的句柄
LPVOID lpBuffer, //
DWORD nBufferSize,//lpBuffer和nBufferSize指定缓冲区和大小,用来从管道接收数据
LPDWORD lpBytesRead, //实际从管道中接收到的字节数
LPDWORD lpTotalBytesAvail, //可以从管道内接收到的字节总数
LPDWORD lpBytesLeftThisMessage //只对消息模式有用,当一条消息长度大于nBufferSize指定的缓冲区大小时,返回消息剩余数据的长度。在字节模式下,返回0。
);
命名管道的限制:
■命名管道名字的限制:
假设已经创建了一个名为\\.\pipe\MyPipes的命名管道,则不能再创建名为\\.\pipe\MyPipes\Pipe1的管道,因为\\.\pipe\MyPipes已经是
一个管道了,不可以作为路径使用来创建新管道。
■WriteFile向管道写入数据时,最大数据量限制在64KB之内,超过这个数值就会失败,GetLastError()会返回ERROR_MORE_DATA。
■如果在命名管道上使用重叠I/O,并且OVERLAPPED结构的Offset和OffsetHigh字段没有被设为0,那么WriteFile和ReadFile调用就会失败,
并返回ERROR_INVALID_PARAMETER错误。
■调用WaitNamedPipe函数时,如果传递给第一个参数的命名管道名字是无效的,那么在Windows95中,GetLastError()返回253错误代码,
而在Windows NT4中则返回161错误代码(ERROR_BAD_PATHNAME),为了解决这种不一致问题,建议将253错误代码解释成161错误代码。
■单台工作站上最大只能创建49个命名管道连接,超出部分会被忽略。
■如果一项服务以本地系统帐号运行,同时试图打开在Windows NT上运行的一个命名管道,那么操作会失败,返回拒绝访问错误,错误代码为5。