进程间通信-windows命名管道详解以及完整代码实现

目录

1.简介

2.实现步骤

3.window的命名管道相关API详解

3.1.CreateNamedPipe

3.2.ConnectNamedPipe 

4.使用管道

4.1.多线程管道服务器

4.2.使用重叠 I/O 的命名管道服务器

4.3.使用完成例程的命名管道服务器

4.4.命名管道客户端        

4.5.命名管道上的事务

5.项目实践完整代码(保证可用)

5.1.基础类

5.2.核心类

6.使用场景

7.总结


1.简介

        命名管道(Named Pipes)是一种进程间通信(IPC)机制,它允许在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。命名管道在Windows、Linux、Unix等操作系统中都有支持,但具体的实现和使用方式可能有所不同。命名管道具有简单易用、支持双向通信、内置安全机制等特点,因此常用于客户端-服务器架构中的通信。

        命名管道是一种具有独特标识符(在Windows中通常是\\.\pipe\加上自定义名称,在Linux中则是文件系统中的路径名)的管道,它允许不同的进程通过该标识符进行通信。与匿名管道相比,命名管道具有更多的灵活性和功能,因为它可以跨进程、甚至跨计算机进行通信。

        在计算机编程里,命名管道是一种从一个进程到另一个进程用内核对象来进行信息传输。和一般的管道不同,命名管道可以被不同进程以不同的方式方法调用(可以跨权限、跨语言、跨平台)。只要程序知道命名管道的名字,发送到命名管道里的信息可以被一切拥有指定授权的程序读取,但对不具有制定授权的。命名管道是一种FIFO(先进先出,First-In First-Out)对象。

2.实现步骤

服务器端

  1. 创建命名管道:使用 CreateNamedPipe 函数。
  2. 等待客户端连接:使用 ConnectNamedPipe 函数(阻塞操作)或 WaitNamedPipe 函数(非阻塞操作)。
  3. 读写数据:使用标准的文件I/O函数,如 ReadFile 和 WriteFile
  4. 关闭管道:使用 CloseHandle 函数。

客户端

  1. 连接到命名管道:使用 CreateFile 函数。
  2. 读写数据:使用标准的文件I/O函数,如 ReadFile 和 WriteFile
  3. 关闭管道:使用 CloseHandle 函数。

3.window的命名管道相关API详解

3.1.CreateNamedPipe

        创建命名管道的实例,并返回后续管道操作的句柄。 命名管道服务器进程使用此函数创建特定命名管道的第一个实例并建立其基本属性,或创建现有命名管道的新实例。

        CreateNamedPipe()原型如下:

HANDLE CreateNamedPipeA(
  [in]           LPCSTR                lpName,
  [in]           DWORD                 dwOpenMode,
  [in]           DWORD                 dwPipeMode,
  [in]           DWORD                 nMaxInstances,
  [in]           DWORD                 nOutBufferSize,
  [in]           DWORD                 nInBufferSize,
  [in]           DWORD                 nDefaultTimeOut,
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

下面逐项做一下解释:

1)名称

        每个命名管道都有一个唯一的名称。管道服务端在调用CreateNamedPipe()时要指定管道的名称,函数为该名称创建管道实例;管道客户端调用CreateFile()或CallNamedPipe()时也要用该名称指定所要连接的管道。

        命名管道的名称采用以下格式:

\\<ServerName>\pipe\<PipeName>

        ServerName既可以是远程计算机的名称,也可以是一个点(.),表示本地计算机。PipeName是给管道起的名称,可以包含除反斜线(\)之外的任何字符,整个名称字符串的长度要限制在256字符以内。管道名称对字母大小写不敏感。

        由于服务端不能在远程主机创建管道,所以<ServerName>部分只能是一个点,调用CreateNamedPipe()时用\\.\pipe\<PipeName>。客户端在调用CreateFile()、WaitNamedPipe()以及CallNamedPipe()函数时根据希望连接的管道是在本地还是远程主机,可以将<ServerName>写成点或远程主机名。

        要使客户端知道服务端所创建的命名管道的名称,服务端可将名称存入某些持久性的存储位置,比如文件、注册表,或者让客户端直接写入源代码。

