进程间通信之------- 命名管道

混江龙づ霸主 提交于 2020-03-04 05:48:07

我们知道管道包括三种:

1):普通管道PIPE,通常有很多限制,一是半双工,只能单向传输,二是只能在父子进程间使用

2):流管道:这种能双向传输,但是也是只能父子进程间使用。

3):命名管道,去除了以上的第二种限制,可以在许多不相关的进程间进行通讯。也是半双工的通信方式。

但是通常我们把管道分为匿名管道和命名管道。但对于匿名管道的话,只能在本机上进程之间通信,而且只能实现本地的父子进程之间的通信,局限性太大了。而这里介绍的命名管道,就和匿名管道有些不同了,在功能上也就显得强大许多,至少其可以实现跨网络之间的进程的通信,同时其客户端既可以接收数据也可以发送数据,服务器端也是可以接收数据,又可以发送数据。

 

匿名管道的概述

  对于匿名管道而言,命名管道使用了windows安全机制,因而命名管道的服务器端可以控制哪些客户有权与其建立连接。哪些客户端是不能够与这个命名管道建立连接的。命名管道的通信是以连接的方式进行的,服务器创建一个命名管道对象,然后在此对象上等待连接请求,一旦客户连接过来,则两者都可以通过命名管道读或者写数据。

  命名管道提供了两种通信模式:字节模式和消息模式。在字节模式下,数据以一个连续的字节流的形式在客户机和服务器之间流动。而在消息模式下,客户机和服务器则通过一系列不连续的数据单位,进行数据的收发,每次在管道上发出一个消息后,它必须作为一个完整的消息读入。

 

命名管道的使用步骤

服务器端:

1):服务器进程调用CreateNamedPipe函数来创建一个有名称的命名管道在创建命名管道的时候必须指定一个本地的命名管道名称。windows允许同一个本地的命名管道名称右多个命名管道实例。所以,服务器进程在调用CreateNamedPipe函数时必须指定最大允许的实例数(0-255).如果CreateNamedPipe函数成功返回后,服务器进程得到一个指向一个命名管道实例的句柄。

2):服务器进程就可以调用ConnectNamedPipe来等待客户的连接请求,这个ConnectNamedPipe既支持同步形式,又支持异步形式,若服务器进程以同步形式调用 ConnectNamedPipe函数,如果没有得到客户端的连接请求,则会一直等到客户端的连接请求。当该函数返回时,客户端和服务器之间的命名管道连接已经建立起来了。

3):这个时候服务器端就可以向客户端读(ReadFile)/写(WriteFile)数据了。

4):在已经建立连接的命名管道实例中,服务器进程就会得到一个指向该管道实例的句柄,这个句柄称之为服务器端句柄,同时服务端进程可以调用DisconnectNamedPipe函数,将一个管道实例与当前建立连接的客户端进程断开,从而可以重新连接到新的客户端进程。当然,服务器也可以调用CloseHandle来关闭一个已经建立连接的命名管道实例。

 

客户端:

1):客户端进程调用CreateFile函数连接到一个正在等待连接的命名管道上。在这里客户端需要指定将要连接的命名管道上。当CreateFile成功返回之后,客户端就得到了一个指向已经建立连接的命名管道实例的句柄。在这里客户端也可以先调用WaitNamedPipe函数来测试指定名称的管道实例是否可用。在已经建立的命名管道实例中,客户端进程就会得到一个指向该管道实例的句柄。这个句柄称之为客户端句柄。

2):这个时候客户端就可以向服务器读(ReadFile)/写(WriteFile)数据了.

3):客户端可以调用CloseHandle来关闭一个已经建立连接的命名管道实例。

 

函数介绍

1:

