Windows进程通信——命名管道

来源:互联网 发布:电脑照相机软件知乎 编辑:程序博客网 时间:2024/05/22 13:57

1. 概述

命名管道是通过网络来完成进程之间的通信的,命名管道依赖于底层网络接口,其中包括有 DNS 服务,TCP/IP 协议等等机制,但是其屏蔽了底层的网络协议细节,对于匿名管道而言,其只能实现在父进程和子进程之间进行通信,而对于命名管道而言,其不仅可以在本地机器上实现两个进程之间的通信,还可以跨越网络实现两个进程之间的通信。命名管道使用了 Windows 安全机制,因而命名管道的服务端可以控制哪些客户有权与其建立连接,而哪些客户端是不能够与这个命名管道建立连接的。利用命名管道机制实现不同机器上的进程之间相互进行通信时,可以将命名管道作为一种网络编程方案时,也就是看做是 Socket 就可以了,它实际上是建立了一个客户机/服务器通信体系,并在其中可靠的传输数据。
命名管道的通信是以连接的方式来进行的,服务器创建一个命名管道对象,然后在此对象上等待连接请求,一旦客户连接过来,则两者都可以通过命名管道读或者写数据。命名管道提供了两种通信模式:字节模式和消息模式。在字节模式下,数据以一个连续的字节流的形式在客户机和服务器之间流动,而在消息模式下,客户机和服务器则通过一系列的不连续的数据单位,进行数据的收发,每次在管道上发出一个消息后,它必须作为一个完整的消息读入。

1.1 软件流程

服务端:
(1)服务端进程调用 CreateNamedPipe 函数来创建一个有名称的命名管道,在创建命名管道的时候必须指定一个本地的命名管道名称(不然就不叫命名管道了),Windows 允许同一个本地的命名管道名称有多个命名管道实例,所以,服务器进程在调用 CreateNamedPipe 函数时必须指定最大允许的实例数(0 -255),如果 CreateNamedPipe 函数成功返回后,服务器进程得到一个指向一个命名管道实例的句柄
(2)服务器进程就可以调用 ConnectNamedPipe 来等待客户的连接请求,这个 ConnectNamedPipe 既支持同步形式,又支持异步形式,若服务器进程以同步形式调用 ConnectNamedPipe 函数,(同步方式也就是如果没有得到客户端的连接请求,则会一直等待)那么,当该函数返回时,客户端与服务器之间的命名管道连接也就已经建立起来了。
在已经建立了连接的命名管道实例中,服务端进程就会得到一个指向该管道实例的句柄,这个句柄称之为服务端句柄。同时,服务端进程可以调用 DisconnectNamedPipe 函数,将一个管道实例与当前建立连接的客户端进程断开,从而可以重新连接到新的客户进程。当然在服务端也是可以调用 CloseHandle 来关闭一个已经建立连接的命名管道实例。

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

1.2 函数介绍

创建命名管道函数CreateNamedPipe(),它的的原型
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 
用来指定管道的访问方式,重叠方式,写直通方式,还有管道句柄的安全访问方式。同一个命名管道的每一个实例都必须具有相同的类型。如果该参数设置为 0 ,则默认将使用字节类型方式,即通过这个参数可指定创建的是字节模式还是消息流模式。对于管道句柄的读取方式来说,同一个管道的不同实例可以指定不同的读取方式。如果该值为 0 ,则默认将使用字节读方式。而对于管道句柄的等待方式,则同一个管道的不同实例可以取不同的等待方式。如果该值设置为 0 ,则默认为阻塞方式。
命名管道的访问方式如下:
PIPE_ACCESS_DUPLEX
双向模式。服务器进程和客户端进程都可以从管道读取数据和向管道中写入数据
PIPE_ACCESS_INBOUND
服务器端就只能读取数据,而客户端就只能向管道中写入数据
PIPE_ACCESS_OUTBOUND
服务器端就只能写入数据,而客户端就只能从管道中读取数据

命名管道的写直通方式和重叠方式:
FILE_FLAG_WRITE_THROUGH
写直通方式(可以简单的看做是同步操作)。该方式只影响对字节类型管道的写入操作。且只有当客户端与服务端进程位于不同的计算机上时才有效。该方式只有等到欲写入命名管道的数据通过网络传送出去,并且放置到了远程计算机的管道缓冲区以后,写数据的函数才会成功返回
FILE_FLAG_OVERLAPPED
重叠模式(可以简单的看做是异步操作)。实现前台线程执行其他操作,而耗时操作可在后台进行

