匿名管道 与 命名管道

参考一

管道(PIPE)实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机.一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读出来.管道分为两种:匿名管道和命名管道.

匿名管道是在父进程和子进程间单向传输数据的一种未命名管道,只能在本地计算机中使用,而不能用于网络间的通信.

匿名通道由CreatePipe()函数创建,该函数在创建匿名管道的同时返回两个句柄:读句柄和写句柄.其原型如下:

BOOL CreatePipe(

   PHANDLE hReadPipe,    //指向读句柄的指针

   PHANDLE hWritePipe,   //指向写句柄的指针

   LPSECURITY_ATTRIBUTES lpPipeAttributes,    //指向安全属性的指针

   DWORD nSize    //管道大小,若为0则由系统决定

};

匿名管道不支持异步读写操作.

命名管道是在管道是在管道服务器和一台或多台管道客户机之间进行单向或双向通信的一种命名的管道.一个命名管道的所有实例共享同一个管道名,但是每一个实例均拥有独立的缓存和句柄,并且为客户-服务通信提供一个分离的管道.

命名管道可以在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程间进行有连接的可靠数据通信,如果连接中断,连接双方都能立即收到连接断开的信息。

每一个命名管道都有一个唯一的名字,以区分存在于系统的命名对象列表中的其他命名管道.管道服务器在调用CreateNamedPipe()函数创建命名管道的一个或多个实例时为其指定了名称.对于管道客户机,则是在调用CreateFile()或CallNamedPipe()函数以连接一个命名管道实例时对管道名进行指定.命名管道对其标识采用UNC格式:

\\Server\\Pipe\[Path]Name

其中,第一部分\\Server指定了服务器的名字,命名管道服务就在此服务器创建,其字串部分可以为一个小数点(表示本机)、星号(当前网络字段)、域名或是一个真正的服务;第二部分是一个不可变化的的硬编码字串;第三部分\[Path]Name则使应用程序可以唯一定义及标识一个命名管道的名字,而且可以设置多级目录.

管道服务器首次调用CreateNamedPipe()函数时,使用nMaxInstance参数指定了能同时存在的管道实例的最大数目.服务器可以重复调用CreateNamedPipe()函数去创建管道新的实例,直至达到设定的最大实例数.下面给出CreateNamedPipe()的函数原型:

HANDLE CreateNamedPipe(

   LPCTSTR lpName,    //指向管道名称的指针

   DWORD dwOpenMode,  //管道打开模式

   DWORD dwPipeMode,  //管道模式

   DWORD nMaxInstance,    //最大实例数

   DWORD nOutBufferSize,  //输出缓存大小

   DWORD nInBufferSize,   //输入缓存大小

   DWORD nDefaultTimeOut,    //超时设置

   LPSECURITY_ATTRIBUTES lpSecurityAttributes    //安全属性指针

};

其中,dwOpenMode参数用来指示管道在创建好之后,它的传输方向、I/O控制以及安全模式。

 

命名管道:可用于网络通信;可通过名称引用;支持多客户端连接;支持双向通信;支持异步重叠I/O;
匿名管道:只能本地使用。

 

参考二

操作系统中负责线程间通讯的东西叫管道

管道(pipe)是进程用来通讯的共享内存区域。一个进程往管道中写入信息,而其它的进程可以从管道中读出信息。如其名,管道是进程间数据交流的通道。邮路(Mailslots)的功能与管道类似,也是进程间通讯(interprocess communications,IPC)的媒介,只不过其具体实现方式与管道有些差别。一个基于Win32的应用程序可以在邮路中储存消息,这些消息通常通过网络发往一个指定的计算机或某域名(域是共享一个组名的一组工作站或服务器。)下的所有计算机。你也可以使用命名管道代替邮路来进行进程间通信。命名管道最适合用来两个进程间的消息传递,邮路则更适合一个进程向多个进程广播消息。邮路具有一个重要的特点,它使用数据包广播消息。广播(broadcast)是网络传输中使用的术语,它意味着接收方收到数据后不发送确认消息通知发送方。而管道(这里的管道指命名管道,有关命名管道以下详解。)则不同,它更类似于打电话,你只对一个当事人说话,但是你却非常清楚你的话都被对方听到。邮路和管道一样,也是一个虚拟文件,它保存在内存中,但是你却必须使用普通的Win32文件函数访问它,比如CreateFile、ReadFile、WriteFile等。邮路中储存的数据可以是任何形式的,唯一的要求是不得超过64K。与磁盘文件不同的是,邮路是一个临时的对象,当某个邮路所有的句柄都关闭的时候,该邮路及其中的数据就被删除。

管道的类型有两种:匿名管道和命名管道。匿名管道是不命名的,它最初用于在本地系统中父进程与它启动的子进程之间的通信。命名管道更高级,它由一个名字来标识,以使客户端和服务端应用程序可以通过它进行彼此通信。而且,Win32命名管道甚至可以在不同系统的进程间使用,这使它成为许多客户/服务器应用程序的理想之选。