2)类型、读取及等待模式

        函数的 dwPipeMode 参数中指定管道类型模式、读取模式和等待模式。 管道客户端可以使用 CreateFile 函数为其管道句柄指定这些管道模式。

        类型模式

        管道的类型模式确定如何将数据写入命名管道。 可以通过命名管道以字节流或消息流的形式传输数据。 在调用 CreateNamedPipe 创建命名管道实例时,管道服务器指定管道类型。 对于管道的所有实例,类型模式必须相同。

        若要创建字节类型管道,请指定PIPE_TYPE_BYTE或使用默认值。 数据作为字节流写入管道,系统不会区分以不同写入操作写入的字节。

        若要创建消息类型管道,请指定PIPE_TYPE_MESSAGE。 系统会将每个写入操作中写入管道的字节视为消息单元。 系统始终对消息类型管道执行写入操作,就像启用了直通写模式一样。

        读取模式

        管道的读取模式决定了如何从命名管道读取数据。 管道服务器在调用 CreateNamedPipe 时指定管道句柄的初始读取模式。 可以在字节读取模式或消息读取模式下读取数据。 字节类型管道的句柄只能处于字节读取模式。 消息类型管道的句柄可以处于字节读取或消息读取模式。 对于消息类型管道,对于同一管道实例的服务器句柄和客户端句柄,读取模式可能不同。

        若要在字节读取模式下创建管道句柄,请指定PIPE_READMODE_BYTE或使用默认值。 数据作为字节流从管道中读取。 读取管道中的所有可用字节或读取指定数量的字节时,读取操作将成功完成。

        若要在消息读取模式下创建管道句柄,请指定PIPE_READMODE_MESSAGE。 数据作为消息流从管道中读取。 仅当读取整个消息时,读取操作才会成功完成。 如果要读取的指定字节数小于下一条消息的大小,则函数在返回零之前读取尽可能多的消息, (GetLastError 函数返回ERROR_MORE_DATA) 。 可以使用另一个读取操作读取消息的其余部分。

        对于管道客户端, CreateFile 返回的管道句柄最初始终处于字节读取模式。 管道客户端和管道服务器都可以使用 SetNamedPipeHandleState 函数更改管道句柄的读取模式。 管道句柄必须具有FILE_WRITE_ATTRIBUTES访问权限。

        等待模式

        管道句柄的等待模式确定 ReadFile、 WriteFile 和 ConnectNamedPipe 函数如何处理冗长操作。 在阻塞等待模式下,函数无限期地等待管道另一端的进程完成操作。 在非阻止等待模式下,函数在需要无限期等待的情况下立即返回。

        当管道为空时, ReadFile 操作受管道句柄的等待模式的影响。 使用阻塞等待句柄时,在线程写入管道另一端的数据可用之前,操作不会成功完成。 使用非阻止等待句柄,函数立即返回零, GetLastError 函数返回ERROR_NO_DATA。

        当管道的缓冲区中空间不足时, WriteFile 操作受管道句柄的等待模式的影响。 使用阻塞等待句柄时,在从管道另一端读取的线程在缓冲区中创建足够的空间之前,写入操作无法成功。 使用非阻止等待句柄时,写入操作将立即返回非零值,无需为消息类型管道) 写入任何字节 (,也不会写入缓冲区保留的字节数 (字节类型管道) 。

        当没有客户端连接或等待连接到管道实例时, ConnectNamedPipe 操作受管道句柄的等待模式的影响。 使用阻塞等待句柄时,在管道客户端通过调用 CreateFile 或 CallNamedPipe 函数连接到管道实例之前,连接操作不会成功。 使用非阻止等待句柄时,连接操作将立即返回零, GetLastError 函数返回ERROR_PIPE_LISTENING。

        默认情况下, CreateNamedPipe 或 CreateFile 函数返回的所有命名管道句柄都是在启用阻止等待模式的情况下创建的。 若要在非阻止等待模式下创建管道,管道服务器在调用 CreateNamedPipe 时指定PIPE_NOWAIT。

        管道客户端和管道服务器都可以通过在对 SetNamedPipeHandleState 函数的调用中指定PIPE_WAIT或PIPE_NOWAIT来更改管道句柄的等待模式。

3.2.ConnectNamedPipe 

        使命名管道服务器进程能够等待客户端进程连接到命名管道的实例。 客户端进程通过调用 CreateFile 或 CallNamedPipe 函数进行连接。

        函数原型如下:

BOOL ConnectNamedPipe(
  [in]                HANDLE       hNamedPipe,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

参数

[in] hNamedPipe

命名管道实例的服务器端的句柄。 此句柄由 CreateNamedPipe 函数返回。

[in, out, optional] lpOverlapped

指向 重叠 结构的指针。

如果使用 FILE_FLAG_OVERLAPPED 打开了 hNamedPipe,则 lpOverlapped 参数不得 NULL。 它必须指向有效的 重叠 结构。 如果使用 FILE_FLAG_OVERLAPPED 打开了 hNamedPipe,并且 lpOverlappedNULL,则该函数可能会错误地报告连接操作已完成。

如果使用 FILE_FLAG_OVERLAPPED 创建了 hNamedPipe,并且 lpOverlapped 未 NULL,则 OVERLAPPED 结构应包含手动重置事件对象的句柄(服务器可以使用 CreateEvent 函数创建)。

如果未使用 FILE_FLAG_OVERLAPPED 打开 hNamedPipe,则在客户端连接或发生错误之前,该函数不会返回。 如果客户端在调用函数后连接,则成功同步操作会导致函数返回非零值。

返回值

如果操作是同步的,ConnectNamedPipe 在操作完成之前不会返回。 如果函数成功,则返回值为非零。 如果函数失败,则返回值为零。 若要获取扩展的错误信息,请调用 GetLastError

如果操作是异步的,ConnectNamedPipe 会立即返回。 如果操作仍在挂起,则返回值为零,GetLastError 返回ERROR_IO_PENDING。 (可以使用 HasOverlappedIoCompleted 宏来确定操作完成的时间。如果函数失败,则返回值为零,GetLastError 返回除ERROR_IO_PENDING或ERROR_PIPE_CONNECTED以外的值。

如果在调用函数之前客户端连接,该函数将返回零,GetLastError 返回ERROR_PIPE_CONNECTED。 如果客户端在 CreateNamedPipe 的调用与 ConnectNamedPipe的调用之间进行连接,则可能会发生这种情况。 在这种情况下,客户端和服务器之间存在良好的连接,即使该函数返回零。

4.使用管道

4.1.多线程管道服务器

        它有一个main线程,其中包含一个循环,用于创建管道实例并等待管道客户端连接。 当管道客户端连接时,管道服务器会创建一个线程来为该客户端提供服务,然后继续在main线程中执行循环。 管道客户端可以在调用 CreateNamedPipe 和 ConnectNamedPipe 函数之间的间隔内成功连接到管道实例。 如果发生这种情况, ConnectNamedPipe 返回零, GetLastError 返回ERROR_PIPE_CONNECTED。

        为服务每个管道实例而创建的线程从管道读取请求,并将答复写入管道,直到管道客户端关闭其句柄。 发生这种情况时,线程将刷新管道、断开连接、关闭其管道句柄并终止。 main线程将运行,直到发生错误或进程结束。

        此管道服务器可与命名管道客户端中所述的 管道客户端一起使用。

#include <windows.h> 
#include <stdio.h> 
#include <tchar.h>
#include <strsafe.h>

#define BUFSIZE 512
 
DWORD WINAPI InstanceThread(LPVOID); 
VOID GetAnswerToRequest(LPTSTR, LPTSTR, LPDWORD); 
 
int _tmain(VOID) 
{ 
   BOOL   fConnected = FALSE; 
   DWORD  dwThreadId = 0; 
   HANDLE hPipe = INVALID_HANDLE_VALUE, hThread = NULL; 
   LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe"); 
 
// The main loop creates an instance of the named pipe and 
// then waits for a client to connect to it. When the client 
// connects, a thread is created to handle communications 
// with that client, and this loop is free to wait for the
// next client connect request. It is an infinite loop.
 
   for (;;) 
   { 
      _tprintf( TEXT("\nPipe Server: Main thread awaiting client connection on %s\n"), lpszPipename);
      hPipe = CreateNamedPipe( 
          lpszPipename,             // pipe name 
          PIPE_ACCESS_DUPLEX,       // read/write access 
          PIPE_TYPE_MESSAGE |       // message type pipe 
          PIPE_READMODE_MESSAGE |   // message-read mode 
          PIPE_WAIT,                // blocking mode 
          PIPE_UNLIMITED_INSTANCES, // max. instances  
          BUFSIZE,                  // output buffer size 
          BUFSIZE,                  // input buffer size 
          0,                        // client time-out 
          NULL);                    // default security attribute 

      if (hPipe == INVALID_HANDLE_VALUE) 
      {
          _tprintf(TEXT("CreateNamedPipe failed, GLE=%d.\n"), GetLastError()); 
          return -1;
      }
 
      // Wait for the client to connect; if it succeeds, 
      // the function returns a nonzero value. If the function
      // returns zero, GetLastError returns ERROR_PIPE_CONNECTED. 
 
      fConnected = ConnectNamedPipe(hPipe, NULL) ? 
         TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); 
 
      if (fConnected) 
      { 
         printf("Client connected, creating a processing thread.\n"); 
      
         // Create a thread for this client. 
         hThread = CreateThread( 
            NULL,              // no security attribute 
            0,                 // default stack size 
            InstanceThread,    // thread proc
            (LPVOID) hPipe,    // thread parameter 
            0,                 // not suspended 
            &dwThreadId);      // returns thread ID 

         if (hThread == NULL) 
         {
            _tprintf(TEXT("CreateThread failed, GLE=%d.\n"), GetLastError()); 
            return -1;
         }
         else CloseHandle(hThread); 
       } 
      else 
        // The client could not connect, so close the pipe. 
         CloseHandle(hPipe); 
   } 

   return 0; 
} 
 
DWORD WINAPI InstanceThread(LPVOID lpvParam)
// This routine is a thread processing function to read from and reply to a client
// via the open pipe connection passed from the main loop. Note this allows
// the main loop to continue executing, potentially creating more threads of
// of this procedure to run concurrently, depending on the number of incoming
// client connections.
{ 
   HANDLE hHeap      = GetProcessHeap();
   TCHAR* pchRequest = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE*sizeof(TCHAR));
   TCHAR* pchReply   = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE*sizeof(TCHAR));

   DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0; 
   BOOL fSuccess = FALSE;
   HANDLE hPipe  = NULL;

   // Do some extra error checking since the app will keep running even if this
   // thread fails.

   if (lpvParam == NULL)
   {
       printf( "\nERROR - Pipe Server Failure:\n");
       printf( "   InstanceThread got an unexpected NULL value in lpvParam.\n");
       printf( "   InstanceThread exitting.\n");
       if (pchReply != NULL) HeapFree(hHeap, 0, pchReply);
       if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest);
       return (DWORD)-1;
   }

   if (pchRequest == NULL)
   {
       printf( "\nERROR - Pipe Server Failure:\n");
       printf( "   InstanceThread got an unexpected NULL heap allocation.\n");
       printf( "   InstanceThread exitting.\n");
       if (pchReply != NULL) HeapFree(hHeap, 0, pchReply);
       return (DWORD)-1;
   }

   if (pchReply == NULL)
   {
       printf( "\nERROR - Pipe Server Failure:\n");
       printf( "   InstanceThread got an unexpected NULL heap allocation.\n");
       printf( "   InstanceThread exitting.\n");
       if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest);
       return (DWORD)-1;
   }

   // Print verbose messages. In production code, this should be for debugging only.
   printf("InstanceThread created, receiving and processing messages.\n");

// The thread's parameter is a handle to a pipe object instance. 
 
   hPipe = (HANDLE) lpvParam; 

// Loop until done reading
   while (1) 
   { 
   // Read client requests from the pipe. This simplistic code only allows messages
   // up to BUFSIZE characters in length.
      fSuccess = ReadFile( 
         hPipe,        // handle to pipe 
         pchRequest,    // buffer to receive data 
         BUFSIZE*sizeof(TCHAR), // size of buffer 
         &cbBytesRead, // number of bytes read 
         NULL);        // not overlapped I/O 

      if (!fSuccess || cbBytesRead == 0)
      {   
          if (GetLastError() == ERROR_BROKEN_PIPE)
          {
              _tprintf(TEXT("InstanceThread: client disconnected.\n")); 
          }
          else
          {
              _tprintf(TEXT("InstanceThread ReadFile failed, GLE=%d.\n"), GetLastError()); 
          }
          break;
      }

   // Process the incoming message.
      GetAnswerToRequest(pchRequest, pchReply, &cbReplyBytes); 
 
   // Write the reply to the pipe. 
      fSuccess = WriteFile( 
         hPipe,        // handle to pipe 
         pchReply,     // buffer to write from 
         cbReplyBytes, // number of bytes to write 
         &cbWritten,   // number of bytes written 
         NULL);        // not overlapped I/O 

      if (!fSuccess || cbReplyBytes != cbWritten)
      {   
          _tprintf(TEXT("InstanceThread WriteFile failed, GLE=%d.\n"), GetLastError()); 
          break;
      }
  }

// Flush the pipe to allow the client to read the pipe's contents 
// before disconnecting. Then disconnect the pipe, and close the 
// handle to this pipe instance. 
 
   FlushFileBuffers(hPipe); 
   DisconnectNamedPipe(hPipe); 
   CloseHandle(hPipe); 

   HeapFree(hHeap, 0, pchRequest);
   HeapFree(hHeap, 0, pchReply);

   printf("InstanceThread exiting.\n");
   return 1;
}

VOID GetAnswerToRequest( LPTSTR pchRequest, 
                         LPTSTR pchReply, 
                         LPDWORD pchBytes )
// This routine is a simple function to print the client request to the console
// and populate the reply buffer with a default data string. This is where you
// would put the actual client request processing code that runs in the context
// of an instance thread. Keep in mind the main thread will continue to wait for
// and receive other client connections while the instance thread is working.
{
    _tprintf( TEXT("Client Request String:\"%s\"\n"), pchRequest );

    // Check the outgoing message to make sure it's not too long for the buffer.
    if (FAILED(StringCchCopy( pchReply, BUFSIZE, TEXT("default answer from server") )))
    {
        *pchBytes = 0;
        pchReply[0] = 0;
        printf("StringCchCopy failed, no outgoing message.\n");
        return;
    }
    *pchBytes = (lstrlen(pchReply)+1)*sizeof(TCHAR);
}