命名管道的安全访问方式
WRITE_DAC
调用者对命名管道的任意访问控制列表都可以进行写入。
WRITE_OWNER
调用者对命名管道的所有者可以进行写入访问。
ACCESS_SYSTEM_SECURITY
调用者对命名管道的安全访问控制列表都可以写入。

参数 dwPipeMode 
用来指定管道句柄的类型,读取和等待方式。
命名管道句柄的类型
PIPE_TYPE_BYTE
数据以字节流的形式写入管道。该方式不能在 PIPE_READMODE_MESSAGE 读方式下使用
PIPE_TYPE_MESSAGE
数据以消息流的形式写入管道

命名管道句柄的读取方式
PIPE_READMODE_BYTE
以字节流的方式从管道中发读取数据。
PIPE_READMODE_MESSAGE
以消息流的方式从管道读取数据。该方式只有在 PIPE_TYPE_MESSAGE 类型下才可以使用。

命名管道句柄的等待方式
PIPE_WAIT
允许阻塞方式也就是同步方式。ReadFile,WriteFile,ConnectNamedPipe 函数,必须等到读取到数据或写入新数据或有一个客户连接来才能返回。
PIPE_NOWAIT
允许非阻塞方式也就是异步方式。ReadFile,WriteFile,ConnectNamedPipe 函数总是立即返回。

参数 nMaxInstance 

指定命名管道能够创建的实例的最大数目。该参数的取值可以从 0 – 255 ,这里说的最大实例数目是指对同一个命名管道最多能创建的实例数目,如果希望同时连接 5 个客户端,那么则必须调用 5 次 CreateNamedPipe 函数创建 5 个命名管道实例,然后才能同时接收到 5 个客户端连接请求的到来,对于同一个命名管道的实例来说,在某一个时刻,它只能和一个客户端进行通信。
参数 nOutBufferSize
用来指定将要为输出缓冲区所保留的字节数。
参数 nInBufferSize
用来指定将要为输入缓冲区所保留的字节数。
参数 nDefaultTimeOut
用来指定默认的超时值,以毫秒为单位,同一个管道的不同实例必须指定同样的超时值。
参数 lpSecurityAttributes
用来设置该命名管道的安全性,一般设置为 NULL ,也就是采用 Windows 提供的默认安全性。         

服务端等待客户端连接请求

BOOL WINAPI ConnectNamedPipe(  _In_        HANDLE       hNamedPipe,  _Inout_opt_ LPOVERLAPPED lpOverlapped);
该函数的作用是让服务器等待客户端的连接请求的到来。
参数  hNamedPipe
指向一个命名管道实例的服务器的句柄。该句柄由 CreateNamedPipe 函数返回。
参数 lpOverlapped
指向一个 OVERLAPPED 结构的指针,如果 hNamedPipe 所标识的命名管道是用 FILE_FLAG_OVERLAPPED ,(也就是重叠模式或者说异步方式)标记打开的,则这个参数不能为 NULL ,必须是一个有效的指向一个 OVERLAPPED 结构的指针,否则该函数可能会错误的执行。             
                

客户端连接命名管道
BOOL WINAPI WaitNamedPipe(  _In_ LPCTSTR lpNamedPipeName,  _In_ DWORD   nTimeOut);
客户端在连接服务端程序创建的命名管道之前,首先应该判断一下,是否有可以利用的命名管道,通过调用该函数可以用来实现这一点,该函数会一直等到,直到等待的时间间隔已过,或者指定的命名管道的实例可以用来连接了,也就是说该管道的服务器进程有正在等待被连接的的 ConnectNamedPipe 操作。
参数 lpNamedPipeName
用来指定命名管道的名称,这个名称必须包括创建该命名管道的服务器进程所在的机器的名称,格式为:\\.\pipe\pipeName ,如果是在同一个机器上编写的命名管道的服务器端程序和客户端程序,则当指定这个名称时,在开始的两个反斜杠后可以设置一个圆点来表示服务器进程在本地机器上运行,如果是跨网络通信,则在这个圆点位置处应该设置为服务器端所在的主机的名称。
参数 nTimeOut
用来指定超时间隔。
NMPWAIT_USE_DEFAULT_WAIT 超时间隔即为服务器端创建该命名管道时指定的超时间隔。
NMPWAIT_WAIT_FOREVER 一直等待,直到出现一个可用的命名管道的实例。

2. 编码实现

2.1 服务端

