进程间通信之管道

时间:2022-12-07 19:08:52

命名管道更加高级。它由一个名字来标识,以使得客户端和服务端应用程序可以通过它进行彼此通信。而且win32命名管道甚至可以在不同系统的进程间使用。命名管道有时候也被称为fifo。有了命名管道后。一个进程可以把数据放到管道中。另一个知道管道名字的进程把数据取走。命名管道与其他交换数据方式不同的地方在于。如果进程不知道这个管道的名字就不可能把数据取走。

管道实际是用于进程间通信的一段共享内存。创建管道的进程称为管道服务器。连接到一个管道的进程为管道客户机。可以用以下函数创建管道。

Handle CreateNamedPipe(LPCTSTR lpName,DWORD dwOpenMode,DWord dwPipeMode)

lpName命名规范:\\[host_name]\pipe\[Path]Name

比如:LPCSTR szPipeName=TEXT("\\\\.\\pipe\\ssnp\\");

第一部分\\[host_name]指定了服务器的名字。命名管道服务即在此服务器创建。其字符串部分可表示为小数点(本机),星号(当前网络字段),域名或是一个真正的服务。第二部分"\pipe"是一个硬编码字符串,第三部分为命名管道的名字。而且可以设置多级目录。

dwOpenMode:管道创建方式。可以是下面值的组合。

pipe_access_inbound:管道只能用作接收数据。

pipe_Access_outbound:管道只能用作发送数据。

pipe_Access_Duplex:管道即可以发送也可以接收数据。

上面这三个值只能够取其中一个。同时也可以包括以下一个或两个标示

File_flag_write_through:管道用于同步发送和接收数据。在系统内部对于命名管道的处理上不经过缓冲区并能直接发送。并且只有在数据被发送到目标地址时发送函数才会返回。

file_flag_overlapped管道可以用于异步输入和输出。

dwPipeMode:命名管道模式:

 通信的实现流程

1:连接建立

服务端通过函数CreateNamedPipe创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。如果在已定义超时值变为0以前。有一个实例管道可以使用。则创建成功并返回管道句柄。并用以侦听来自客户端的连接请求。该功能通过connectnamepipe实现。另一方面。客户端通过函数waitnamepipe使服务进程等待来自客户的实例连接。如果在超时值变为0以前。有一个管道可以为连接使用。则waitnamedpipe返回true.并通过调用createfile或callnamedpipe来呼叫对服务端的连接。此时服务端将接受客户端的连接请求。成功建立连接。服务端connectnamepipe返回true.客户端createFile返回一个指向管道文件的句柄。

从时序上讲。首先是客户端通过waitnamedpipe使服务端的createfile在限定时间内创建实例成功。然后双方通过connectnamedpipe和createfile成功连接。并返回用于通信的文件句柄。此时双方即可进行通信。

2:通信实现

建立连接之后。客户和服务端可以通过得到的管道文件句柄利用readfile和writefile进行彼此间的信息交换。

3:连接终止

客户端应调用closefile而服务端应接着调用disconnectnamedpipe。当然服务端也可通过单方面调用disconnectnamedpipe终止连接。最后应调用closehandle来关闭该管道。

实例解析

服务端:

(1)定义一个定时器不断的更新收到的消息。

m_strPipe = thePipe.GetRequest();
    UpdateData(FALSE);
 (2)创建命名管道,并创建线程

m_strReply=strReply;
 LPCSTR szPipeName=TEXT("\\\\.\\pipe\\ssnp\\"); 
 // 为接收消息创建命名管道.
 m_hPipe=CreateNamedPipe(szPipeName,
  PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH,
  // 阻塞模式.
  PIPE_WAIT|PIPE_TYPE_BYTE,
  PIPE_UNLIMITED_INSTANCES,
  128,128,NULL,NULL); 
 // 检查是否命名管道被创建.
 if (m_hPipe == INVALID_HANDLE_VALUE)
 {
  TRACE("Unable to create a named pipe.\n");
  return;
 }
 m_pThread=AfxBeginThread(ServerReadProc, this); // 启动线程.