4.2.使用重叠 I/O 的命名管道服务器

        服务器使用重叠操作为多个管道客户端的同时连接提供服务。 管道服务器创建固定数量的管道实例。 每个管道实例都可以连接到单独的管道客户端。 当管道客户端使用完其管道实例后,服务器会断开与客户端的连接,并重复使用该管道实例连接到新客户端。 此管道服务器可与命名管道客户端中所述的 管道客户端一起使用。

        示例代码如下:

#include <windows.h> 
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>
 
#define CONNECTING_STATE 0 
#define READING_STATE 1 
#define WRITING_STATE 2 
#define INSTANCES 4 
#define PIPE_TIMEOUT 5000
#define BUFSIZE 4096
 
typedef struct 
{ 
   OVERLAPPED oOverlap; 
   HANDLE hPipeInst; 
   TCHAR chRequest[BUFSIZE]; 
   DWORD cbRead;
   TCHAR chReply[BUFSIZE];
   DWORD cbToWrite; 
   DWORD dwState; 
   BOOL fPendingIO; 
} PIPEINST, *LPPIPEINST; 
 
 
VOID DisconnectAndReconnect(DWORD); 
BOOL ConnectToNewClient(HANDLE, LPOVERLAPPED); 
VOID GetAnswerToRequest(LPPIPEINST); 
 
PIPEINST Pipe[INSTANCES]; 
HANDLE hEvents[INSTANCES]; 
 
int _tmain(VOID) 
{ 
   DWORD i, dwWait, cbRet, dwErr; 
   BOOL fSuccess; 
   LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe"); 
 
// The initial loop creates several instances of a named pipe 
// along with an event object for each instance.  An 
// overlapped ConnectNamedPipe operation is started for 
// each instance. 
 
   for (i = 0; i < INSTANCES; i++) 
   { 
 
   // Create an event object for this instance. 
 
      hEvents[i] = CreateEvent( 
         NULL,    // default security attribute 
         TRUE,    // manual-reset event 
         TRUE,    // initial state = signaled 
         NULL);   // unnamed event object 

      if (hEvents[i] == NULL) 
      {
         printf("CreateEvent failed with %d.\n", GetLastError()); 
         return 0;
      }
 
      Pipe[i].oOverlap.hEvent = hEvents[i]; 
      Pipe[i].oOverlap.Offset = 0;
      Pipe[i].oOverlap.OffsetHigh = 0;
 
      Pipe[i].hPipeInst = CreateNamedPipe( 
         lpszPipename,            // pipe name 
         PIPE_ACCESS_DUPLEX |     // read/write access 
         FILE_FLAG_OVERLAPPED,    // overlapped mode 
         PIPE_TYPE_MESSAGE |      // message-type pipe 
         PIPE_READMODE_MESSAGE |  // message-read mode 
         PIPE_WAIT,               // blocking mode 
         INSTANCES,               // number of instances 
         BUFSIZE*sizeof(TCHAR),   // output buffer size 
         BUFSIZE*sizeof(TCHAR),   // input buffer size 
         PIPE_TIMEOUT,            // client time-out 
         NULL);                   // default security attributes 

      if (Pipe[i].hPipeInst == INVALID_HANDLE_VALUE) 
      {
         printf("CreateNamedPipe failed with %d.\n", GetLastError());
         return 0;
      }
 
   // Call the subroutine to connect to the new client
 
      Pipe[i].fPendingIO = ConnectToNewClient( 
         Pipe[i].hPipeInst, 
         &Pipe[i].oOverlap); 
 
      Pipe[i].dwState = Pipe[i].fPendingIO ? 
         CONNECTING_STATE : // still connecting 
         READING_STATE;     // ready to read 
   } 
 
   while (1) 
   { 
   // Wait for the event object to be signaled, indicating 
   // completion of an overlapped read, write, or 
   // connect operation. 
 
      dwWait = WaitForMultipleObjects( 
         INSTANCES,    // number of event objects 
         hEvents,      // array of event objects 
         FALSE,        // does not wait for all 
         INFINITE);    // waits indefinitely 
 
   // dwWait shows which pipe completed the operation. 
 
      i = dwWait - WAIT_OBJECT_0;  // determines which pipe 
      if (i < 0 || i > (INSTANCES - 1)) 
      {
         printf("Index out of range.\n"); 
         return 0;
      }
 
   // Get the result if the operation was pending. 
 
      if (Pipe[i].fPendingIO) 
      { 
         fSuccess = GetOverlappedResult( 
            Pipe[i].hPipeInst, // handle to pipe 
            &Pipe[i].oOverlap, // OVERLAPPED structure 
            &cbRet,            // bytes transferred 
            FALSE);            // do not wait 
 
         switch (Pipe[i].dwState) 
         { 
         // Pending connect operation 
            case CONNECTING_STATE: 
               if (! fSuccess) 
               {
                   printf("Error %d.\n", GetLastError()); 
                   return 0;
               }
               Pipe[i].dwState = READING_STATE; 
               break; 
 
         // Pending read operation 
            case READING_STATE: 
               if (! fSuccess || cbRet == 0) 
               { 
                  DisconnectAndReconnect(i); 
                  continue; 
               }
               Pipe[i].cbRead = cbRet;
               Pipe[i].dwState = WRITING_STATE; 
               break; 
 
         // Pending write operation 
            case WRITING_STATE: 
               if (! fSuccess || cbRet != Pipe[i].cbToWrite) 
               { 
                  DisconnectAndReconnect(i); 
                  continue; 
               } 
               Pipe[i].dwState = READING_STATE; 
               break; 
 
            default: 
            {
               printf("Invalid pipe state.\n"); 
               return 0;
            }
         }  
      } 
 
   // The pipe state determines which operation to do next. 
 
      switch (Pipe[i].dwState) 
      { 
      // READING_STATE: 
      // The pipe instance is connected to the client 
      // and is ready to read a request from the client. 
 
         case READING_STATE: 
            fSuccess = ReadFile( 
               Pipe[i].hPipeInst, 
               Pipe[i].chRequest, 
               BUFSIZE*sizeof(TCHAR), 
               &Pipe[i].cbRead, 
               &Pipe[i].oOverlap); 
 
         // The read operation completed successfully. 
 
            if (fSuccess && Pipe[i].cbRead != 0) 
            { 
               Pipe[i].fPendingIO = FALSE; 
               Pipe[i].dwState = WRITING_STATE; 
               continue; 
            } 
 
         // The read operation is still pending. 
 
            dwErr = GetLastError(); 
            if (! fSuccess && (dwErr == ERROR_IO_PENDING)) 
            { 
               Pipe[i].fPendingIO = TRUE; 
               continue; 
            } 
 
         // An error occurred; disconnect from the client. 
 
            DisconnectAndReconnect(i); 
            break; 
 
      // WRITING_STATE: 
      // The request was successfully read from the client. 
      // Get the reply data and write it to the client. 
 
         case WRITING_STATE: 
            GetAnswerToRequest(&Pipe[i]); 
 
            fSuccess = WriteFile( 
               Pipe[i].hPipeInst, 
               Pipe[i].chReply, 
               Pipe[i].cbToWrite, 
               &cbRet, 
               &Pipe[i].oOverlap); 
 
         // The write operation completed successfully. 
 
            if (fSuccess && cbRet == Pipe[i].cbToWrite) 
            { 
               Pipe[i].fPendingIO = FALSE; 
               Pipe[i].dwState = READING_STATE; 
               continue; 
            } 
 
         // The write operation is still pending. 
 
            dwErr = GetLastError(); 
            if (! fSuccess && (dwErr == ERROR_IO_PENDING)) 
            { 
               Pipe[i].fPendingIO = TRUE; 
               continue; 
            } 
 
         // An error occurred; disconnect from the client. 
 
            DisconnectAndReconnect(i); 
            break; 
 
         default: 
         {
            printf("Invalid pipe state.\n"); 
            return 0;
         }
      } 
  } 
 
  return 0; 
} 
 
 
// DisconnectAndReconnect(DWORD) 
// This function is called when an error occurs or when the client 
// closes its handle to the pipe. Disconnect from this client, then 
// call ConnectNamedPipe to wait for another client to connect. 
 
VOID DisconnectAndReconnect(DWORD i) 
{ 
// Disconnect the pipe instance. 
 
   if (! DisconnectNamedPipe(Pipe[i].hPipeInst) ) 
   {
      printf("DisconnectNamedPipe failed with %d.\n", GetLastError());
   }
 
// Call a subroutine to connect to the new client. 
 
   Pipe[i].fPendingIO = ConnectToNewClient( 
      Pipe[i].hPipeInst, 
      &Pipe[i].oOverlap); 
 
   Pipe[i].dwState = Pipe[i].fPendingIO ? 
      CONNECTING_STATE : // still connecting 
      READING_STATE;     // ready to read 
} 
 
// ConnectToNewClient(HANDLE, LPOVERLAPPED) 
// This function is called to start an overlapped connect operation. 
// It returns TRUE if an operation is pending or FALSE if the 
// connection has been completed. 
 
