视频网站建设服务,网站如何关闭,潍坊市住房和城乡建设厅网站,电商管理系统一、管道实现进程间通讯 基本的理论知识 1#xff0e;什么是管道以及分类 管道是两个头的东西#xff0c;每个头各连接一个进程或者同一个进程的不同代码#xff0c;按照管道的类别分有两种管道#xff0c;匿名的和命名的#xff1b;按照管道的传输方向分也可以分成两种什么是管道以及分类 管道是两个头的东西每个头各连接一个进程或者同一个进程的不同代码按照管道的类别分有两种管道匿名的和命名的按照管道的传输方向分也可以分成两种单向的双向的。根据管道的特点命名管道通常用在网络环境下不同计算机上运行的进程之间的通信当然也可以用在同一台机的不同进程中它可以是单向或双向的而匿名管道只能用在同一台计算机中它只能是单向的。匿名管道其实是通过用给了一个指定名字的有名管道来实现的。 使用管道的好处在于读写它使用的是对文件操作的 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:DWORDlpInBuffer缓冲区中的字符数量 lpOutBufferLPVOID指定一个内存缓冲区用于装载从管道中读出的数据 nOutBufferSizeDWORD指定一个长整数变量用于装载来自管道的数据 lpBytesReadLPDWORD指定从管道中读出的字节数。会阅读单条消息。如lpOutBuffer的容量不够大不能容下整条消息则函数会返回FALSE而且GetLastError会设为ERROR_MORE_DATA消息中留下的任何字节都会丢失 nTimeOutDWORD下列常量之一 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 调用可能失败 二、 [cpp] view plain copy 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可能会打开管道失败因为该实例有可能被服务端关闭或被已经被其他客户端打开。 三、管道使用的伤痛所在
平时都使用得好好的管道突然出发问题了 [cpp] view plain copy 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; } 在 [cpp] view plain copy waitRet WaitForMultipleObjects(numOfHandles, handles, FALSE, timeout); 发生了阻塞此管理是采用异步的这里写的管道是作为服务端问题是服务端的代码根本就没有变倒是管道客户端的代码变了。 在客户端是起一个线程不断地读管道的数据但是读到数据后是有一个处理数据的过程。 而在以前的版本呻这个过程只是PostMessage一个消息就继续去ReadFile了而在新的版本中读到数据却是花了很多时间去处理数据然后才去ReadFile。 导致服务端管道写阻塞的原因 我们设置的模式是PIPE_WAIT根据MSDN的要求是当一端在写时另一要发生读的动作才能写成功否则阻塞。 [cpp] view plain copy 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端“执行的动作的结果。