HANDLE WINAPI CreateNamedPipe(
  _In_     LPCTSTR               lpName,
  _In_     DWORD                 dwOpenMode,
  _In_     DWORD                 dwPipeMode,
  _In_     DWORD                 nMaxInstances,
  _In_     DWORD                 nOutBufferSize,
  _In_     DWORD                 nInBufferSize,
  _In_     DWORD                 nDefaultTimeOut,
  _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

 该函数用来创建一个命名管道的实例,并返回这个命名管道的句柄。如果需要创建一个命名管道的多个实例,就需要多次调用CreateNamedPipe函数,参数 lpName 为一个字符串,其格式必须为 \\.\pipe\pipeName,其中圆点 ”.” 表示的是本地机器,如果想要与远程的服务器建立连接,那么这个圆点位置处应指定这个远程服务器的名称,而其中的 “pipe” 这个是个固定的字符串,也就是说不能进行改变的,最后的 “pipename” 则代表的是我将要创建的命名管道的名称了,参数 dwOpenMode 用来指定管道的访问方式,重叠方式,写直通方式,还有管道句柄的安全访问方式。

  命名管道的访问方式如下表:

命名管道的写直通方式和重叠方式

命名管道的安全访问方式

 命名管道句柄的类型

 命名管道句柄的读取方式

 命名管道句柄的等待方式

 nMaxInstance 指定命名管道能够创建的实例的最大数目.该参数的取值可以从 0 – 255 ,这里说的最大实例数目是指对同一个命名管道最多能创建的实例数目。

nOutBufferSize 用来指定将要为输出缓冲区所保留的字节数

nInBufferSize 用来指定将要为输入缓冲区所保留的字节数

nDefaultTimeOut 用来指定默认的超时值,以毫秒为单位,同一个管道的不同实例必须指定同样的超时值

lpSecurityAttributes 用来设置该命名管道的安全性,一般设置为 NULL ,也就是采用 Windows 提供的默认安全性

 

2:

BOOL WINAPI ConnectNamedPipe(
  _In_        HANDLE       hNamedPipe,
  _Inout_opt_ LPOVERLAPPED lpOverlapped
);该函数的作用是让服务器等待客户端的连接请求的到来hNamedPipe 指向一个命名管道实例的服务器的句柄

 lpOverlapped 指向一个 OVERLAPPED结构的指针,

如果 hNamedPipe 所标识的命名管道是用 FILE_FLAG_OVERLAPPED

(也就是重叠模式或者说异步方式)标记打开的,则这个参数不能为 NULL

必须是一个有效的指向一个 OVERLAPPED 结构的指针,否则该函数可能会错误的执行

 3:

BOOL WINAPI WaitNamedPipe(
  _In_ LPCTSTR lpNamedPipeName,
  _In_ DWORD   nTimeOut
);

 通过该函数可以判断是否有可用的命名管道。直到等待的时间间隔已过,或者指定的命名管道的实例可以用来连接了。

lpNamedPipeName 用来指定命名管道的名称,格式同CreateNamedPipe函数的lpNamedPipeName参数。

 参数 nTimeOut 用来指定超时间隔,参数可以填写系列的值::

NMPWAIT_USE_DEFAULT_WAIT::超时间隔即为服务器端创建该命名管道时指定的超时间隔。

NMPWAIT_USE_DEFAULT_WAIT::一直等待,直到出现一个可用的命名管道的实例。

 

命名管道的通信实例:

服务器端的实现

NamePipeServer.h

 1 #ifndef NAME_PIPE_SERVER_H
 2 #define NAME_PIPE_SERVER_H
 3 
 4 #include<windows.h>
 5 #include<iostream>
 6 
 7 class NamePipeServer
 8 {
 9 public:
10     NamePipeServer()
11     {    
12         pStr = "data from server";
13         pPipeName = "\\\\.\\pipe\\testPipe";
14     }
15     //创建命名管道
16     void CreateNamedPipeInServer(); 
17     //从命名管道中读取数据
18     void NamedPipeReadInServer(); 
19     //往命名管道中写入数据
20     void NamedPipeWriteInServer();
21 private:
22     HANDLE    hNamedPipe;
23     const char *pStr;
24     const char *pPipeName;
25 };
26 
27 #endif

NamePipeServer.cpp

  1 #include "stdafx.h"
  2 #include "NamePipeServer.h"
  3 
  4 using namespace std;
  5 void NamePipeServer::CreateNamedPipeInServer()
  6 {
  7      HANDLE  hEvent;
  8      OVERLAPPED ovlpd;
  9 
 10      BYTE sd[SECURITY_DESCRIPTOR_MIN_LENGTH];
 11      SECURITY_ATTRIBUTES sa;
 12 
 13      sa.nLength = sizeof(SECURITY_ATTRIBUTES);
 14      sa.bInheritHandle = TRUE;
 15      sa.lpSecurityDescriptor = &sd;
 16 
 17      InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
 18      SetSecurityDescriptorDacl(&sd, TRUE, (PACL) 0, FALSE);
 19      //创建命名管道
 20      //这里创建的是双向模式且使用重叠模式(异步操作)的命名管道
 21      hNamedPipe = CreateNamedPipe( L"\\\\.\\pipe\\testspipe",PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,0, 1, 1024, 1024, 0, &sa);
 22      if( INVALID_HANDLE_VALUE == hNamedPipe )
 23      {
 24          cout << GetLastError() << endl;
 25          hNamedPipe = NULL;
 26          cout << "创建命名管道失败!!!" << endl << endl;
 27          return;
 28      }
 29      hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
 30      if( !hEvent )    
 31      {        
 32          cout<<"创建事件失败 ..."<< endl<< endl;        
 33          return;    
 34      }
 35      memset(&ovlpd, 0, sizeof(OVERLAPPED)); 
 36      ovlpd.hEvent = hEvent;
 37 
 38      cout << "等待客户端的连接" << endl;
 39      if( !ConnectNamedPipe(hNamedPipe, &ovlpd) )
 40      {
 41          if( ERROR_IO_PENDING != GetLastError() )
 42          {
 43               CloseHandle(hNamedPipe);            
 44               CloseHandle(hEvent);             
 45               cout<<"等待客户端连接失败 ..."<< endl << endl;            
 46               return;
 47          }
 48      }
 49       //等待事件 hEvent 失败
 50      if( WAIT_FAILED == WaitForSingleObject(hEvent, INFINITE) )    
 51      {        
 52          CloseHandle(hNamedPipe);        
 53          CloseHandle(hEvent);         
 54          cout<<"等待对象失败 ..."<<endl<<endl;        
 55          return;    
 56      }     
 57      CloseHandle(hEvent);
 58 }
 59 
 60 void NamePipeServer::NamedPipeReadInServer()
 61 {
 62        char *            pReadBuf;    
 63        DWORD            dwRead;     
 64        pReadBuf = new char[strlen(pStr) + 1];    
 65        memset(pReadBuf, 0, strlen(pStr) + 1);     
 66        //从命名管道中读取数据    
 67        if( !ReadFile(hNamedPipe, pReadBuf, strlen(pStr), &dwRead, NULL) )    
 68        {        
 69            delete []pReadBuf;         
 70            cout<<"读取数据失败 ..."<< endl<< endl;        
 71            return;    
 72        }    
 73        cout << "读取数据成功::"<< pReadBuf << endl<< endl;
 74 }
 75 
 76 void NamePipeServer::NamedPipeWriteInServer()
 77 {
 78      DWORD            dwWrite;     
 79      //向命名管道中写入数据    
 80      if( !WriteFile(hNamedPipe, pStr, strlen(pStr), &dwWrite, NULL) )    
 81      {        
 82          cout << "写入数据失败 ..." << endl<< endl;        
 83          return;    
 84      }    
 85      cout << "写入数据成功:: "<< pStr<< endl<< endl;
 86 }
 87 
 88 int main()
 89 {
 90     NamePipeServer  pipeserver;
 91     //创建命名管道
 92     pipeserver.CreateNamedPipeInServer();
 93     //从命名管道读数据
 94     pipeserver.NamedPipeReadInServer();
 95     //向匿名管道中写入数据
 96     pipeserver.NamedPipeWriteInServer();
 97 
 98     system("pause");
 99     return 0;
100 }


客户端代码实现:

NamePipeClient.h

 1 #ifndef _NAME_PIPE_CLIENT_H
 2 #define _NAME_PIPE_CLIENT_H
 3 
 4 #include<windows.h>
 5 #include<iostream>
 6 
 7 class NamePipeClient
 8 {
 9 public:
10     NamePipeClient()
11     {
12         pStr = "data from client";
13         pPipeName = "\\\\.\\pipe\\testPipe";
14     }
15     //打开命名管道
16     void OpenNamedPipeInClient(); 
17     //客户端从命名管道中读取数据
18     void NamedPipeReadInClient(); 
19     //客户端往命名管道中写入数据
20     void NamedPipeWriteInClient();
21 
22 private:
23     //用来保存在客户端通过 CreateFile 打开的命名管道句柄HANDLE            
24     HANDLE hNamedPipe;
25     const char * pStr;
26     const char * pPipeName;
27 };
28 
29 #endif

NamePipeClient.cpp

 1 #include "stdafx.h"
 2 #include "NamePipeClient.h"
 3 
 4 using namespace std;
 5 
 6 void NamePipeClient::OpenNamedPipeInClient()
 7 {    
 8     //等待连接命名管道    
 9     if( !WaitNamedPipe(L"\\\\.\\pipe\\testspipe", NMPWAIT_WAIT_FOREVER) )    
10     {        
11         cout<<"命名管道实例不存在 ..."<< endl<< endl;        
12         return;    
13     } 
14     cout << "成功连接到服务器" << endl;
15     //打开命名管道    
16     hNamedPipe = CreateFile( L"\\\\.\\pipe\\testspipe", GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);    
17     if( INVALID_HANDLE_VALUE == hNamedPipe )    
18     {        
19         cout << "打开命名管道失败!!!" << endl << endl;        
20         return;    
21     }
22 }
23 
24 void NamePipeClient::NamedPipeReadInClient()
25 {
26       char * pReadBuf;    
27       DWORD  dwRead;     
28       pReadBuf = new char[strlen(pStr) + 1];    
29       memset(pReadBuf, 0, strlen(pStr) + 1);     
30       //从命名管道中读取数据    
31       if( !ReadFile(hNamedPipe, pReadBuf, strlen(pStr), &dwRead, NULL) )    
32       {        
33           delete []pReadBuf;         
34           cout << "读取数据失败 ..."<< endl << endl;        
35           return;    
36       }    
37       cout<<"读取数据成功:: "<< pReadBuf << endl << endl;
38 }
39 
40 void NamePipeClient::NamedPipeWriteInClient()
41 {
42        DWORD dwWrite;     
43        //向命名管道中写入数据    
44        if( !WriteFile(hNamedPipe, pStr, strlen(pStr), &dwWrite, NULL) )    
45        {        
46            cout<<"写入数据失败 ..." << endl << endl;        
47            return;    
48        }    
49        cout<< "写入数据成功:: "<< pStr << endl << endl;
50 }
51 
52 int main()
53 {
54     NamePipeClient pipeclient;
55     pipeclient.OpenNamedPipeInClient();
56     //往命名管道中写入数据
57     pipeclient.NamedPipeWriteInClient();
58     //接收从服务器发来的数据
59     pipeclient.NamedPipeReadInClient();
60     system("pause");
61     return 0;
62 }


首先启动服务器,服务器在等待客户端的连接

然后启动客户端

 可以看到服务器成功从客户端读取到数据,并且写入数据成功。

而客户端也写入服务器数据成功,并且成功读取到服务器的数据。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!