一.基本的理论知识
1
.什么是管道以及分类
管道是两个头的东西,每个头各连接一个进程或者同一个进程的不同代码,按照管道的类别分有两种管道,匿名的和命名的;按照管道的传输方向分也可以分成两种,单向的双向的。根据管道的特点,命名管道通常用在网络环境下不同计算机上运行的进程之间的通信(当然也可以用在同一台机的不同进程中)它可以是单向或双向的;而匿名管道只能用在同一台计算机中,它只能是单向的。匿名管道其实是通过用给了一个指定名字的有名管道来实现的。
使用管道的好处在于:读写它使用的是对文件操作的
api
,结果操作管道就和操作文件一样。即使你在不同的计算机之间用命名管道来通信,你也不必了解和自己去实现网络间通信的具体细节。
2
.管道的使用
A
.命名管道
命名管道是由服务器端的进程建立的,管道的命名必须遵循特定的命名方法,就是
"//./pipe/
管道名
"
,当作为客户端的进程要使用时,使用
"//
计算机名
//pipe/
管道名
"
来打开使用,具体步骤如下:
服务端通过函数
CreateNamedPipe
创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。服务端侦听来自客户端的连接请求,该功能通过
ConnectNamedPipe
函数实现。
客户端通过函数
WaitNamedPipe
来等待管道的出现,如果在超时值变为零以前,有一个管道可以使用,则
WaitNamedPipe
将返回
True
,并通过调用
CreateFile
或
CallNamedPipe
来呼叫对服务端的连接。
此时服务端将接受客户端的连接请求,成功建立连接,服务端
ConnectNamedPipe
返回
True
建立连接之后,客户端与服务器端即可通过
ReadFile
和
WriteFile
,利用得到的管道文件句柄,彼此间进行信息交换。
当客户端与服务端的通信结束,客户端调用
CloseFile
,服务端接着调用
DisconnectNamedPipe
。最后调用函数
CloseHandle
来关闭该管道。
B
.匿名管道
由于命名管道使用时作为客户端的程序必须知道管道的名称,所以更多的用在同一
“
作者
”
编写的服务器
/
工作站程序中,你不可能随便找出一个程序来要求它和你写的程序来通过命名管道通信。而匿名管道的使用则完全不同,它允许你和完全不相干的进程通信,条件是这个进程通过控制台
“console”
来输入输出,典型的例子是老的
Dos
应用程序,它们在运行时
Windows
为它们开了个
Dos
窗口,它们的输入输出就是
console
方式的。还有一些标准的
Win32
程序也使用控制台输入输出,如果在
Win32
编程中不想使用图形界面,你照样可以使用
AllocConsole
得到一个控制台,然后通过
GetStdHandle
得到输入或输出句柄,再通过
WriteConsole
或
WriteFile
把结果输出到控制台(通常是一个象
Dos
窗口)的屏幕上。虽然这些程序看起来象
Dos
程序,但它们是不折不扣的
Win32
程序,如果你在纯
Dos
下使用,就会显示
“The program must run under Windows!”
。
一个控制台有三个句柄:标准输入、标准输出和和标准错误句柄,标准输入、标准输出句柄是可以重新定向的,你可以用匿名管道来代替它,这样一来,你可以在管道的另一端用别的进程来接收或输入,而控制台一方并没有感到什么不同,就象
Dos
下的
>
或者
<
可以重新定向输出或输入一样。通常控制台程序的输入输出如下:
(
控制台进程
output) write ---->
标准输出设备(一般是屏幕)
(
控制台进程
input) read <----
标准输入设备(一般是键盘)
而用管道代替后:
(
作为子进程的控制台进程
output) write ---->
管道
1 ----> read (
父进程
)
(
作为子进程的控制台进程
input) read <---->
管道
2 <---- write (
父进程
)
使用匿名管道的步骤如下:
使用
CreatePipe
建立两个管道,得到管道句柄,一个用来输入,一个用来输出
准备执行控制台子进程,首先使用
GetStartupInfo
得到
StartupInfo
使用第一个管道句柄代替
StartupInfo
中的
hStdInput
,第二个代替
hStdOutput
、
hStdError
,即标准输入、输出、错误句柄
使用
CreateProcess
执行子进程,这样建立的子进程输入和输出就被定向到管道中
父进程通过
ReadFile
读第二个管道来获得子进程的输出,通过
WriteFile
写第一个管道来将输入写到子进程
父进程可以通过
PeekNamedPipe
来查询子进程有没有输出
子进程结束后,要通过
CloseHandle
来关闭两个管道。
二.管道使用的
API
函数集
CallNamedPipe
函数
|
函数原型:
BOOL CallNamedPipe( LPCTSTR lpNamedPipeName, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesRead, DWORD nTimeOut );
|
说明
:
这个函数由一个希望通过管道通信的一个客户进程调用。如有可能,它就同一个管道连接(在必要的情况下等候管道可用)。随后,它对指定的数据进行读写,然后将管道关闭
|
|
参数表
:
lpNamedPipeName:
LPCTSTR
,指定管道名,采用的形式是:
//./
管道
/
管道名。最多可达
256
个字符的长度,而且不用区分大小写。如果存在指定名字的一个管道,则创建那个管道的一个新实例
lpInBuffer:
LPVOID
,包含了要写入管道的数据的一个内存缓冲区
nInBufferSize:
DWORD
,
lpInBuffer
缓冲区中的字符数量
lpOutBuffer
:
LPVOID
,
指定一个内存缓冲区,用于装载从管道中读出的数据
nOutBufferSize
:
DWORD
,
指定一个长整数变量,用于装载来自管道的数据
lpBytesRead
:
LPDWORD
,
指定从管道中读出的字节数。会阅读单条消息。如
lpOutBuffer
的容量不够大,不能容下整条消息,则函数会返回
FALSE
,而且
GetLastError
会设为
ERROR_MORE_DATA
(消息中留下的任何字节都会丢失)
nTimeOut
:
DWORD
,下列常量之一:
1.
NMPWAIT_NOWAIT
:
如管道不可用,则立即返回一个错误
2.
NMPWAIT_WAIT_FOREVER
:
永远等候管道可用
.
3.
NMPWAIT_USE_DEFAULT_WAIT
:
使用管道的默认超时设置,这个设置是用
CreateNamedPipe
函数指定的
|
|
ConnectNamedPipe
函数
|
函数原型:
BOOL ConnectNamedPipe( HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped );
|
说明:
指示一台服务器等待下去,直至客户机同一个命名管道连接
|
返回值:
BOOL
,如
lpOverlapped
为
NULL
,那么:
1.
如管道已连接,就返回
Ture
(非零);如发生错误,或者管道已经连接,就返回零(
GetLastError
此时会返回
ERROR_PIPE_CONNECTED
)
2.
lpOverlapped
有效,就返回零;如管道已经连接,
GetLastError
会返回
ERROR_PIPE_CONNECTED
;如重叠操作成功完成,就返回
ERROR_IO_PENDING
。在这两种情况下,倘若一个客户已关闭了管道,且服务器尚未用
DisconnectNamedPipe
函数同客户断开连接,那么
GetLastError
都会返回
ERROR_NO_DATA
|
参数
:
hNamedPipe
:
HANDLE
,管道的句柄
lpOverlapped
:
LPOVERLAPPED
,如设为
NULL
(传递
ByVal As Long
),表示将线程挂起,直到一个客户同管道连接为止。否则就立即返回;此时,如管道尚未连接,客户同管道连接时就会触发
lpOverlapped
结构中的事件对象。随后,可用一个等待函数来监视连接
|
适用平台
:
Windows NT
|
注释:
可用这个函数将一个管道换成同另一个客户连接,但首先必须用
DisconnectNamedPipe
函数断开同当前进程的连接
|
|
CreateNamedPipe
函数
|
函数原型:
HANDLE CreateNamedPipe( LPCTSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes);
|
说明:
创建一个命名管道。返回的句柄由管道的服务器端使用
|
返回值:
HANDLE
,如执行成功,返回管道的句柄。
INVALID_HANDLE_VALUE
表示失败
.
会设置
GetLastError
|
参数:
lpName
:
LPCTSTR
,指定管道名,采用的形式是:
//./
管道
/
管道名
。最多可达
256
个字符的长度,而且不用区分大小写。如果存在指定名字的一个管道,则创建那个管道的一个新实例
dwOpenMode
:
DWORD
,
下述常数组的一个组合
下述常数之一(对于管道的所有实例都要一样):
1.
PIPE_ACCESS_DUPLEX
管道是双向的
2.
PIPE_ACCESS_INBOUND
数据从客户端流到服务器端
3.
PIPE_ACCESS_OUTBOUND
数据从服务器端流到客户端
下述常数的任意组合
1.
FILE_FLAG_WRITE_THROUGH
在网络中建立的字节型管道内,强迫数据在每次读写操作的时候通过网络传输。否则传输就可能延迟
2.
FILE_FLAG_OVERLAPPED
允许(但不要求)用这个管道进行异步(重叠式)操作
常数
WRITE_DAC
,
WRITE_OWNER
和
ACCESS_ SYSTEM_SECURITY
提供了附加的安全选项
dwPipeMode
:
DWORD
,下述常数组的一个组合
下述常数之一(管道的所有实例都必须指定相同的常数)
1.
PIPE_TYPE_BYTE
数据作为一个连续的字节数据流写入管道
2.
PIPE_TYPE_MESSAGE
数据用数据块(名为
“
消息
”
或
“
报文
”
)的形式写入管道
下述常数之一:
1.
PIPE_READMODE_PIPE
数据以单独字节的形式从管道中读出
2.
PIPE_READMODE_MESSAGE
数据以名为
“
消息
”
的数据块形式从管道中读出(要求指定
PIPE_TYPE_MESSAGE
)
下述常数之一:
1.
PIPE_WAIT
同步操作在等待的时候挂起线程
2.
PIPE_NOWAIT
(不推荐!)
同步操作立即返回。这样可为异步传输提供一种落后的实现方法,已由
Win32
的重叠式传输机制取代了
nMaxInstances
:
DWORD
,这个管道能够创建的最大实例数量。必须是
1
到常数
PIPE_UNLIMITED_INSTANCES
间的一个值。它对于管道的所有实例来说都应是相同的
nOutBufferSize
:
DWORD
,建议的输出缓冲区长度;零表示用默认设置
nInBufferSize
:
DWORD
,建议的输入缓冲区长度;零表示用默认设置
nDefaultTimeOut
:
DWORD
,管道的默认等待超时。对一个管道的所有实例来说都应相同
lpSecurityAttributes
:
LPSECURITY_ATTRIBUTES
,指定一个
SECURITY_ATTRIBUTES
结构,或者传递零值(将参数声明为
ByVal As Long
,并传递零值),以便使用不允许继承的一个默认描述符
|
适用平台:
Windows NT
|
|
CreatePipe
函数
|
函数原型:
BOOL CreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize );
|
说明:
创建一个匿名管道
|
|
参数
:
phReadPipe
:
PHANDLE
,指定一个变量,设为管道读入(输出)端的一个句柄
phWritePipe
:
PHANDLE
,指定一个变量,设为管道写入(输入)端的一个句柄
lpPipeAttributes
:
LPSECURITY _ATTRIBUTES
,指定一个
SECURITY_ATTRIBUTES
结构,或者传递零值,以便使用不允许继承的一个默认描述符
nSize
:
DWORD
,管道缓冲区的建议大小。零表示用默认值
|
注解:
匿名管道不允许异步操作,所以如在一个管道中写入数据,且缓冲区已满,那么除非另一个进程从管道中读出数据,从而腾出了缓冲区的空间,否则写入函数不会返回
|
|
DisconnectNamedPipe
函数
|
函数原型:
BOOL DisconnectNamedPipe( HANDLE hNamedPipe );
|
说明:
断开一个客户与一个命名管道的连接
|
|
参数:
hNamedPipe Long
,管道的句柄
|
适用平台:
Windows NT
|
注解
:如客户尚未在它自己那端关闭管道句柄,下次试图访问管道的时候就会发生错误
|
|
GetNamedPipeHandleState
函数
|
函数原型:
BOOL GetNamedPipeHandleState(HANDLE hNamedPipe, LPDWORD lpState, LPDWORD lpCurInstances, LPDWORD lpMaxCollectionCount, LPDWORD lpCollectDataTimeout, LPTSTR lpUserName, DWORD nMaxUserNameSize);
|
说明
:
获取一个命名管道当前的状态信息
|
|
参数
:
hNamedPipe
Long
,指定一个命名管道的句柄
lpState
Long
,用于装载下述一个或多个常数的长整数变量
PIPE_NOWAIT
管道设置成永不堵塞,这种模式很少使用
PIPE_READMODE_MESSAGE
管道设置成读取消息
lpCurInstances
Long
,装载这个管道目前存在的实例数量
lpMaxCollectionCount
Long
,如管道设置成通过一个网络传输数据,就用这个变量装载通过管道发送之前可排队等候的最大数据量
lpCollectDataTimeout
Long
,如管道设置成通过一个网络传输数据,就在这里指定一个长整数变量,用它装载进行一次网络数据传输前需要等候的最长时间
lpUserName
String
,如这是个服务器句柄,就在这里指定一个字串缓冲区,在其中载入客户应用程序的用户名。可设为
vbNullString
,表示不取回信息
nMaxUserNameSize
Long
,指定
lpUserName
缓冲区的长度,可以为零
|
|
GetNamedPipeInfo
函数
|
函数原型
:
BOOL GetNamedPipeInfo(HANDLE hNamedPipe, LPDWORD lpFlags, LPDWORD lpOutBufferSize, LPDWORD lpInBufferSize, LPDWORD lpMaxInstances);
|
说明
:获得指定命名管道的信息
|
返回值
:如果函数执行成功,返回值非零,否则,返回值为零,此时调用
GetLastError
函数获得扩展错误信息
|
参数
:
hNamedPipe
:命名管道句柄。这个句柄具有命名管道的
GENERIC_READ
访问权限。
lpFlags
:指定一个识别命名管道类型的
32
位变量。如果这个信息不需获得,可以给此参数置
NULL
。否则使用以下的值:
1.
PIPE_CLIENT_END
这个句柄是关于一个命名管道的客户端,此值被默认
2.
PIPE_SERVER_END
这个句柄是关于命名管道的服务器端。如果这个值没有被指定,这个命名管道句柄是关于客户端的
3.
PIPE_TYPE_BYTE
命名管道是一个字节管道型,此值被默认
4.
PIPE_TYPE_MESSAGE
命名管道是一个消息管道。如果这个值没有被指定,则默认为字节管道型
lpOutBufferSize
:一个
32
变量地址。用来按字节,返回输出数据缓冲的尺寸。如果缓冲值为零,则这个缓冲区没有按要求分配。如果镇魂歌信息不需要的,可以被置
NULL.
lpInBufferSize
:一个
32
变量地址。用来按字节,返回输入数据缓冲的尺寸。如果缓冲值为零,则这个缓冲区没有按要求分配。如果这个信息不需要的,可以被置
NULL.
lpMaxInstances
:一个
32
变量地址。用来获得被创建的管道实例的最大尺寸。如果此位被设置为
PIPE_UNLIMITED_INSTANCES,
被创建的管道实例的最大尺寸被按照系统的可容量所限制。如果这个信息不需要的,可以被置
NULL.
|
|
PeekNamedPipe
函数
|
函数原型
:
BOOL PeekNamedPipe(HANDLE hNamedPipe, LPVOID lpBuffer,DWORD nBufferSize, LPDWORD lpBytesRead, LPDWORD lpTotalBytesAvail, LPDWORD lpBytesLeftThisMessage );
|
说明
:
预览一个管道中的数据,或取得与管道中的数据有关的信息
|
|
参数
:
hNamedPipe
HANDLE
,指定一个管道的句柄。这并不一定是某个命名管道的句柄
——
匿名管道同样适用
lpBuffer
LPVOID
,指定要装载数据的一个缓冲区的头一个字符。可以为零
nBufferSize
DWORD
,
lpBuffer
缓冲区长度
lpBytesRead
LPDWORD
,保存装载到缓冲区的字符数量
lpTotalBytesAvail
LPDWORD
,保存管道中可用的字符数量
lpBytesLeftThisMessage
Long
,保存这次读操作后仍然保留在消息中的字符数。只能为那些基于消息的命名管道设置
|
注解
:
由这个函数读入的数据实际并不能从管道中删除。如果要对一个管道进行轮询,了解是否有可能数据,那么使用这个函数特别理想
|
|
SetNamedPipeHandleState
函数
|
函数原型
:
BOOL SetNamedPipeHandleState(HANDLE hNamedPipe, LPDWORD lpMode, LPDWORD lpMaxCollectionCount, LPDWORD lpCollectDataTimeout );
|
说明
:
设置与一个命名管道的运作有关的信息
|
|
参数:
hNamedPipe
HANDLE
,指定一个命名管道的句柄
lpMode
LPDWORD
,下列常数的一个或多个:
PIPE_WAIT
,
PIPE_NOWAIT
,
PIPE_READMODE_BYTE
以及
PIPE_READMODE_MESSAGE
。请参考
CreateNamedPipe
函数,了解有关这些标志的进一步情况
pMaxCollectionCount
LPDWORD
,如管道设为通过一个网络传输数据,则在这里指定通过管道发送之前可排除等候的最大数据量
lpCollectDataTimeout
LPDWORD
,如管道设为通过一个网络传输数据,则在这里指定网络数据传输前能够忍受的最长等候时间(超时)
|
|
TransactNamedPipe
函数
|
函数原型
:
BOOL TransactNamedPipe( HANDLE hNamedPipe, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesRead, LPOVERLAPPED lpOverlapped );
|
说明
:
该函数在单独一个函数中同时合并了对管道的读、写操作。客户和服务器进程都可用它
|
返回值
:
BOOL
,如操作已结束,则返回
TRUE(
非零
);
否则返回零。在异步模式中,
GetLastError
会设置成
ERROR_IO_PENDING
,而且操作会继续在后台进行。可测试
lpOverlapped
结构中的事件对象,了解操作是否结束
|
参数
:
hNamedPipe
HANDLE
,指定一个消息类型的命名管道的句柄
lpInBuffer
LPVOID
,指定一个内存缓冲区,在其中包含要写入管道的数据
nInBufferSize
DWORD
,指定
lpInBuffer
缓冲区中的字节数量
lpOutBuffer
LPVOID
,指定一个内存缓冲区,用于装载从管道中读入的数据
nOutBufferSize
DWORD
,用于装载来自管道的数据
lpBytesRead
LPDWORD
,指定要从管道读入的字节数量。会读入单条消息。如由于
lpOutBuffer
不够大,不能容下完整的消息,那么函数会返回
FALSE
,而且
GetLastError
会设为
ERROR_MORE_DATA
(消息中剩下的所有字节都会丢失)
lpOverlapped
LPOVERLAPPED
,可以为
NULL
(变成
ByVal As Long
,并传递零值),或指定包含了一个事件对象的
OVERLAPPED
结构
|
注解
:
如
lpOverlapped
设为
NULL
,或者句柄没有创建成
FILE_FLAG_OVERLAPPED
样式,那么除非读和写操作都完成,否则函数不会返回
|
|
WaitNamedPipe
函数
|
函数原型
:
BOOL WaitNamedPipe(LPCTSTR lpNamedPipeName, DWORD nTimeOut );
|
|
|
参数
:
lpNamedPipeName
LPCTSTR
,指定要连接的管道名称
nTimeOut
DWORD
,以毫秒数表示的等待时间,或者下述常数之一:
1.
NMPWAIT_USE_DEFAULT_WAIT
使用管道创建时的默认超时设定
2.
NMPWAIT_WAIT_FOREVER
永远等待
|
|
|