BOOL ConnectToNewClient(HANDLE hPipe, LPOVERLAPPED lpo) 
{ 
   BOOL fConnected, fPendingIO = FALSE; 
 
// Start an overlapped connection for this pipe instance. 
   fConnected = ConnectNamedPipe(hPipe, lpo); 
 
// Overlapped ConnectNamedPipe should return zero. 
   if (fConnected) 
   {
      printf("ConnectNamedPipe failed with %d.\n", GetLastError()); 
      return 0;
   }
 
   switch (GetLastError()) 
   { 
   // The overlapped connection in progress. 
      case ERROR_IO_PENDING: 
         fPendingIO = TRUE; 
         break; 
 
   // Client is already connected, so signal an event. 
 
      case ERROR_PIPE_CONNECTED: 
         if (SetEvent(lpo->hEvent)) 
            break; 
 
   // If an error occurs during the connect operation... 
      default: 
      {
         printf("ConnectNamedPipe failed with %d.\n", GetLastError());
         return 0;
      }
   } 
 
   return fPendingIO; 
}

VOID GetAnswerToRequest(LPPIPEINST pipe)
{
   _tprintf( TEXT("[%d] %s\n"), pipe->hPipeInst, pipe->chRequest);
   StringCchCopy( pipe->chReply, BUFSIZE, TEXT("Default answer from server") );
   pipe->cbToWrite = (lstrlen(pipe->chReply)+1)*sizeof(TCHAR);
}

4.3.使用完成例程的命名管道服务器

        使用扩展函数 ReadFileEx 和 WriteFileEx 通过完成例程执行重叠 I/O,该例程在操作完成后排队等待执行。 管道服务器使用 WaitForSingleObjectEx 函数,该函数执行可警报的等待操作,该操作在完成例程准备好执行时返回。 当向事件对象发出信号时,wait 函数也会返回 ,在此示例中指示重叠的 ConnectNamedPipe 操作已完成 (新客户端已连接) 。 此管道服务器可与命名管道客户端中所述的 管道客户端一起使用。        

        示例代码如下:

#include <windows.h> 
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>

#define PIPE_TIMEOUT 5000
#define BUFSIZE 4096
 
typedef struct 
{ 
   OVERLAPPED oOverlap; 
   HANDLE hPipeInst; 
   TCHAR chRequest[BUFSIZE]; 
   DWORD cbRead;
   TCHAR chReply[BUFSIZE]; 
   DWORD cbToWrite; 
} PIPEINST, *LPPIPEINST; 
 
VOID DisconnectAndClose(LPPIPEINST); 
BOOL CreateAndConnectInstance(LPOVERLAPPED); 
BOOL ConnectToNewClient(HANDLE, LPOVERLAPPED); 
VOID GetAnswerToRequest(LPPIPEINST); 

VOID WINAPI CompletedWriteRoutine(DWORD, DWORD, LPOVERLAPPED); 
VOID WINAPI CompletedReadRoutine(DWORD, DWORD, LPOVERLAPPED); 
 
HANDLE hPipe; 
 
int _tmain(VOID) 
{ 
   HANDLE hConnectEvent; 
   OVERLAPPED oConnect; 
   LPPIPEINST lpPipeInst; 
   DWORD dwWait, cbRet; 
   BOOL fSuccess, fPendingIO; 
 
// Create one event object for the connect operation. 
 
   hConnectEvent = CreateEvent( 
      NULL,    // default security attribute
      TRUE,    // manual reset event 
      TRUE,    // initial state = signaled 
      NULL);   // unnamed event object 

   if (hConnectEvent == NULL) 
   {
      printf("CreateEvent failed with %d.\n", GetLastError()); 
      return 0;
   }
 
   oConnect.hEvent = hConnectEvent; 
 
// Call a subroutine to create one instance, and wait for 
// the client to connect. 
 
   fPendingIO = CreateAndConnectInstance(&oConnect); 
 
   while (1) 
   { 
   // Wait for a client to connect, or for a read or write 
   // operation to be completed, which causes a completion 
   // routine to be queued for execution. 
 
      dwWait = WaitForSingleObjectEx( 
         hConnectEvent,  // event object to wait for 
         INFINITE,       // waits indefinitely 
         TRUE);          // alertable wait enabled 
 
      switch (dwWait) 
      { 
      // The wait conditions are satisfied by a completed connect 
      // operation. 
         case 0: 
         // If an operation is pending, get the result of the 
         // connect operation. 
 
         if (fPendingIO) 
         { 
            fSuccess = GetOverlappedResult( 
               hPipe,     // pipe handle 
               &oConnect, // OVERLAPPED structure 
               &cbRet,    // bytes transferred 
               FALSE);    // does not wait 
            if (!fSuccess) 
            {
               printf("ConnectNamedPipe (%d)\n", GetLastError()); 
               return 0;
            }
         } 
 
         // Allocate storage for this instance. 
 
            lpPipeInst = (LPPIPEINST) GlobalAlloc( 
               GPTR, sizeof(PIPEINST)); 
            if (lpPipeInst == NULL) 
            {
               printf("GlobalAlloc failed (%d)\n", GetLastError()); 
               return 0;
            }
 
            lpPipeInst->hPipeInst = hPipe; 
 
         // Start the read operation for this client. 
         // Note that this same routine is later used as a 
         // completion routine after a write operation. 
 
            lpPipeInst->cbToWrite = 0; 
            CompletedWriteRoutine(0, 0, (LPOVERLAPPED) lpPipeInst); 
 
         // Create new pipe instance for the next client. 
 
            fPendingIO = CreateAndConnectInstance( 
               &oConnect); 
            break; 
 
      // The wait is satisfied by a completed read or write 
      // operation. This allows the system to execute the 
      // completion routine. 
 
         case WAIT_IO_COMPLETION: 
            break; 
 
      // An error occurred in the wait function. 
 
         default: 
         {
            printf("WaitForSingleObjectEx (%d)\n", GetLastError()); 
            return 0;
         }
      } 
   } 
   return 0; 
} 
 
// CompletedWriteRoutine(DWORD, DWORD, LPOVERLAPPED) 
// This routine is called as a completion routine after writing to 
// the pipe, or when a new client has connected to a pipe instance.
// It starts another read operation. 
 
VOID WINAPI CompletedWriteRoutine(DWORD dwErr, DWORD cbWritten, 
   LPOVERLAPPED lpOverLap) 
{ 
   LPPIPEINST lpPipeInst; 
   BOOL fRead = FALSE; 
 
// lpOverlap points to storage for this instance. 
 
   lpPipeInst = (LPPIPEINST) lpOverLap; 
 
// The write operation has finished, so read the next request (if 
// there is no error). 
 
   if ((dwErr == 0) && (cbWritten == lpPipeInst->cbToWrite)) 
      fRead = ReadFileEx( 
         lpPipeInst->hPipeInst, 
         lpPipeInst->chRequest, 
         BUFSIZE*sizeof(TCHAR), 
         (LPOVERLAPPED) lpPipeInst, 
         (LPOVERLAPPED_COMPLETION_ROUTINE) CompletedReadRoutine); 
 
// Disconnect if an error occurred. 
 
   if (! fRead) 
      DisconnectAndClose(lpPipeInst); 
} 
 
// CompletedReadRoutine(DWORD, DWORD, LPOVERLAPPED) 
// This routine is called as an I/O completion routine after reading 
// a request from the client. It gets data and writes it to the pipe. 
 
VOID WINAPI CompletedReadRoutine(DWORD dwErr, DWORD cbBytesRead, 
    LPOVERLAPPED lpOverLap) 
{ 
   LPPIPEINST lpPipeInst; 
   BOOL fWrite = FALSE; 
 
// lpOverlap points to storage for this instance. 
 
   lpPipeInst = (LPPIPEINST) lpOverLap; 
 
// The read operation has finished, so write a response (if no 
// error occurred). 
 
   if ((dwErr == 0) && (cbBytesRead != 0)) 
   { 
      GetAnswerToRequest(lpPipeInst); 
 
      fWrite = WriteFileEx( 
         lpPipeInst->hPipeInst, 
         lpPipeInst->chReply, 
         lpPipeInst->cbToWrite, 
         (LPOVERLAPPED) lpPipeInst, 
         (LPOVERLAPPED_COMPLETION_ROUTINE) CompletedWriteRoutine); 
   } 
 
// Disconnect if an error occurred. 
 
   if (! fWrite) 
      DisconnectAndClose(lpPipeInst); 
} 
 
// DisconnectAndClose(LPPIPEINST) 
// This routine is called when an error occurs or the client closes 
// its handle to the pipe. 
 
VOID DisconnectAndClose(LPPIPEINST lpPipeInst) 
{ 
// Disconnect the pipe instance. 
 
   if (! DisconnectNamedPipe(lpPipeInst->hPipeInst) ) 
   {
      printf("DisconnectNamedPipe failed with %d.\n", GetLastError());
   }
 
// Close the handle to the pipe instance. 
 
   CloseHandle(lpPipeInst->hPipeInst); 
 
// Release the storage for the pipe instance. 
 
   if (lpPipeInst != NULL) 
      GlobalFree(lpPipeInst); 
} 
 