(3)等待客户端连接上并读写管道。

为避免在服务程序出现阻塞现象。创建了一个进程。

UINT CMyPipe::ServerReadProc(LPVOID lpVoid)
{
 DWORD dwNumBytesRead,dwNumBytesWrite;
 char toDisptxt[80];
 
 // 允许客户连接命名管道,如果不成功,终止.
 TRACE("Waiting for connection... \n");
 
 CMyPipe *Parent= (CMyPipe*)lpVoid;
 //ConnectNmaepipe:服务进程准备好一个连接到客户进程的管道。并等待一个客户进程连接上为至;

//当没有客户程序连接时。服务程序就停止在此处等待外界的连接。只有在接收到来自客户端的连接时。程序才继续向下执行。对于这种情况。如果不采用线程。服务程序窗口就不能显示出来。连接成功后。用readfile和writefile分别从管道里读取数据和向管道里写入数据。
 if(!ConnectNamedPipe(Parent->m_hPipe, (LPOVERLAPPED) NULL))
 {
  TRACE("Unable to connect a named pipe. Error: %d\n",GetLastError());
  CloseHandle(Parent->m_hPipe);
  return 1;
 }
 // 反复检查消息直到程序终止.
 while(1)
 {
  // 读取消息并检查读取数据是否成功.
  if (!ReadFile(Parent->m_hPipe, toDisptxt,sizeof(toDisptxt),
   &dwNumBytesRead, (LPOVERLAPPED) NULL))
  {
   TRACE("Unable to read from named pipe. Error: %d\n" ,GetLastError());
   CloseHandle(Parent->m_hPipe);
   return 1;
  }
  else
  {
   // 保存接收的字符串.
   Parent->m_strRequest=toDisptxt;
   strcpy(toDisptxt,Parent->m_strReply);
   // 写回一个字符串.
   WriteFile(Parent->m_hPipe, toDisptxt,sizeof(toDisptxt),
        &dwNumBytesWrite, (LPOVERLAPPED) NULL);
  }
 }

 return 0;
}

客户端:

(1):在命名管道服务程序启动后。可以用createfile打开命名管道。就像创建文件一样。当文件名必须与服务端创建命名管道的名称相同。否则不能与服务相连。

LPCSTR szPipeName=TEXT("\\\\.\\pipe\\ssnp\\"); 
 m_hPipe=CreateFile(szPipeName,
  GENERIC_READ|GENERIC_WRITE,
  FILE_SHARE_READ| FILE_SHARE_WRITE,NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_ARCHIVE|FILE_FLAG_WRITE_THROUGH,
  // FILE_FLAG_WRITE_THROUGH 设定阻塞.
  NULL); 
 // 检查并判别命名管道文件是否被打开,如果没有被打开,终止程序.
 if (m_hPipe == INVALID_HANDLE_VALUE)
  TRACE("Unable to create a named pipe. Error: %d\n",GetLastError());

(2):向管道写入数据读出数据。

m_strRequest=strRequest;
 char toSendtxt[80];

 // 反复发送消息直到程序终止.
 while(1)
 {
  TRACE("Sending...\n");
  DWORD dwNumBytesWritten,dwNumBytesRead; 
  strcpy(toSendtxt,m_strRequest);
  // 向管道写入消息.
  if (!WriteFile(m_hPipe,toSendtxt, (DWORD)sizeof(toSendtxt),
   &dwNumBytesWritten,(LPOVERLAPPED) NULL))
  {
   // 如果向命名管道写入时出现错误,终止程序.
   TRACE("Unable to write to named pipe. Error: %d\n",GetLastError());
   CloseHandle(m_hPipe);
  }
  else{
   ReadFile(m_hPipe,toSendtxt, (DWORD)sizeof(toSendtxt),
    &dwNumBytesRead,(LPOVERLAPPED) NULL);
   m_strReply=toSendtxt;
   break;
  }
  // 在再次发送消息前等待.
        Sleep(4800);
 }