一、管道实现进程间通讯
基本的理论知识
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 );
|
说明:这个函数由一个希望通过管道通信的一个客户进程调用。如有可能,它就同一个管道连接(在必要的情况下等候管道可用)。随后,它对指定的数据进行读写,然后将管道关闭
|
返回值 :Long,非零表示成功,零表示失败。会设置GetLastError
|
参数表 :
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 );
|
说明:
创建一个匿名管道
|
返回值:
Long
,非零表示成功,零表示失败。会设置
GetLastError
|
参数:
phReadPipe:
PHANDLE
,指定一个变量,设为管道读入(输出)端的一个句柄
phWritePipe:
PHANDLE
,指定一个变量,设为管道写入(输入)端的一个句柄
lpPipeAttributes:
LPSECURITY _ATTRIBUTES
,指定一个
SECURITY_ATTRIBUTES
结构,或者传递零值,以便使用不允许继承的一个默认描述符
nSize:
DWORD
,管道缓冲区的建议大小。零表示用默认值
|
注解:
匿名管道不允许异步操作,所以如在一个管道中写入数据,且缓冲区已满,那么除非另一个进程从管道中读出数据,从而腾出了缓冲区的空间,否则写入函数不会返回
|
|
DisconnectNamedPipe函数
|
函数原型:
BOOL DisconnectNamedPipe( HANDLE hNamedPipe );
|
说明:
断开一个客户与一个命名管道的连接(服务器端与客户端都可调用)
|
返回值:
BOOL
,非零表示成功,零表示失败。会设置
GetLastError
|
参数:
hNamedPipe Long
,管道的句柄
|
适用平台:
Windows NT
|
注解
:如客户尚未在它自己那端关闭管道句柄,下次试图访问管道的时候就会发生错误
|
|
GetNamedPipeHandleState函数
|
函数原型:
BOOL GetNamedPipeHandleState(HANDLE hNamedPipe, LPDWORD lpState, LPDWORD lpCurInstances, LPDWORD lpMaxCollectionCount, LPDWORD lpCollectDataTimeout, LPTSTR lpUserName, DWORD nMaxUserNameSize);
|
说明:
获取一个命名管道当前的状态信息
|
返回值:
BOOL
,非零表示成功,零表示失败。会设置
GetLastError
|
参数:
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 );
|
说明:
预览一个管道中的数据,或取得与管道中的数据有关的信息
|
返回值:
BOOL
,非零表示成功,零表示失败。会设置
GetLastError
|
参数:
hNamedPipe
HANDLE
,指定一个管道的句柄。这并不一定是某个命名管道的句柄
——
匿名管道同样适用
lpBuffer
LPVOID
,指定要装载数据的一个缓冲区的头一个字符。可以为零
nBufferSize
DWORD
,
lpBuffer
缓冲区长度
lpBytesRead
LPDWORD
,保存装载到缓冲区的字符数量
lpTotalBytesAvail
LPDWORD
,保存管道中可用的字符数量
lpBytesLeftThisMessage
Long
,保存这次读操作后仍然保留在消息中的字符数。只能为那些基于消息的命名管道设置
|
注解:
由这个函数读入的数据实际并不能从管道中删除。如果要对一个管道进行轮询,了解是否有可能数据,那么使用这个函数特别理想
|
|
SetNamedPipeHandleState函数
|
函数原型:
BOOL SetNamedPipeHandleState(HANDLE hNamedPipe, LPDWORD lpMode, LPDWORD lpMaxCollectionCount, LPDWORD lpCollectDataTimeout );
|
说明:
设置与一个命名管道的运作有关的信息
|
返回值值:
BOOL
,非零表示成功,零表示失败。会设置
GetLastError
|
参数:
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 );
|
说明:
由一个客户进程调用,等候一个管道变成可用状态(比如服务器已调用
ConnectNamedPipe
函数同一个客户连接)
|
返回值:
BOOL
,非零表示成功;如果失败,或者管道不存在,则返回零。会设置
GetLastError
|
参数:
lpNamedPipeName
LPCTSTR
,指定要连接的管道名称
nTimeOut
DWORD
,以毫秒数表示的等待时间,或者下述常数之一:
1.
NMPWAIT_USE_DEFAULT_WAIT
使用管道创建时的默认超时设定
2. NMPWAIT_WAIT_FOREVER
永远等待
|
注解:
在这个函数之后,用
CreateFile
打开管道。注意从这个函数返回到调用
CreateFile
函数期间,倘若有另一个进程同管道连接,那么这个
CreateFile
调用可能失败
|
|
二、
BOOL WaitNamedPipe( LPCTSTR lpNamedPipeName, DWORD nTimeOut );
lpNamedPipeName 要打开的管道名,格式\\servername\pipe\pipename,如果是本地管道则servername可以使用点“.”。
nTimeOut 等待命名管道的一个实例有效的超时时间,单位毫秒,也可以使用下面两个值中的一个:
NMPWAIT_USE_DEFAULT_WAIT 0x00000000,使用服务端CreateNamedPipe 创建管道时设置的超时时间。
NMPWAIT_WAIT_FOREVER 0xffffffff,一直等到一个命名管道的实例有效才返回。
返回值:
如果在超时时间前管道的一个实例有效,返回非0。
如果超时时间内没有一个有效的实例,返回0。
注意:
如果指定的命名管道没有实例存在,即没有服务端创建该命名管道(所以在确定服务器端创建了该命名管道时可以不调用此API),函数无视超时等待时间直接返回0。
如果函数执行成功返回TRUE,表示至少有一个命名管道的实例有效,接下来应该使用CreateFile函数打开命名管道的一个句柄,但是CreateFile可能会打开管道失败,因为该实例有可能被服务端关闭或被已经被其他客户端打开。
三、管道使用的伤痛所在
平时都使用得好好的管道突然出发问题了
static inline BOOL
CTPipe_WriteNBytes(HANDLE hPipe, BYTE *buf, DWORD size, DWORD timeout, HANDLE stopEvent)
{
BOOL ret = FALSE;
BOOL writeRet;
OVERLAPPED ol;
BYTE *pos;
DWORD cbBytesWrite;
HANDLE hWriteEvent;
memset( &ol, 0, sizeof(ol) );
ol.hEvent = hWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if ( ol.hEvent == NULL )
{
return FALSE;
}
pos = buf;
while ( size > 0 )
{
writeRet = WriteFile(hPipe,
pos,
size,
&cbBytesWrite,
&ol);
if ( writeRet )
{
pos += cbBytesWrite;
size -= cbBytesWrite;
ResetEvent(hWriteEvent);
}
else
{
if ( GetLastError() == ERROR_IO_PENDING )
{
DWORD waitRet;
HANDLE handles[2];
int numOfHandles = 1;
handles[0] = hWriteEvent;
if ( stopEvent )
{
handles[1] = stopEvent;
numOfHandles++;
}
waitRet = WaitForMultipleObjects(numOfHandles, handles, FALSE, timeout);
if ( waitRet == WAIT_OBJECT_0 )
{
writeRet = GetOverlappedResult(hPipe, &ol, &cbBytesWrite, TRUE );
if ( writeRet == FALSE )
{
goto EXIT;
}
pos += cbBytesWrite;
size -= cbBytesWrite;
ResetEvent(hWriteEvent);
}
else /* timeout, notify event or other */
{
/*
* Cancel request
*/
if ( CancelIo(hPipe) == FALSE )
{
L_ERROR(_T("IoCancel fail 0x%x, force close pipe\n"), GetLastError());
CloseHandle(hPipe);
}
else
{
/*
* Wait cannel finish
*/
GetOverlappedResult(hPipe, &ol, &cbBytesWrite, TRUE);
}
goto EXIT;
}
}
else
{
goto EXIT;
}
}
}
ret = TRUE;
EXIT:
if ( hWriteEvent )
{
CloseHandle(hWriteEvent);
}
return ret;
}
在
waitRet = WaitForMultipleObjects(numOfHandles, handles, FALSE, timeout);
发生了阻塞,此管理是采用异步的,这里写的管道是作为服务端,问题是服务端的代码根本就没有变,倒是管道客户端的代码变了。
在客户端是起一个线程不断地读管道的数据,但是读到数据后是有一个处理数据的过程。
而在以前的版本呻这个过程只是PostMessage一个消息,就继续去ReadFile了;而在新的版本中读到数据却是花了很多时间去处理数据,然后才去ReadFile。
导致服务端管道写阻塞的原因:
我们设置的模式是PIPE_WAIT,根据MSDN的要求是:当一端在写时,另一要发生读的动作,才能写成功;否则阻塞。
hPipe = CreateNamedPipe("Name",
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
0,
0,
0, /*XENHANCE_PIPE_TIMEOUT*/
&sa);
解决方法 :
法一、在服务端的管道写时设置超时时间,这虽然能使用服务端管道的线程不卡死,但也可能该写的数据没有写进去。
法二、根据上图所示,去掉“管道Client端“中的”等待RetEvent事件。
缺点:“管道Client端“不知道自己要求”管道Service端“执行的动作的结果。
1、
2、
3、
4、
5、
6、
7、