// CreateAndConnectInstance(LPOVERLAPPED) 
// This function creates a pipe instance and connects to the client. 
// It returns TRUE if the connect operation is pending, and FALSE if 
// the connection has been completed. 
 
BOOL CreateAndConnectInstance(LPOVERLAPPED lpoOverlap) 
{ 
   LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe"); 
 
   hPipe = CreateNamedPipe( 
      lpszPipename,             // pipe name 
      PIPE_ACCESS_DUPLEX |      // read/write access 
      FILE_FLAG_OVERLAPPED,     // overlapped mode 
      PIPE_TYPE_MESSAGE |       // message-type pipe 
      PIPE_READMODE_MESSAGE |   // message read mode 
      PIPE_WAIT,                // blocking mode 
      PIPE_UNLIMITED_INSTANCES, // unlimited instances 
      BUFSIZE*sizeof(TCHAR),    // output buffer size 
      BUFSIZE*sizeof(TCHAR),    // input buffer size 
      PIPE_TIMEOUT,             // client time-out 
      NULL);                    // default security attributes
   if (hPipe == INVALID_HANDLE_VALUE) 
   {
      printf("CreateNamedPipe failed with %d.\n", GetLastError()); 
      return 0;
   }
 
// Call a subroutine to connect to the new client. 
 
   return ConnectToNewClient(hPipe, lpoOverlap); 
}

BOOL ConnectToNewClient(HANDLE hPipe, LPOVERLAPPED lpo) 
{ 
   BOOL fConnected, fPendingIO = FALSE; 
 
// Start an overlapped connection for this pipe instance. 
   fConnected = ConnectNamedPipe(hPipe, lpo); 
 
// Overlapped ConnectNamedPipe should return zero. 
   if (fConnected) 
   {
      printf("ConnectNamedPipe failed with %d.\n", GetLastError()); 
      return 0;
   }
 
   switch (GetLastError()) 
   { 
   // The overlapped connection in progress. 
      case ERROR_IO_PENDING: 
         fPendingIO = TRUE; 
         break; 
 
   // Client is already connected, so signal an event. 
 
      case ERROR_PIPE_CONNECTED: 
         if (SetEvent(lpo->hEvent)) 
            break; 
 
   // If an error occurs during the connect operation... 
      default: 
      {
         printf("ConnectNamedPipe failed with %d.\n", GetLastError());
         return 0;
      }
   } 
   return fPendingIO; 
}

VOID GetAnswerToRequest(LPPIPEINST pipe)
{
   _tprintf( TEXT("[%d] %s\n"), pipe->hPipeInst, pipe->chRequest);
   StringCchCopy( pipe->chReply, BUFSIZE, TEXT("Default answer from server") );
   pipe->cbToWrite = (lstrlen(pipe->chReply)+1)*sizeof(TCHAR);
}

4.4.命名管道客户端        

        命名管道客户端使用 CreateFile 函数打开命名管道的句柄。 如果管道存在,但其所有实例都忙, 则 CreateFile 返回 INVALID_HANDLE_VALUE , GetLastError 函数返回ERROR_PIPE_BUSY。 发生这种情况时,命名管道客户端使用 WaitNamedPipe 函数等待命名管道的实例变为可用。

        如果指定的访问与服务器创建管道时指定的 (双工、出站或入站) 的访问不兼容, 则 CreateFile 函数将失败。 对于双工管道,客户端可以指定读取、写入或读/写访问权限;对于出站管道 (只写服务器) ,客户端必须指定只读访问权限;对于入站管道 (只读服务器) ,客户端必须指定只写访问。

       CreateFile 返回的句柄默认为字节读取模式、阻止等待模式、禁用重叠模式和禁用写通模式。 管道客户端可以使用 CreateFile 通过指定FILE_FLAG_OVERLAPPED来启用重叠模式,或者通过指定FILE_FLAG_WRITE_THROUGH来启用写通模式。 客户端可以使用 SetNamedPipeHandleState 函数通过指定PIPE_NOWAIT来启用非阻止模式,或者通过指定PIPE_READMODE_MESSAGE来启用消息读取模式。

        以下示例演示了一个管道客户端,该客户端打开命名管道,将管道句柄设置为消息读取模式,使用 WriteFile 函数向服务器发送请求,并使用 ReadFile 函数读取服务器的回复。 此管道客户端可以与本主题底部列出的任何消息类型服务器一起使用。 但是,对于字节类型服务器,此管道客户端在调用 SetNamedPipeHandleState 以更改为消息读取模式时失败。 由于客户端在消息读取模式下从管道读取,因此 ReadFile 操作可以在读取部分消息后返回零。 当消息大于读取缓冲区时,就会发生这种情况。 在这种情况下, GetLastError 返回ERROR_MORE_DATA,客户端可以使用对 ReadFile 的其他调用来读取消息的其余部分。

        此管道客户端可以与“另请参阅”下列出的任何管道服务器一起使用。

#include <windows.h> 
#include <stdio.h>
#include <conio.h>
#include <tchar.h>

#define BUFSIZE 512
 
int _tmain(int argc, TCHAR *argv[]) 
{ 
   HANDLE hPipe; 
   LPCTSTR lpvMessage=TEXT("Default message from client."); 
   TCHAR  chBuf[BUFSIZE]; 
   BOOL   fSuccess = FALSE; 
   DWORD  cbRead, cbToWrite, cbWritten, dwMode; 
   LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe"); 

   if( argc > 1 )
      lpvMessage = argv[1];
 
// Try to open a named pipe; wait for it, if necessary. 
 
   while (1) 
   { 
      hPipe = CreateFile( 
         lpszPipename,   // pipe name 
         GENERIC_READ |  // read and write access 
         GENERIC_WRITE, 
         0,              // no sharing 
         NULL,           // default security attributes
         OPEN_EXISTING,  // opens existing pipe 
         0,              // default attributes 
         NULL);          // no template file 
 
   // Break if the pipe handle is valid. 
 
      if (hPipe != INVALID_HANDLE_VALUE) 
         break; 
 
      // Exit if an error other than ERROR_PIPE_BUSY occurs. 
 
      if (GetLastError() != ERROR_PIPE_BUSY) 
      {
         _tprintf( TEXT("Could not open pipe. GLE=%d\n"), GetLastError() ); 
         return -1;
      }
 
      // All pipe instances are busy, so wait for 20 seconds. 
 
      if ( ! WaitNamedPipe(lpszPipename, 20000)) 
      { 
         printf("Could not open pipe: 20 second wait timed out."); 
         return -1;
      } 
   } 
 
// The pipe connected; change to message-read mode. 
 
   dwMode = PIPE_READMODE_MESSAGE; 
   fSuccess = SetNamedPipeHandleState( 
      hPipe,    // pipe handle 
      &dwMode,  // new pipe mode 
      NULL,     // don't set maximum bytes 
      NULL);    // don't set maximum time 
   if ( ! fSuccess) 
   {
      _tprintf( TEXT("SetNamedPipeHandleState failed. GLE=%d\n"), GetLastError() ); 
      return -1;
   }
 
// Send a message to the pipe server. 
 
   cbToWrite = (lstrlen(lpvMessage)+1)*sizeof(TCHAR);
   _tprintf( TEXT("Sending %d byte message: \"%s\"\n"), cbToWrite, lpvMessage); 

   fSuccess = WriteFile( 
      hPipe,                  // pipe handle 
      lpvMessage,             // message 
      cbToWrite,              // message length 
      &cbWritten,             // bytes written 
      NULL);                  // not overlapped 

   if ( ! fSuccess) 
   {
      _tprintf( TEXT("WriteFile to pipe failed. GLE=%d\n"), GetLastError() ); 
      return -1;
   }

   printf("\nMessage sent to server, receiving reply as follows:\n");
 
   do 
   { 
   // Read from the pipe. 
 
      fSuccess = ReadFile( 
         hPipe,    // pipe handle 
         chBuf,    // buffer to receive reply 
         BUFSIZE*sizeof(TCHAR),  // size of buffer 
         &cbRead,  // number of bytes read 
         NULL);    // not overlapped 
 
      if ( ! fSuccess && GetLastError() != ERROR_MORE_DATA )
         break; 
 
      _tprintf( TEXT("\"%s\"\n"), chBuf ); 
   } while ( ! fSuccess);  // repeat loop if ERROR_MORE_DATA 

   if ( ! fSuccess)
   {
      _tprintf( TEXT("ReadFile from pipe failed. GLE=%d\n"), GetLastError() );
      return -1;
   }

   printf("\n<End of message, press ENTER to terminate connection and exit>");
   _getch();
 
   CloseHandle(hPipe); 
 
   return 0; 
}