// NamedPipe.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <iostream>#include <stdlib.h>#include <stdio.h>#include <windows.h>#include <string>using std::cout;using std::endl;HANDLE m_hNamedPipe;LPCTSTR PipeName(_T("\\\\.\\pipe\\my_test"));bool my_CreateNamedPipe();//创建命名管道bool my_SendData(std::string data);//发送数据int _tmain(int argc, _TCHAR* argv[]){system("color f0");std::string send_data = "";if(!my_CreateNamedPipe()) return 0;while ("0" != send_data){cout << "输入需要发送的数据: " << endl;std::cin >> send_data;if(!my_SendData(send_data)) break;;}CloseHandle(m_hNamedPipe);system("pause");return 0;}bool my_CreateNamedPipe()//创建命名管道{//创建命名管道,采用双向非重叠模式m_hNamedPipe = CreateNamedPipe(_T("\\\\.\\pipe\\my_test"),//管道的名称PIPE_ACCESS_DUPLEX |FILE_FLAG_OVERLAPPED,//管道的打开方式NULL,//管道句柄的类型,读取和等待方式5,//管道的实例数(最多)4096,//管道的输出缓存大小4096,//管道的输入缓则大小0,//设定的超时时间,使用默认的50毫秒NULL);if (INVALID_HANDLE_VALUE == m_hNamedPipe){cout << "创建命名管道失败"  << endl;CloseHandle(m_hNamedPipe);return false;}//检查是否创建成功HANDLE hEvent;OVERLAPPED ovlpd;//添加事件以等待客户端连接命名管道//该事件为手动重置事件,且初始化状态为无信号状态hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);if (!hEvent){cout << "创建事件失败 ..." << endl << endl;return false;}memset(&ovlpd, 0, sizeof(OVERLAPPED));//将手动重置事件传递给 ovlap 参数ovlpd.hEvent = hEvent;//等待客户端连接,非阻塞操作,函数立即返回if (!ConnectNamedPipe(m_hNamedPipe, &ovlpd)){if (ERROR_IO_PENDING != GetLastError()){CloseHandle(m_hNamedPipe);CloseHandle(hEvent);cout << "等待客户端连接失败 ..." << endl << endl;return false;}}//等待事件 hEvent 失败if (WAIT_FAILED == WaitForSingleObject(hEvent, INFINITE)){CloseHandle(m_hNamedPipe);CloseHandle(hEvent);cout << "等待对象失败 ..." << endl << endl;return false;}CloseHandle(hEvent);return true;}bool my_SendData(std::string data)//发送数据{DWORD m_DData;if (!WriteFile(m_hNamedPipe, data.c_str(), data.length(), &m_DData, NULL)){cout << "server send data faild" << endl;return false;}return true;}

2.2 客户端

#include "stdafx.h"#include <iostream>#include <stdlib.h>#include <stdio.h>#include <windows.h>using std::cout;using std::endl;HANDLE m_hNamedPipe;LPCTSTR PipeName(_T("\\\\.\\pipe\\my_test"));bool my_OpenPipe();//打开管道bool my_SendData(std::string data);//发送数据DWORD WINAPI Pipe_Listen(LPVOID lpParameter);//获取管道数据线程int _tmain(int argc, _TCHAR* argv[]){system("color f0");if(!my_OpenPipe()) return 0;TCHAR data[4096] = { 0 };DWORD data_read;while (true){memset(data, 0, sizeof(data));if (!ReadFile(m_hNamedPipe, data, 4096, &data_read, NULL)){}elsecout << "client say:" << data << endl;//Sleep(200);  }CloseHandle(m_hNamedPipe);system("pause");return 0;}bool my_OpenPipe()//打开管道{//通过管道名称,等待连接命名管道,设置为一直等待if (!WaitNamedPipe(_T("\\\\.\\pipe\\my_test"), NMPWAIT_WAIT_FOREVER)){cout << "命名管道实例不存在 ..." << endl << endl;return false;}//根据命名管道的名称,打开命名管道m_hNamedPipe = CreateFile(_T("\\\\.\\pipe\\my_test"), GENERIC_READ | GENERIC_WRITE,0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (INVALID_HANDLE_VALUE == m_hNamedPipe){cout << "打开命名管道失败 ..." << endl << endl;return false;}return true;}bool my_SendData(std::string data)//发送数据{DWORD m_DData;if (!WriteFile(m_hNamedPipe, data.c_str(), data.length(), &m_DData, NULL)){cout << "server send data faild" << endl;return true;}return true;}

2.3 结果


原创粉丝点击