就像水管连接两个地方并输送水一样,软件的管道连接两个进程并输送数据。一个一个管道一旦被建立,它就可以象文件一样被访问,并且可以使用许多与文件操作同样的函数。可以使用CreateFile函数获取一个已打开的管道的句柄,或者由另一个进程提供一个句柄。使用WriteFile函数向管道写入数据,之后这些数据可以被另外的进程用ReadFile函数读取。管道是系统对象,因此管道的句柄在不需要时必须使用CloseHandle函数关闭。

匿名管道只能单向传送数据,而命名管道可以双向传送。管道可以以比特流形式传送任意数量的数据。命名管道还可以将数据集合到称为消息的数据块中。命名管道甚至具有通过网络连接多进程的能力。但遗憾的是Windows9X不支持创建命名管道,它只能在WindowsNT系列(如Windows NT,Windows 2000,Windows XP)的操作系统上创建。

当讨论管道时,通常涉及到两个进程:客户进程和服务进程。服务进程负责创建管道。客户进程连接到管道。服务进程可以创建一个管道的多个实例,以此支持多个客户进程。

 

参考三

一、概述

  管道(Pipe)实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读取出来。匿名管道(Anonymous Pipes)是在父进程和子进程间单向传输数据的一种没有名字的管道,只能在本地计算机中使用,而不可用于网络间的通信。

 

二、匿名管道

  匿名管道由CreatePipe()函数创建,该函数在创建匿名管道的同时返回两个句柄:管道读句柄和管道写句柄。CreatePipe()的函数原型为: 
 
BOOL CreatePipe(PHANDLE hReadPipe,                      // 指向读句柄的指针
  PHANDLE hWritePipe,                     // 指向写句柄的指针
  LPSECURITY_ATTRIBUTES lpPipeAttributes, // 指向安全属性的指针
  DWORD nSize                             // 管道大小
  );

如果函数成功返回TRUE,否则返回FALSE,可以使用GetLastError()得到错误值。
其中,第3个参数的定义为:

typedef struct _SECURITY_ATTRIBUTES{
    DWORD  nLength;               // 本结构的字节数
    LPVOID lpSecurityDescriptor;  // 安全描述符地址
    BOOL   bInheritHandle;        // 是否可以被子进程继承
}SECURITY_ATTRIBUTES;

这个结构包含了一个对象的安全描述信息,并且指定了管道是否可以被自进程继承。

  通过hReadPipe和hWritePipe所指向的句柄可分别以只读、只写的方式去访问管道。但是在同一个进程中去读写管道是没有意义的,我们常常需要的是在父子进程中传递数据,也就是父写数据,子读数据,或者子写数据,子读数据。我们这里仅讨论后一种情况,一个典型是示例就是一个有窗口的程序调用控制台程序,把控制台的输出信息在父窗口中输出。

    当父进程执行CreateProcess()启动子进程时,系统会检查父进程内可以继承的内核对象句柄,复制到子进程空间,这样子进程就有了和父进程一样的匿名管道句柄,子进程对管道的写端放入数据,父进程就可以从读端取到数据。同样,父进程在写端放入数据,子进程也可以从读端取出数据。也就是说,一个匿名管道同时拥有了两个写端和读端。当父子进程任何一个关闭的时候,无论时候显式的关闭读写句柄,系统都会帮进程关闭所拥有的管道句柄。正常情况下,控制台进程的输输入出是在控制台窗口的,但是如果我们在创建子进程的时候指定了其输入输出,那么子进程就会从我们的管道读数据,把输出数据写到我们指定的管道。CreateProcess()定义如下:

BOOL CreateProcess(
    LPCTSTR  lpApplicationName,                 // 执行程序的名字
    LPTSTR  lpCommandLine,                 // 命令行字符串
    LPSECURITY_ATTRIBUTES  lpProcessAttributes, // 进程的安全属性的指针
    LPSECURITY_ATTRIBUTES  lpThreadAttributes, // 主线程安全属性指针
    BOOL  bInheritHandles,                 // 是否继承父进程句柄的标志
    DWORD  dwCreationFlags,                 // 创建时候的标志位
    LPVOID  lpEnvironment,                 // 环境变量块指针
    LPCTSTR  lpCurrentDirectory,         // 指定工作目录的字符串指针
    LPSTARTUPINFO  lpStartupInfo,         // 启动信息指针
    LPPROCESS_INFORMATION  lpProcessInformation // 进程信息指针
   );

    其中我们关心的启动信息结构,其定义为:
typedef struct _STARTUPINFO {    // si
    DWORD   cb;                  // 本结构体字节数,用于版本控制
    LPTSTR  lpReserved;
    LPTSTR  lpDesktop;
    LPTSTR  lpTitle;
    DWORD   dwX;
    DWORD   dwY;
    DWORD   dwXSize;
    DWORD   dwYSize;
    DWORD   dwXCountChars;
    DWORD   dwYCountChars;
    DWORD   dwFillAttribute;
    DWORD   dwFlags;
    WORD    wShowWindow;
    WORD    cbReserved2;
    LPBYTE  lpReserved2;
    HANDLE  hStdInput;          // 输入句柄
    HANDLE  hStdOutput;         // 输出句柄
    HANDLE  hStdError;          // 错误显示句柄
} STARTUPINFO, *LPSTARTUPINFO; 

    在创建子进程时候,指定了子进程的输出管道,我们在管道另一端读取就可以了。那么存在一个问题,我们什么时候能知道子进程结束了,或者说不再写数据了呢?ReadFile()函数会阻塞到读到数据或者出错的后才会返回,也就是说当管道的所有写端都关闭的时候,读会出错,能够使函数在一个循环中返回,那么,我们应该在创建子进程后立即关闭父进程所拥有的写句柄,那么当子进程结束时候,读到0字节返回。

  同样的道理,在用WriteFile()函数向管道写入数据时,只有在向管道写完指定字节的数据后或是在有错误发生时函数才会返回。如管道缓冲已满而数据还没有写完,WriteFile()将要等到另一进程对管道中数据读取以释放出更多可用空间后才能够返回。管道服务器在调用CreatePipe()创建管道时以参数nSize对管道的缓冲大小作了设定。 匿名管道并不支持异步读、写操作,这也就意味着不能在匿名管道中使用ReadFileEx()和WriteFileEx(),而且ReadFile()和WriteFile()中的lpOverLapped参数也将被忽略。


三、命名管道

命名管道作为一种通信方法,有其独特的优越性,这主要表现在它不完全依赖于某一种协议,而是适用于任何协议——只要能够实现通信。
  命名管道具有很好的使用灵活性,表现在:
  1) 既可用于本地,又可用于网络。
  2) 可以通过它的名称而被引用。
  3) 支持多客户机连接。
  4) 支持双向通信。
  5) 支持异步重叠I/O操作。

 

实例代码

 

客户端源码

#include 
#include 
using namespace std;

const TCHAR szPipeName[] = "\\\\.\\pipe\\potok";

int main(void)
{
	HANDLE hPipe = CreateFile(szPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	if (hPipe == INVALID_HANDLE_VALUE)
	{
		printf("CreateFile return [%d]!\n", GetLastError());
		return -1;
	}

	DWORD dwRead, dwWrite;
	char szBuf[1024] = {0};

	for (int i = 0; i < 10; ++i)
	{
		sprintf(szBuf, "%d", i);
		WriteFile(hPipe, szBuf, strlen(szBuf), &dwWrite, 0);
		printf("Send: %s\n", szBuf);
		memset(szBuf, 0, sizeof(szBuf));
		ReadFile(hPipe, szBuf, sizeof(szBuf), &dwRead, 0);
		printf("Recv: %s\n", szBuf);
	}

	system("pause");
	return 0;
}



 

服务器端源码

#include 
#include 
using namespace std;

int main(void)
{
	TCHAR strPipeName[] = "\\\\.\\pipe\\potok";
	PSECURITY_DESCRIPTOR psd;
	psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
	if (!InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION))
	{
		LocalFree((HLOCAL)psd);
		return -1;
	}
	if (!SetSecurityDescriptorDacl(psd, TRUE, (PACL)NULL, FALSE))
	{
		LocalFree((HLOCAL)psd);
		return -1;
	}

	SECURITY_ATTRIBUTES saAttr;
	saAttr.nLength =sizeof(SECURITY_ATTRIBUTES);
	saAttr.lpSecurityDescriptor = psd;
	saAttr.bInheritHandle = TRUE;
	HANDLE hIPC = CreateNamedPipe(strPipeName, 
				  PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 
				  PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 
				  1, 0, 0, 10000, &saAttr);
	if (hIPC == INVALID_HANDLE_VALUE)
	{
		printf("CreateNamedPipe return [%d]!\n", GetLastError());
		return -1;
	}

	char szBuf[1024] = {0};
	DWORD dwRead, dwWrite;
	char szWrite[] = "Get You\n";
	ConnectNamedPipe(hIPC, NULL);
	while(1)
	{
		if (!ReadFile(hIPC, szBuf, sizeof(szBuf), &dwRead, 0))
		{
			break;
		}
		printf("%s\n", szBuf);
		memset(szBuf, 0, sizeof(szBuf));

		if (!WriteFile(hIPC, szWrite, strlen(szWrite), &dwWrite, NULL))
		{
			break;
		}
	}

	return 0;
}; 



 

你可能感兴趣的:(Windows底层,匿名管道,命名管道)