4.5.命名管道上的事务

        命名管道事务是客户端/服务器通信,它将写入操作和读取操作合并到单个网络操作中。 事务只能在双工消息类型管道上使用。 事务可提高客户端与远程服务器之间的网络通信性能。 进程可以使用 TransactNamedPipe 和 CallNamedPipe 函数来执行命名管道事务。

        管道客户端最常使用 TransactNamedPipe 函数将请求消息写入命名管道服务器并读取服务器的响应消息。 管道客户端必须指定GENERIC_READ |GENERIC_WRITE通过调用 CreateFile 函数打开其管道句柄时进行访问。 然后,管道客户端通过调用 SetNamedPipeHandleState 函数将管道句柄设置为消息读取模式。 如果在对 TransactNamedPipe 的调用中指定的读取缓冲区不够大,无法容纳服务器写入的整个消息,则函数返回零, GetLastError 返回ERROR_MORE_DATA。 客户端可以通过调用 ReadFile、ReadFileEx 或 PeekNamedPipe 函数来读取消息的其余部分。

       TransactNamedPipe 通常由管道客户端调用,但也可以由管道服务器使用。

        以下示例演示使用 TransactNamedPipe 的管道客户端。 此管道客户端可以与“另请参阅”下列出的任何管道服务器一起使用。

#include <windows.h> 
#include <stdio.h>
#include <conio.h>
#include <tchar.h>

#define BUFSIZE 512
 
int _tmain(int argc, TCHAR *argv[]) 
{ 
   HANDLE hPipe; 
   LPTSTR lpszWrite = TEXT("Default message from client"); 
   TCHAR chReadBuf[BUFSIZE]; 
   BOOL fSuccess; 
   DWORD cbRead, dwMode; 
   LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe"); 

   if( argc > 1)
   {
      lpszWrite = argv[1]; 
   }
 
   // Try to open a named pipe; wait for it, if necessary. 
    while (1) 
   { 
      hPipe = CreateFile( 
         lpszPipename,   // pipe name 
         GENERIC_READ |  // read and write access 
         GENERIC_WRITE, 
         0,              // no sharing 
         NULL,           // default security attributes
         OPEN_EXISTING,  // opens existing pipe 
         0,              // default attributes 
         NULL);          // no template file 
 
      // Break if the pipe handle is valid. 
      if (hPipe != INVALID_HANDLE_VALUE) 
         break; 
 
      // Exit if an error other than ERROR_PIPE_BUSY occurs. 
      if (GetLastError() != ERROR_PIPE_BUSY) 
      {
         printf("Could not open pipe\n"); 
         return 0;
      }
 
      // All pipe instances are busy, so wait for 20 seconds. 
      if (! WaitNamedPipe(lpszPipename, 20000) ) 
      {
         printf("Could not open pipe\n"); 
         return 0;
      }
  } 
 
   // The pipe connected; change to message-read mode. 
   dwMode = PIPE_READMODE_MESSAGE; 
   fSuccess = SetNamedPipeHandleState( 
      hPipe,    // pipe handle 
      &dwMode,  // new pipe mode 
      NULL,     // don't set maximum bytes 
      NULL);    // don't set maximum time 
   if (!fSuccess) 
   {
      printf("SetNamedPipeHandleState failed.\n"); 
      return 0;
   }
 
   // Send a message to the pipe server and read the response. 
   fSuccess = TransactNamedPipe( 
      hPipe,                  // pipe handle 
      lpszWrite,              // message to server
      (lstrlen(lpszWrite)+1)*sizeof(TCHAR), // message length 
      chReadBuf,              // buffer to receive reply
      BUFSIZE*sizeof(TCHAR),  // size of read buffer
      &cbRead,                // bytes read
      NULL);                  // not overlapped 

   if (!fSuccess && (GetLastError() != ERROR_MORE_DATA)) 
   {
      printf("TransactNamedPipe failed.\n"); 
      return 0;
   }
 
   while(1)
   { 
      _tprintf(TEXT("%s\n"), chReadBuf);

      // Break if TransactNamedPipe or ReadFile is successful
      if(fSuccess)
         break;

      // Read from the pipe if there is more data in the message.
      fSuccess = ReadFile( 
         hPipe,      // pipe handle 
         chReadBuf,  // buffer to receive reply 
         BUFSIZE*sizeof(TCHAR),  // size of buffer 
         &cbRead,  // number of bytes read 
         NULL);    // not overlapped 

      // Exit if an error other than ERROR_MORE_DATA occurs.
      if( !fSuccess && (GetLastError() != ERROR_MORE_DATA)) 
         break;
      else _tprintf( TEXT("%s\n"), chReadBuf); 
   }

   _getch(); 

   CloseHandle(hPipe); 
 
   return 0; 
}

5.项目实践完整代码(保证可用)

实现命名管道的服务端和客户端,并在项目上实际用。

5.1.基础类

CriticalSectionWrapper.h

#pragma once

/******************************************************/
#define  FASTLOCK					CRITICAL_SECTION
#define  FastLockInit(x)			InitializeCriticalSection(x)
#define  FastLockAcquire(x)			EnterCriticalSection(x)
#define  FastLockTryAcquire(x)		TryEnterCriticalSection(x)
#define  FastLockRelease(x)			LeaveCriticalSection(x)
#define  FastLockClose(x)			DeleteCriticalSection(x)

#define  SPINLOCK					CRITICAL_SECTION
#define  SpinLockInit(x, num)	    InitializeCriticalSectionAndSpinCount(x, num)
#define  SpinLockAcquire(x)			EnterCriticalSection(x)
#define  SpinLockTryAcquire(x)		TryEnterCriticalSection(x)
#define  SpinlockRelease(x)			LeaveCriticalSection(x)
#define  SpinLockClose(x)			DeleteCriticalSection(x)
/********************************************************/

/********************************************************/
class CCriticalSectionWrapper
{
public:
	CCriticalSectionWrapper()
	{
		m_bInitialized = TRUE;
		InitializeCriticalSection(&m_criticalSection);
	}
	
	~CCriticalSectionWrapper()
	{
		if (m_bInitialized)
			DeleteCriticalSection(&m_criticalSection);
		m_bInitialized = FALSE;
	}
	
	void Lock()
	{
		if (m_bInitialized)
			EnterCriticalSection(&m_criticalSection);
	}
	void Unlock()
	{
		if (m_bInitialized)
			LeaveCriticalSection(&m_criticalSection);
	}

protected:
	CRITICAL_SECTION m_criticalSection;
	BOOL m_bInitialized;
};
/************************************************************/

BasicSwitchComm.h

#ifndef  _BASIC_SWITCH_COMM_H_
#define  _BASIC_SWITCH_COMM_H_

class  CSwitchBasicComm
{
public:
	CSwitchBasicComm() {}
	virtual ~CSwitchBasicComm() {}

public:
	virtual BOOL Init(const void* pParam) = 0;
	virtual BOOL Exit(const void* pParam) = 0;
	virtual int Send(const void* pBuf, int nLen) = 0;
	virtual int Recv(void* pBuf, int nLen ) = 0;
	virtual void OnRecv(int nErrorCode) = 0;
	virtual void OnSend(int nErrorCode) = 0;
};

class  CRecvDataNotify
{
public:
	virtual void  OnNotify(const char* pBuf, int nLen) = 0; 
};

#endif

LockObj.h

/*******************************************************************************

功能: 智能指针类,范围指针类

设计要求: 1、 能够灵活使用
2、 可以抛出异常
3、 线程安全
特点:1 不能转换所有权
2 不能共享所有权
3 不能用于管理数组对象

注意的问题:此智能指针需要搭配线程同步部分使用,如同工程的BaseLock

*******************************************************************************/
#pragma once
#include "Noncopyable.h"

/*! \brief  Smart pointer with a scoped locking mechanism.
*
* This class is a wrapper for a volatile pointer. It enables synchronized access to the
* internal pointer by locking the passed mutex.
*/
template <typename Mutex>
class CLockObj : 
	private CNoncopyable
{
private:						//!< The instance pointer. 
	Mutex & m_mutex;              //!< Mutex is used for scoped locking.

public:
	/// Constructor.
	CLockObj(const volatile Mutex& mtx)
		: m_mutex(*const_cast<Mutex*>(&mtx))
	{   
		// Lock mutex
		m_mutex.Lock();
	}

	/// Destructor.
	~CLockObj()
	{ 
		// Unlock mutex
		m_mutex.Unlock();
	}

protected:
	CLockObj& operator=(const CLockObj& src) {  assert(0); return (*this); }
};

5.2.核心类

管道服务端

NamedPipeSrv.h

/*******************************************************************

// 功能: 命名管道通信服务端

// 作者: zdxiao

// 日期: 2013.04.15

// 备注: 目前的服务器端只支持单个客服端的连接
         还有很多需要改进的地方,后面有时间继续完善

// 修改历史

   修改日期     版本     修改人      修改功能
   2014.06.05   1.0		 zdxiao      1、把m_CriticalSection替换成已经写好的库
   2014.08.12   1.1		 zdxiao      2、线程同步用同步对象CLockObj
********************************************************************/
#ifndef  _NAME_PIPE_SRV_H_
#define  _NAME_PIPE_SRV_H_
#include "BasicSwitchComm.h"
#include "CriticalSectionWrapper.h"
#include <vector>
using namespace std;

struct  st_pipe_send_packet
{
	char  m_szBuf[1024];
	int   m_nLen;

public:
	st_pipe_send_packet()
	{
		memset(this, 0x00, sizeof(st_pipe_send_packet));
	}
};

class  CNamedPipeSrv : public CSwitchBasicComm
{
public:
	CNamedPipeSrv(CRecvDataNotify* pNotify = NULL);
	virtual ~CNamedPipeSrv();

public:
	BOOL Init(const void* pParam);
	BOOL Exit(const void* pParam);
	int Send(const void* pBuf, int nLen);
	int Recv(void* pBuf, int nLen ) {return 0;}
	void OnRecv(int nErrorCode) {}
	void OnSend(int nErrorCode) {}

