上一篇《进程通信之二管道技术第二篇匿名管道》中讲解了匿名管道,匿名管道有读取端和写入端,在创建匿名管道(CreatePipe)后就可以像读写文件一样的对管道中进行读写(ReadFile与WriteFile,注意读写顺序)。在关闭匿名管道两端后会由系统负责销毁并回收资源。文章中还示范了父进程如何使用匿名管道来改变子进程的输入输出。
本篇将讲解管道技术中的命名管道(Named Pipes),顾名思义,这个管道肯定是有名字的,联想到秒杀多线程面试题中的事件、互斥量、信号量(见附1),它们的名字主要是用于确保多个进程访问同一个对象。因此肯定也可以通过管道的名字来确保多个进程访问同一个管道。事实上,命名管道不仅可在同一台计算机的不同进程之间传输数据,甚至能在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。
先来看看如何创建和使用命名管道。
第一个CreateNamedPipe
函数功能:创建命名管道
函数原型:
HANDLEWINAPICreateNamedPipe(
LPCTSTRlpName,
DWORDdwOpenMode,
DWORDdwPipeMode,
DWORDnMaxInstances,
DWORDnOutBufferSize,
DWORDnInBufferSize,
DWORDnDefaultTimeOut,
LPSECURITY_ATTRIBUTESlpSecurityAttributes
);
参数说明:
第一个参数LPCTSTRlpName
表示管道名称,采用的形式是:\\.\pipe\pipename。最多可达256个字符的长度,而且不区分大小写。如果已经有同名管道,则会创建那个管道的一个新实例。
第二个参数DWORDdwOpenMode
表示管道的打开方式。下面列出最常用的三种,更多请参阅MSDN。
1.PIPE_ACCESS_DUPLEX
该管道是双向的,服务器和客户端进程都可以从管道读取或者向管道写入数据。
2.PIPE_ACCESS_INBOUND
该管道中数据是从客户端流向服务端,即客户端只能写,服务端只能读。
3.PIPE_ACCESS_OUTBOUND
该管道中数据是从服务端流向客户端,即客户端只能读,服务端只能写。
第三个参数DWORDdwPipeMode
表示管道的模式,下面是一些常用模式介绍,更多请参阅MSDN。
1.PIPE_TYPE_BYTE
数据作为一个连续的字节数据流写入管道。
2.PIPE_TYPE_MESSAGE
数据用数据块(名为“消息”或“报文”)的形式写入管道。
3.PIPE_READMODE_BYTE
数据以单独字节的形式从管道中读出。
4.PIPE_READMODE_MESSAGE
数据以名为“消息”的数据块形式从管道中读出(要求指定PIPE_TYPE_MESSAGE)。
5.PIPE_WAIT
同步操作在等待的时候挂起线程。
6.PIPE_NOWAIT
同步操作立即返回。
第四个参数DWORDnMaxInstances
表示该管道所能够创建的最大实例数量。必须是1到常数PIPE_UNLIMITED_INSTANCES间的一个值。
在WINBASE.H中有#define PIPE_UNLIMITED_INSTANCES 255
第五个参数DWORDnOutBufferSize
表示管道的输出缓冲区容量,为0表示使用默认大小。
第六个参数DWORDnInBufferSize
表示管道的输入缓冲区容量,为0表示使用默认大小。
第七个参数DWORDnDefaultTimeOut
表示管道的默认等待超时。
第八个参数LPSECURITY_ATTRIBUTESlpSecurityAttributes
表示管道的安全属性。
函数返回值:
函数执行成功返回命名管道的句柄,否则返回INVALID_HANDLE_VALUE。
第二个ConnectNamedPipe
函数功能:等待客户端连接命名管道
函数原型:
BOOLWINAPIConnectNamedPipe(
HANDLEhNamedPipe,
LPOVERLAPPEDlpOverlapped
);
函数说明:
第一个参数表示命名管道的句柄。
第二个参数是一个指向OVERLAPPED结构的指针,一般置为NULL就可以了。
第三个WaitNamedPipe
函数功能:客户端连接命名管道
函数原型:
BOOLWINAPIWaitNamedPipe(
LPCTSTRlpNamedPipeName,
DWORDnTimeOut
);
函数说明:
第一个参数LPCTSTRlpNamedPipeName
表示管道名称,采用的形式是:\\servername\pipe\pipename。如果是本机管道,servername用“.”来表示。
第二个参数DWORDnTimeOut
表示等待命名管道的一个实例有效的超时时间,单位毫秒。也可以用NMPWAIT_USE_DEFAULT_WAIT表示使用命名管道的设定值(在调用CreateNamedPipe创建命名管道时指定的),NMPWAIT_WAIT_FOREVER表示无限等待。
函数返回值:
在指定时间内连接成功返回TRUE,否则返回FALSE。
注意
1:如果指定名称的命名管道还没创建,函数立即返回,返回值为FALSE。
2:如果函数执行成功返回TRUE,表示至少有一个命名管道的实例有效,接下来应该使用CreateFile函数打开命名管道的一个句柄,但是CreateFile可能会打开管道失败,因为该实例有可能被服务端关闭或被已经被其他客户端打开。
下面给出使用命名管道的实例,该实例分为命名管道的服务端和客户端。服务端和客户端的主要步骤如下所示:
1. 服务端用CreateNamedPipe创建一个命名管道并使用ConnectNamedPipe等待客户端的连接。
2. 客户端使用WaitNamedPipe连接成功后,用CreateFile打开管道并使用WriteFile向管道中写入一段数据(即向服务端发送消息)。
3. 服务端使用ReadFile从管道中读取数据后(即收到消息)再向管道中写入确认信息表明已经收到客户端传输的数据(即通知客户端已收到)。
4. 客户端收到确认信息后结束,调用CloseHandle关闭管道(该管道是CreateFile打开的)。
5.服务端使用DisconnectNamedPipe和CloseHandle关闭管道。
代码中的CreateFile,WriteFile,ReadFile请参见上一篇《进程通信之二管道技术第二篇匿名管道》中的介绍。
服务端代码如下:
#include
#include
#include
const char *pStrPipeName = "\\\\.\\pipe\\NamePipe_MoreWindows";
int main()
{
printf(" 命名管道 服务器\n");
printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
printf("创建命名管道并等待连接\n");
HANDLE hPipe = CreateNamedPipe(pStrPipeName, PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_WAIT_FOREVER, 0);
if (ConnectNamedPipe(hPipe, NULL) != NULL)//等待连接。
{
printf("连接成功,开始接收数据\n");
const int BUFFER_MAX_LEN = 256;
char szBuffer[BUFFER_MAX_LEN];
DWORD dwLen;
//接收客户端发送的数据
ReadFile(hPipe, szBuffer, BUFFER_MAX_LEN, &dwLen, NULL);//读取管道中的内容(管道是一种特殊的文件)
printf("接收到数据长度为%d字节\n", dwLen);
printf("具体数据内容如下:%s\n", szBuffer);
//确认已收到数据
printf("向客户端发送已经收到标志\n");
strcpy(szBuffer, "服务器已经收到");
WriteFile(hPipe, szBuffer, strlen(szBuffer) + 1, &dwLen, NULL);
}
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);//关闭管道
return 0;
}
客户端代码如下:
#include
#include
#include
const char *pStrPipeName = "\\\\.\\pipe\\NamePipe_MoreWindows";
int main()
{
printf(" 命名管道 客户端\n");
printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
printf("按任意键以开始连接命名管道\n");
getch();
printf("开始等待命名管道\n");
if (WaitNamedPipe(pStrPipeName, NMPWAIT_WAIT_FOREVER) == FALSE)
{
printf("Error! 连接命名管道失败\n");
return 0;
}
printf("打开命名管道\n");
HANDLE hPipe = CreateFile(pStrPipeName, GENERIC_READ | GENERIC_WRITE, 0,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
printf("向服务端发送数据\n");
const int BUFFER_MAX_LEN = 256;
char szBuffer[BUFFER_MAX_LEN];
DWORD dwLen = 0;
//向服务端发送数据
sprintf(szBuffer,"进程%d说\"%s\"", GetCurrentProcessId(), "Hello World!");
WriteFile(hPipe, szBuffer, strlen(szBuffer) + 1, &dwLen, NULL);
printf("数据写入完毕共%d字节\n", dwLen);
//接收服务端发回的数据
ReadFile(hPipe, szBuffer, BUFFER_MAX_LEN, &dwLen, NULL);//读取管道中的内容(管道是一种特殊的文件)
printf("接收服务端发来的确认信息长度为%d字节\n", dwLen);
printf("具体数据内容如下:%s\n", szBuffer);
CloseHandle(hPipe);
return 0;
}
运行结果如下所示,运行时先启动服务器,然后再运行客户端:
命名管道就先介绍到这里了,管道技术上、中、下三篇到此也就全部结束下,下面给出目录,方便大家查看。
《进程通信之二管道技术第一篇输入输出的重定向》
《进程通信之二管道技术第二篇匿名管道》
《进程通信之二管道技术第三篇命名管道》
后面将有文章介绍进程通信中最底层,最高效的方法——共享内存。欢迎继续浏览。
附1 其实进程线程同步除了使用秒杀多线程面试题系列中的介绍的关键段、事件、互斥量、信号量、读写锁。管道也可以用于线程的同步。
转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/8260087
Windows编程系列文章地址:http://blog.csdn.net/morewindows/article/category/862060
欢迎关注新浪微博:http://weibo.com/MoreWindows
---------------------------------------------------华丽的分割线-----------------------------------------------------------------
CSDN博客之星评选活动正在进行,觉得本博客对您有帮助,麻烦投我一票,非常感谢。您的支持就是我写作的最大动力。
投票地址:http://vote.blog.csdn.net/item/blogstar/MoreWindows
谢谢大家!