	/********************************************/
	void OnRecv(const void* pBuf, int nLen);
	BOOL GetNewPacket(st_pipe_send_packet* packet);

public:
	void  SetClientConnectStatus(BOOL bStatus) { m_bClientIsConnected = bStatus; }
	BOOL  IsClientConnected() const { return m_bClientIsConnected; }

private:
	static unsigned int  _stdcall ServerReadProc(void* lpVoid);
	static unsigned int  _stdcall ServerWriteProc(void* lpVoid);

public:
	HANDLE  m_hReadPipe;
	HANDLE  m_hWritePipe;
	HANDLE  m_hReadEvent;
	HANDLE  m_hWriteEvent;

private:
	CCriticalSectionWrapper m_CriticalSection;
	CRecvDataNotify* m_pRecvDataNotify;
	HANDLE  m_hWriteThread;
	HANDLE  m_hReadThread;
	std::vector<st_pipe_send_packet*>  m_vecSendPacket;
	BOOL  m_bClientIsConnected;
};
#endif

NamedPipeSrv.cpp

#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include "NamedPipeSrv.h"
#include <assert.h>
#include "ShowTip.h"
#include <tchar.h>
#include "LockObj.h"

CNamedPipeSrv::CNamedPipeSrv(CRecvDataNotify* pNotify)
: m_bClientIsConnected(FALSE)
 ,m_hReadPipe(INVALID_HANDLE_VALUE)
 ,m_hReadThread(INVALID_HANDLE_VALUE)
 ,m_hWritePipe(INVALID_HANDLE_VALUE)
 ,m_hWriteThread(INVALID_HANDLE_VALUE)
 ,m_hReadEvent(INVALID_HANDLE_VALUE)
 ,m_hWriteEvent(INVALID_HANDLE_VALUE)
 ,m_pRecvDataNotify(pNotify)
{

}

CNamedPipeSrv::~CNamedPipeSrv()
{
	Exit(NULL);
}

BOOL CNamedPipeSrv::Exit(const void* pParam)
{
	if (m_hReadThread != INVALID_HANDLE_VALUE)
	{
		SetEvent(m_hReadEvent);
		WaitForSingleObject(m_hReadThread, 1000);
		CloseHandle(m_hReadThread);
		CloseHandle(m_hReadEvent);
		m_hReadThread = INVALID_HANDLE_VALUE;
		m_hReadEvent = INVALID_HANDLE_VALUE;
	}

	if (m_hWriteThread != INVALID_HANDLE_VALUE)
	{
		SetEvent(m_hWriteEvent);
		WaitForSingleObject(m_hWriteThread, 1000);
		CloseHandle(m_hWriteThread);
		CloseHandle(m_hWriteEvent);
		m_hWriteEvent = INVALID_HANDLE_VALUE;
		m_hWriteThread = INVALID_HANDLE_VALUE;
	}

	// 释放还未发送的数据信息
	st_pipe_send_packet* pPacket;
	std::vector<st_pipe_send_packet*>::iterator Iter = 	m_vecSendPacket.begin();
	for ( ; Iter != m_vecSendPacket.end(); Iter++)
	{
		pPacket = *Iter;
		delete pPacket;
	}
	m_vecSendPacket.clear();

	return TRUE;
}

BOOL CNamedPipeSrv::Init(const void* pParam)
{
	unsigned int nThreadID = 0;
	const char* pName = (const char*)pParam;
	assert(pName != NULL);

	// 读管道
	char szPipeName[MAX_PATH] = { 0 };
	_tcscat_s(szPipeName, pName);
	_tcscat_s(szPipeName, "_read");
	m_hReadPipe = ::CreateNamedPipe(szPipeName, 
					PIPE_ACCESS_DUPLEX,         // 数据留从客户到服务,全双工
					PIPE_TYPE_BYTE | PIPE_WAIT,  // 字节流,并且阻塞
					100,                         // 实例个数
					0,                           // 流出数据缓冲大小
					0,                           // 流入数据缓冲大小
					150,                         // 超时,毫秒
					NULL                         // 安全信息
					);
	if (m_hReadPipe == INVALID_HANDLE_VALUE)
		return FALSE;

	// 写管道
	memset(szPipeName, 0x00, sizeof(szPipeName));
	_tcscat_s(szPipeName, pName);
	_tcscat_s(szPipeName, "_write");
	m_hWritePipe = ::CreateNamedPipe(szPipeName, 
						PIPE_ACCESS_DUPLEX,         // 数据留从客户到服务,全双工
						PIPE_TYPE_BYTE | PIPE_WAIT,  // 字节流,并且阻塞
						100,                         // 实例个数
						0,                           // 流出数据缓冲大小
						0,                           // 流入数据缓冲大小
						150,                         // 超时,毫秒
						NULL                         // 安全信息
						);
	if (m_hWritePipe == INVALID_HANDLE_VALUE)
	{
		CloseHandle(m_hReadPipe);
		m_hReadPipe = INVALID_HANDLE_VALUE;
		return FALSE;
	}

	m_hReadEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	assert(m_hReadEvent != INVALID_HANDLE_VALUE);
	m_hWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	assert(m_hWriteEvent != INVALID_HANDLE_VALUE);

	m_hReadThread = (HANDLE)_beginthreadex(NULL, 0, CNamedPipeSrv::ServerReadProc, this, 0, &nThreadID);
	assert(m_hReadThread != INVALID_HANDLE_VALUE);
	m_hWriteThread = (HANDLE)_beginthreadex(NULL, 0, CNamedPipeSrv::ServerWriteProc, this, 0, &nThreadID);
	assert(m_hWriteThread != INVALID_HANDLE_VALUE);

	return TRUE;
}

void CNamedPipeSrv::OnRecv(const void* pBuf, int nLen)
{
	if (m_pRecvDataNotify != NULL)
	{
		m_pRecvDataNotify->OnNotify((const char*)pBuf, nLen);
	}
}

unsigned int  _stdcall CNamedPipeSrv::ServerReadProc(void* lpVoid)
{
	BOOL  bResult = FALSE;

	// 允许客户连接命名管道,如果不成功,终止.
	CNamedPipeSrv *pNamePipe = (CNamedPipeSrv*)lpVoid;
	if (pNamePipe == NULL)
		return 0;

	while (TRUE)
	{
		if (::WaitForSingleObject(pNamePipe->m_hReadEvent, 10) == WAIT_OBJECT_0)
			break;

		// 等待客户端的连接
		bResult = ConnectNamedPipe(pNamePipe->m_hReadPipe, NULL);
		if (!bResult) // 还没有断开,
		{
			if (GetLastError() ==  ERROR_PIPE_CONNECTED)  // 客户还在连接中
			{
				bResult = TRUE;
			}
			if (GetLastError() ==  ERROR_NO_DATA)         // 客户已经关闭
			{
				DisconnectNamedPipe(pNamePipe->m_hReadPipe);
				continue;
			}
		}

		// 读取数据
		char szMsg[1024] = "";
		DWORD  dwSize = 1024, dwBytesRead = 0;
		if (!bResult)
			continue;

		bResult = ::ReadFile(pNamePipe->m_hReadPipe, szMsg, dwSize, &dwBytesRead, NULL);
		if (bResult)
		{
			pNamePipe->OnRecv((void*)szMsg, dwBytesRead);
		}
	}

	FlushFileBuffers(pNamePipe->m_hReadPipe); 
	DisconnectNamedPipe(pNamePipe->m_hReadPipe); 
	CloseHandle(pNamePipe->m_hReadPipe); 
	pNamePipe->m_hReadPipe = INVALID_HANDLE_VALUE;

	// 结束进程
	_endthreadex(0);

	return 0;
}

unsigned int  _stdcall CNamedPipeSrv::ServerWriteProc(void* lpVoid)
{
	DWORD dwNumBytesWrite;

	// 允许客户连接命名管道,如果不成功,终止.
	CNamedPipeSrv *pNamePipe = (CNamedPipeSrv*)lpVoid;
	if (pNamePipe == NULL)
		return 0;

	while (TRUE)
	{
		if (::WaitForSingleObject(pNamePipe->m_hWriteEvent, 10) == WAIT_OBJECT_0)
			break;

		// 等待客户端的连接
		if (!ConnectNamedPipe(pNamePipe->m_hWritePipe, NULL))
		{
			if (GetLastError() == ERROR_PIPE_CONNECTED)  // 客户还在连接中
			{
				pNamePipe->SetClientConnectStatus(TRUE);
			}
			else if (GetLastError() == ERROR_NO_DATA)         // 客户已经关闭
			{
				pNamePipe->SetClientConnectStatus(FALSE);
				DisconnectNamedPipe(pNamePipe->m_hWritePipe);
				//continue;
			}
		}

		//获取数据
		st_pipe_send_packet  packet;
		if (!pNamePipe->GetNewPacket(&packet))
			continue;

		//发送数据
		if ( pNamePipe->IsClientConnected() )
		{
			::WriteFile(pNamePipe->m_hWritePipe, packet.m_szBuf, packet.m_nLen, &dwNumBytesWrite, NULL);
		}
	}

	FlushFileBuffers(pNamePipe->m_hWritePipe); 
	DisconnectNamedPipe(pNamePipe->m_hWritePipe); 
	CloseHandle(pNamePipe->m_hWritePipe); 
	pNamePipe->m_hWritePipe = INVALID_HANDLE_VALUE;
	
	// 结束进程
	_endthreadex(0);

	return 0;
}

int CNamedPipeSrv::Send(const void* pBuf, int nLen)
{
	CLockObj<CCriticalSectionWrapper> LockObj(m_CriticalSection);

	if ( !IsClientConnected() )
		return 0;

	int nPacketLen = 0;
	int nSendLen = 0;
	st_pipe_send_packet* pPacket = new st_pipe_send_packet;
	if (pPacket != NULL)
	{
		nPacketLen = sizeof(pPacket->m_szBuf);
		nPacketLen = (nPacketLen>nLen)?nLen:nPacketLen;
		memcpy(pPacket->m_szBuf, (const char*)pBuf, nPacketLen);
		pPacket->m_nLen = nPacketLen;
		m_vecSendPacket.push_back(pPacket);
		nSendLen = nPacketLen;
	}

	return nSendLen;
}

BOOL CNamedPipeSrv::GetNewPacket(st_pipe_send_packet* packet)
{
	if (m_vecSendPacket.size() <= 0)
		return FALSE;

	CLockObj<CCriticalSectionWrapper> LockObj(m_CriticalSection);
	std::vector<st_pipe_send_packet*>::iterator Iter = 	m_vecSendPacket.begin();
	*packet = *(*Iter);
	delete (*Iter);
	m_vecSendPacket.erase(Iter);

	return TRUE;
}

管道客户端

NamedPipeClient.h

/*******************************************************************

// 功能: 命名管道通信客户端

// 作者: zdxiao

// 日期: 2013.04.15

// 备注: 还有很多需要改进的地方,后面有时间继续完善

********************************************************************/
#ifndef  _NAME_PIPE_CLIENT_H_
#define  _NAME_PIPE_CLIENT_H_
#include "BasicSwitchComm.h"

class  CNamedPipeClient : public CSwitchBasicComm
{
public:
	CNamedPipeClient(CRecvDataNotify* pNotify = NULL);
	virtual ~CNamedPipeClient();

public:
	BOOL Init(const void* pParam);
	BOOL Exit(const void* pParam);
	int Send(const void* pBuf, int nLen);
	int Recv(void* pBuf, int nLen ) {return 0;}
	void OnRecv(int nErrorCode) {}
	void OnSend(int nErrorCode) {}

	/********************************************/
	void OnRecv(const void* pBuf, int nLen);

private:
	static unsigned int _stdcall  ClientReadProc(void* lpVoid);

public:
	HANDLE  m_hReadPipe;
	HANDLE  m_hReadEvent;
	HANDLE  m_hWritePipe;

private:
	CRecvDataNotify* m_pRecvDataNotify;
	HANDLE  m_hReadThread;
};
#endif

NamedPipeClient.cpp

#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include "NamedPipeClient.h"
#include <assert.h>
#include <tchar.h>

CNamedPipeClient::CNamedPipeClient(CRecvDataNotify* pNotify)
:m_hReadThread(INVALID_HANDLE_VALUE)
,m_hReadPipe(INVALID_HANDLE_VALUE)
,m_hWritePipe(INVALID_HANDLE_VALUE)
,m_hReadEvent(INVALID_HANDLE_VALUE)
,m_pRecvDataNotify(pNotify)
{

}

CNamedPipeClient::~CNamedPipeClient()
{
	Exit(NULL);
}

BOOL CNamedPipeClient::Exit(const void* pParam)
{
	if (m_hReadThread != INVALID_HANDLE_VALUE)
	{
		SetEvent(m_hReadEvent);
		WaitForSingleObject(m_hReadThread, 1000/*INFINITE*/);
		CloseHandle(m_hReadThread);
		CloseHandle(m_hReadEvent);
		m_hReadThread = INVALID_HANDLE_VALUE;
		m_hReadEvent = INVALID_HANDLE_VALUE;
	}

	// 关掉管道
	if (m_hWritePipe != INVALID_HANDLE_VALUE)
	{
		FlushFileBuffers(m_hWritePipe); 
		DisconnectNamedPipe(m_hWritePipe); 
		CloseHandle(m_hWritePipe); 
		m_hWritePipe = INVALID_HANDLE_VALUE;
	}

	return TRUE;
}

BOOL CNamedPipeClient::Init(const void* pParam)
{
	unsigned int nThreadID = 0;
	const char* pName = (const char*)pParam;
	assert(pName != NULL);

	// 读管道
	DWORD dwError;
	char szPipeName[MAX_PATH] = { 0 };
	_tcscat_s(szPipeName, pName);
	_tcscat_s(szPipeName, "_read");
	BOOL bResult = ::WaitNamedPipe(szPipeName, 5000);
	if (!bResult)
		return FALSE;
	m_hWritePipe  = ::CreateFile(szPipeName, 
						GENERIC_WRITE, 
						FILE_SHARE_WRITE, 
						NULL, 
						OPEN_EXISTING, 
						FILE_ATTRIBUTE_NORMAL, 
						NULL);
	if (m_hWritePipe == INVALID_HANDLE_VALUE)
	{
		dwError = GetLastError();
		return FALSE;
	}

	// 写管道
	memset(szPipeName, 0x00, sizeof(szPipeName));
	_tcscat_s(szPipeName, pName);
	_tcscat_s(szPipeName, "_write");
	m_hReadPipe  = ::CreateFile(szPipeName, 
						GENERIC_READ, 
						FILE_SHARE_READ, 
						NULL, 
						OPEN_EXISTING, 
						FILE_ATTRIBUTE_NORMAL, 
						NULL);
	if (m_hReadPipe == INVALID_HANDLE_VALUE)
	{
		dwError = GetLastError();
		CloseHandle(m_hWritePipe);
		m_hWritePipe = INVALID_HANDLE_VALUE;
		return FALSE;
	}

	m_hReadEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	assert(m_hReadEvent != INVALID_HANDLE_VALUE);

	m_hReadThread = (HANDLE)_beginthreadex(NULL, 0, CNamedPipeClient::ClientReadProc, this, 0, &nThreadID);
	assert(m_hReadThread != INVALID_HANDLE_VALUE);

	return TRUE;
}

void CNamedPipeClient::OnRecv(const void* pBuf, int nLen)
{
	if (m_pRecvDataNotify != NULL)
	{
		m_pRecvDataNotify->OnNotify((const char*)pBuf, nLen);
	}
}

unsigned int _stdcall  CNamedPipeClient::ClientReadProc(void* lpVoid)
{
	BOOL  bResult = FALSE;

	// 允许客户连接命名管道,如果不成功,终止.
	CNamedPipeClient *pNamePipe = (CNamedPipeClient*)lpVoid;
	if (pNamePipe == NULL)
		return 0;

	while (TRUE)
	{
		if (::WaitForSingleObject(pNamePipe->m_hReadEvent, 50) == WAIT_OBJECT_0)
			break;

		// 读取数据
		char szMsg[1024] = { 0 };
		DWORD  dwSize = 1024, dwBytesRead = 0;

		bResult = ReadFile(pNamePipe->m_hReadPipe, szMsg, dwSize, &dwBytesRead, NULL);
		if (bResult)
		{
			pNamePipe->OnRecv((void*)szMsg, dwBytesRead);
		}
	}

	FlushFileBuffers(pNamePipe->m_hReadPipe); 
	DisconnectNamedPipe(pNamePipe->m_hReadPipe); 
	CloseHandle(pNamePipe->m_hReadPipe); 
	pNamePipe->m_hReadPipe = INVALID_HANDLE_VALUE;

	// 结束进程
	_endthreadex(0);

	return 0;
}

int CNamedPipeClient::Send(const void* pBuf, int nLen)
{
	BOOL bResult = FALSE;
	DWORD dwBytesWriten = 0;
	bResult = ::WriteFile(m_hWritePipe, pBuf, nLen, &dwBytesWriten, NULL);
	if (!bResult)
	{
		return 0;
	}

	return dwBytesWriten;
}

6.使用场景

  • 客户端-服务器通信:命名管道非常适合作为客户端-服务器模型中的通信媒介。服务器进程可以创建一个命名管道并等待客户端连接,而客户端进程则可以连接到该管道并与服务器进行通信。这种模型在分布式系统或网络应用程序中非常常见。
  • 进程间通信:不同进程之间需要进行数据交换或共享资源时,可以使用命名管道进行通信。

7.总结

        命名管道具有简单易用、跨平台兼容性、双向通信、安全性和可靠性等优点。然而,它也受到单机通信限制、性能瓶颈、同步问题、复杂性增加和依赖性等缺点的制约。

        命名管道通常只适用于同一台计算机上的进程间通信。虽然有些操作系统(如Windows)可能支持跨网络的命名管道通信,但这种方式通常不如套接字(Sockets)等网络通信机制灵活和高效。因此,在选择IPC机制时,应根据实际需求考虑数据传输的速度、大小、方向以及进程间的亲缘关系等因素。

相关链接:

命名管道打开模式 - Win32 apps | Microsoft Learn

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值