命名管道编程的原理及实现

概述

管道(Pipe)实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。命名管道(NamedPipes)是在管道服务器和一台或多台管道客户机之间进行单向或双向通信的一种命名的管道。一个命名管道的所有实例共享同一个管道名,但是每一个实例均拥有独立的缓存与句柄,并且为客户——服务通信提供有一个分离的管道。实例的使用保证了多个管道客户能够在同一时间使用同一个命名管道。

Microsoft Windows NT、Windows 2000、Windows 95以及Windows98均提供对命名管道的支持(不包括Windows CE),但只有WindowsNT和Windows2000才支持服务器端的命名管道技术。命名管道可以在同一台计算机的不同进程之间,或在跨越一个网络的不同计算机的不同进程之间进行有连接的可靠数据通信,如果连接中断,连接双方都能立即收到连接断开的信息。命令管道是围绕Windows文件系统而设计的一种机制,采用的是命名管道文件系统(NamedPipe File System,NPFS)接口。对数据的收发也采用文件读写函数ReadFile()和WriteFile()来完成。在设计上,由于命名管道也利用了微软网络提供者(MSNP)重定向器,因此无需涉及底层的通信协议细节。命名管道还充分利用了WindowsNT及Windows 2000内建的安全特性,通信的安全性相对较好。

命名规范及通信模式

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

\\Server\Pipe\[Path]Name

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

命名管道提供了两种基本的通信模式:字节模式和消息模式。可在CreateNamePipe()创建命名管道时分别用PIPE_TYPE_BYTE和PIPE_TYPE_MESSAGE标志进行设定。在字节模式中,信息以连续字节流的形式在客户与服务器之间流动。这也就意味着,对于客户机应用和服务器应用,在任何一个特定的时间段内,都无法准确知道有多少字节从管道中读出或写入。在这种通信模式中,一方在向管道写入某个数量的字节后,并不能保证管道另一方能读出等量的字节。对于消息模式,客户机和服务器则是通过一系列不连续的数据包进行数据的收发。从管道发出的每一条消息都必须作为一条完整的消息读入。

使用命名管道

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

HANDLE CreateNamedPipe(

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

 DWORD dwOpenMode, // 管道打开模式

 DWORD dwPipeMode, // 管道模式

 DWORD nMaxInstances, // 最大实例数

 DWORD nOutBufferSize, // 输出缓存大小

 DWORD nInBufferSize, // 输入缓存大小

 DWORD nDefaultTimeOut, // 超时设置

 LPSECURITY_ATTRIBUTES lpSecurityAttributes // 安全属性指针

);

如果在已定义超时值变为零以前,有一个实例管道可以使用,则创建成功并返回管道句柄,以此侦听来自客户机的连接请求。另一方面,客户机通过函数WaitNamedPipe()使服务器进程等待来自客户的实例连接。如果在超时值变为零以前,有一个管道可供连接使用,则函数将成功返回,并通过调用CreateFile()或CallNamedPipe()来呼叫对服务器的连接。此时服务器将接受客户的连接请求,成功建立连接,服务器调用的等待客户机建立连接的ConnectNamedPipe()函数也将成功返回。

从调用时序上看,首先是客户机通过WaitNamedPipe()使服务器的CreateFile()在限时时间内创建实例成功,然后双方通过ConnectNamedPipe()和CreateFile()成功连接,在返回用以通信的文件句柄后,客户、服务双方即可进行通信。

在建立了连接后,客户机与服务器即可通过ReadFile()和WriteFile()并利用得到的管道句柄,以文件读写的形式彼此间进行信息交换。当客户与服务器的通信结束,或是由于某种原因一方需要断开时,由客户机调用CloseFile()函数关闭打开的管道句柄,服务器随即调用DisconnectNamedPipe()函数。当然,服务器也可以通过单方面调用DisconnectNamedPipe()来终止连接。在终止连接后调用函数CloseHandle()来关闭此管道。下面给出的程序清单即是按照上述方法实现的命名管道服务器和客户机进行通信的简单程序实现代码:

服务器端:

m_hPipe = CreateNamedPipe("\\\\.\\Pipe\\Test", PIPE_ACCESS_DUPLEX, 
  PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 1000, NULL); // 创建命名管道

if (m_hPipe == INVALID_HANDLE_VALUE)

 m_sMessage = "创建命名管道失败!";

else{

 m_sMessage = "成功创建命名管道!";

 AfxBeginThread(ReadProc, this); // 开启线程

}

由于ConnectNamedPipe()函数在没有客户机连接到服务器时会无限等待下去,因此为避免由此引起主线程的阻塞,为其开辟了一个子线程ReadProc:

UINT ReadProc(LPVOID lpVoid)

{

 char buffer[1024]; // 数据缓存

 DWORD ReadNum;

 CServerView* pView = (CServerView*)lpVoid; // 获取视句柄

 if (ConnectNamedPipe(pView->m_hPipe, NULL) == FALSE) // 等待客户机的连接

 {

CloseHandle(pView->m_hPipe); // 关闭管道句柄

pView->m_sMessage = "与客户机建立连接失败!"; // 显示信息

pView->Invalidate();

return 0;

 }else{

pView->m_sMessage = "与客户机建立连接!"; // 显示信息

pView->Invalidate();

 }

 // 从管道读取数据

 if (ReadFile(pView->m_hPipe, buffer, sizeof(buffer), &ReadNum, NULL) == FALSE)

 {

CloseHandle(pView->m_hPipe); // 关闭管道句柄

pView->m_sMessage = "从管道读取数据失败!"; // 显示信息

pView->Invalidate();

 } else {

buffer[ReadNum] = '\0'; // 显示接收到的信息

pView->m_sMessage = CString(buffer);

pView->Invalidate();

 }

 return 1;

}

在客户同服务器建立连接后,ConnectNamedPipe()才会返回,其下语句才得以执行。随后的ReadFile()将负责把客户写入管道的数据读取出来。在全部操作完成后,服务器可以通过调用函数DisconnectNamedPipe()而终止连接:

if (DisconnectNamedPipe(m_hPipe) == FALSE) // 终止连接

 m_sMessage = "终止连接失败!";

else

{

 CloseHandle(m_hPipe); // 关闭管道句柄

 m_sMessage = "成功终止连接!";

}

客户机端:

CString Message = "[测试数据,由客户机发出]"; // 要发送的数据

DWORD WriteNum; // 发送的是数据长度

// 等待与服务器的连接

if (WaitNamedPipe("\\\\.\\Pipe\\Test", NMPWAIT_WAIT_FOREVER) == FALSE)

{

 m_sMessage = "等待连接失败!"; // 显示信息

 Invalidate();

 return;

}

// 打开已创建的管道句柄

HANDLE hPipe = CreateFile("\\\\.\\Pipe\\Test", GENERIC_READ | GENERIC_WRITE, 
  0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

if (hPipe == INVALID_HANDLE_VALUE)

{

 m_sMessage = "管道打开失败!"; // 显示信息

 Invalidate();

 return;

} else {

 m_sMessage = "成功打开管道!"; // 显示信息

 Invalidate();

}

// 向管道写入数据

if (WriteFile(hPipe, Message, Message.GetLength(), 
  &WriteNum, NULL) == FALSE)

{

 m_sMessage = "数据写入管道失败!"; // 显示信息

 Invalidate();

} else {

 m_sMessage = "数据成功写入管道!"; // 显示信息

 Invalidate();

}

CloseHandle(hPipe); // 关闭管道句柄

这篇文章将会介绍现有的Win32函数支持的网络通信功能,并且展示了如何在你自己的应用中使用它们。在API中,有两个支持网络通信的便利方法:mailslot和命名管道(namepipes)。这篇文章将分别讨论它们,并且分别介绍它们的优缺点。

   因为Win32API直接支持网络通信,因此要创建各种使用网络的应用是特别简单的。例如,你要在你的网络中建立一个多用户会议系统,与BBS的“CB”类似。在这个系统中,用户在各自的机器上运行一个会议系统程序,他们所打的全部信息都会广播给同一网络的所有其他用户。通常这种系统可使用mailslots实现,因为mailslots很容易广播信息。事实上,多人的网络游戏使用的也是类似的技术。

  当你要在两部机器间传送大量的数据流时,你通常使用点对点的命名管道连接。例如,你会使用命名管道来实现一个网络数字电话或者视频系统。客户/服务器也都是使用命名管道的。其中的一个中央机器会作为服务器端,然后其它所有的客户端就可使用命名管道与它分别进行连接。

  网络基础

  为了进行更好地理解下面的例子,懂得一些网络的基本知识是很有必要的。下面的图展示了一个小型公司中常见的简单网络。每台机器都使用一个网络适配器连接到网络中,并且都拥有一个唯一的名字来标识它。网络适配器决定了网络的类型,常见的有以太网或者令牌网。适配器还决定了网络使用的媒体,可以是同轴线、双绞线等。要知道的一点是,在这样的简单网络中,所有的机器都可以平等地与其它的机器进行通信。

命名管道编程的原理及实现(2)

*******************图一*************************

   通过传统的Win32API函数在机器间通信有两种方法。一种是mailslot,一个机器可以广播一个信息,网络中的其它所有机器都可以接收到。使用命名管道的话,一部机器选择另一部进行通信,并且与它建立一个特别的连接。命名管道的好处是连接可靠。如果连接打断的话,例如一块网卡或者网线出现故障,连接的两端都可以马上接收到连接断开的信息。mailslots是不可靠的,因为发送者无法确认接收者是否已经收到了信息。mailslot的好处是它可以很容易地同时给许多机器发信息。

  上面的图展示的是一个网段。一个网段的定义是直接互相连接的一组机器。一个网段中的机器数目是受到限制的,因为当机器的数量增加时,网络的通信量也会增大。通常的限制是大约为100台机器。在一个大的公司中,每个网段大约包括有20到30台机器。所有的网段之间可以通过路由器进行连接,这样它们之间就可以进行通信,如下图所示。了解到这种差别是重要的,因为通常一个mailslot信息只能在一个网段中传送,而命名管道的信息可以经过路由器传送到另一个网段中。

命名管道编程的原理及实现(2)

******************图二***********************

  使用mailslots和命名管道来进行网络通信,有三种不同的通信方式:广播、点对点和客户/服务器方式。将mailslot应用在广播模式时,一台机器发送信息到网段上的其它所有机器上。在点对点通信时,一台机器与另一台建立一个特别的连接,数据可以通过命名管道在它们之间往复传送。在一个客户/服务器的关系中,一台机器作为服务器,所有机器通过点对点的命名管道来与它进行连接。如果要使用客户/服务器的方式来模拟一个广播的操作,可以通过一台机器发送一个信息到服务器,然后服务器就可以将信息的副本分别发到每个客户端。

  mailslot连接

  使用Win32API来进行通信时,Mailslots是最简单的方式。在同一网段中,Mailslots提供了一条单向的通信路径,将信息由一个发送者传送到一个或者多个接收者中。如果你想同时发送数据到许多接收者时,可以考虑使用mailslot的方式。

  要创建mailslots是非常简单的,读取和写入都是通过API常见的ReadFile和WriteFile函数进行的。在创建mailslot时,一个特别的路径名会传送到CreateMailslot函数中,可让系统知道要创建一个mailslot而不是一个通常的文件。(这里提到的函数,你都可以从Win32编程者的参考指南、SDK或者Visual C++V2的Win32帮助文件中找到)

  文章最后有一些程序的列表,在列表1和2中的程序已经尽量地简化,从中你可以很容易地了解到通过一个mailslot来传送和接收数据的必要步骤。列表1展示了如何通过CreateMailslto函数来创建一个mailslot服务器,服务器保存有接收信息的一个队列,直到你使用ReadFile函数来读取它们。存储在队列中的信息以它们到达的顺序排列。

  mailslot的名字必须以\\.\mailslot\[path]name的形式排列。这看来象一个文件名,事实上,它的ReadFile函数操作也与一个文件类似。有点不同的是,该函数并不会创建任何真正的文件:mailslot保存在内存中。在列表1中使用的mailslot名字是:"\\.\mailslot\sms"。为了进一步对mailslot进行分类,你可以在路径中加入“子目录”。

  在你创建mailslot时,你可以指定信息的最大长度,以及读取的超时时间。在网络上,mailslots在一个信息(message)中可传送不超过400字节的信息。如果你将超时的值设置为0,那么不管缓冲中是否有信息,对ReadFile的任何调用都会马上返回。如果你将超时时间设置为一个特定值(以毫秒计),若在定义时间内都没有任何信息到达,读取的操作将会失败。你还可以使用MAILSLOT_WAIT_FOREVER常数来创建一个阻塞的读取。

  列表1使用的是无阻塞的方式,并且使用GetMailslotInfo函数来确保在进行一个读取操作前,mailslot队列中有信息存在。该函数返回队列中信息的最大长度、下一信息的长度和等待的信息数目。程序不断地检测mailslot中是否有信息存在,如果有的话,它就读取第一个,从mailslot中读取信息的操作与读取一个文件类似。

  在网络上的任何计算机,如果要发送信息给一台运行列表1程序的机器,需要提供发送者和接收者的mailslot名字。列表2展示了如何发送信息给一个mailslot。它首先通过常用的CreateFile函数来打开一个到mailslot的可写连接。该程序作为一个mailslot客户端使用,因为它写信息到已经在网络上运行的mailslot服务器上。CreateFile函数通过检查mailslot文件的名字,就知道不是要创建一个文件,而是要与一个mailslot通信。文件名可有4种不同的格式:

\\.\mailslot\[path]name
\\*\mailslot\[path]name
\\domain\mailslot\[path]name
\\machine\mailslot\[path]name

上面的例子中,这些名字分别指定了网络上的本地机器或者某台特定的机器。第二种形式指定了一个到本地主域所有机器的广播操作。第三种形式指定了到某个域的所有机器。对于域和域控制器的详细信息,可参考NT的书籍。

  在打开mailslot后,列表2使用GetComputerName得到本地的计算机名字,然后广播该名字给当前域的所有mailslot,每5秒广播一次。

  列表1使用一个轮询的技术来检查信息。每隔半秒,它就会调用GetMailslotInfo并且检查slot中是否有信息。通常轮询在一个多线程的环境中并不是一个好的技术,因为效率比较低。你可以不用轮询,而是通过设置CreateMailslot中的超时时间为一个适当的值,然后以缓冲为0的参数调用ReadFile来等待信息的到来。一旦ReadFlie返回,你就知道有信息存在,接着可调用GetMailslotInfo和ReadFile,如列表1所示。

  当你运行列表2的程序时,它将广播给网络上的所有机器。如果你在同一或者不同的机器上运行读取器的多个副本,它们都将看到由写入者产生的信息。你也可以在网络上运行多个写入者,读取者都将会看到所有写入者产生的信息。在列表1和2的程序中,要注意到它们都是假定该程序将会从外部终止。你可以使用CloseHandle函数来关闭一个mailslot服务器或者客户端。

  命名管道(Named Pipes)

  命名管道提供了一个确认的传送技术。与网络上的广播方式不同,你通过一个命名管道与另一台机器建立一个不同的连接。如果连接中断。例如是由于一台机器关掉或者网络的某部分有故障,连接的双方都可以在尝试发送或者接收时,马上知道中断的信息。通过一个命名管道,可确保包顺序到达。命名管道的唯一问题是你不能广播包了。要广播任何信息,所有的目标机器都必须与中央的服务器建立一个连接,服务器必须分别传送信息到各个不同的机器上。

  命名管道的创建只比mailslot难一点。列表4和5的程序展示了如何在两个使用命名管道的应用之间,创建一个简单的点对点连接。首先运行列表4中的接收程序,然后在同一机器上运行列表5中的发送程序。该程序将询问你要连接的机器名字。由于你在同一部机器上运行发送和接收的程序,因此可输入“.”或者是你的机器名。你将会看到每隔5秒左右,就有一个信息由发送者传到接收者上。当你关闭发送者的时候,在接收者上就会马上出现一个信息,指示它已经检测出管道连接中断。如果只启动发送的程序,发送者将会马上出错,因为它不能建立一个连接。与mailslot不同,管道可以告诉我们另一端的工作是否正常。

  命名管道连接在网络上的使用与在同一部机器上一样简单。例如,如果列表4中的服务器程序运行在一部称为“orion”的机器上,使用与该机器同样的帐号和密码在另一台不同的机器上登录,在上面运行列表5的程序,要求机器名时,输入“orion”的名字。这样连接就被正确地建立起来了。要注意一点,使用命名管道的时候,你必须要知道运行服务器的机器名字。

  你还要知道,如果使用另一个用户来尝试连接接收器时,连接将会失败。例如用户“jones”在“orion”的机器上运行接收程序,当用户“smith”尝试由另一台机器进行连接时,连接将会失败,并显示一个“拒绝访问”的错误。这是NT的安全系统造成的。你可以看NT书籍的相关部分,了解详细的原因和解决办法。

  在列表4的程序中,通过使用CreateNamedPipe来创建一个命名管道服务器。与CreateNamedPipe函数一起使用的名字通常有以下的形式:

   \\.\pipe\[path]name

与mailslot一样,你可以在管道名字的前面指定一个路径,以区分系统中不同的管道。

  传送给CreateNamedPipe的openMode参数用来决定管道的方向。命名管道可以是单向的,也可以是双向的,在于openMode参数使用的常数,包括有:

PIPE_Access_DUPLEX
PIPE_ACCESS_INBOUND
PIPE_ACCESS_OUTBOUND

  CreateNamedPipe的pipeMode参数决定管道的工作方式,可以是字节流或者是称为信息的字节包。字节流是没有逻辑边界的。信息将一组的字节组合起来作为一个单元传送。你可以在读取和写入时指定一种方式:

PIPE_TYPE_MESSAGE
PIPE_TYPE_BYTE
PIPE_READMODE_MESSAGE
PIPE_READMODE_BYTE

  在一台机器上,一个管道可以有超过一个的实例。这可让一个程序处理多个的客户端,每个使用独立的线程,还必须创建一个命名管道服务器。由于列表4和5中的例子只是一个简单的点对点连接,只需要一个实例,一个实例的最大值在调用CreateNamedPipe时指定。

  列表4中的程序接着会等待一个通过ConnectNamedPipe函数建立的连接。当一个客户端程序使用正确的机器名和命名管道调用CreateFile时,就会与服务器建立起一个连接。在连接时,ConnectNamedPipe函数会返回。你可以选择指定一个交迭的结构,ConnectNamedPipe 将马上返回,然后产生连接的事件。

  列表4的程序然后进入一个循环,等待数据到达。ReadFile函数的工作与操作文件时有点不同。因为这个命名管道是信息的模式,ReadFile只要一接收到一个完整的信息,就会立刻返回。这里使用的是一个阻塞的读取,你也可以选择一个交迭的读取。

  列表5的程序是列表4的一个简单的客户。列表5首先通过CreateFile函数创建连接到命名管道。然后通过使用WriteFile函数写入信息。每次调用WriteFile都会在命名管道的接收端建立一个信息,因此接收者的ReadFile函数将在接收信息时除去阻塞,服务器就会在屏幕上显示一个信息。

  如果两个客户的副本同时尝试连接列表4中的程序,服务器将会拒绝第二个客户。不论是终止客户或者服务器,另一方都将在检测出连接中断时马上终止。

  结论

  命名管道通常用在客户/服务器系统,这时服务器使用一个多线程的方式来同时处理多个连接。

  列表1

  该程序创建一个mailslot服务器并且从中进行读取


/
hSMS_Slot=CreateMailslot("\\\\.\\mailslot\\sms",
0, 0, (LPSECURITY_ATTRIBUTES) NULL);


if (hSMS_Slot == INVALID_HANDLE_&#118alue)
{
cerr << "ERROR: Unable to create mailslot "
<< GetLastError() << endl;
return (1);
}


while(1)
{
Status=GetMailslotInfo(hSMS_Slot,
(LPDWORD) NULL, &nextSize, &Msgs,
(LPDWORD) NULL);
if (!Status)
{
cerr << "ERROR: Unable to get status. "
<< GetLastError() << endl;
CloseHandle(hSMS_Slot);
return (1);
}


if (Msgs)
{


if (!ReadFile(hSMS_Slot, toDisptxt, nextSize,
&NumBytesRead, (LPOVERLAPPED) NULL))
{
cerr
<< "ERROR: Unable to read from mailslot "
<< GetLastError() << endl;
CloseHandle(hSMS_Slot);
return (1);
}


cout << toDisptxt << endl;
}
else

Sleep(500);
}
}

  列表2

  该程序每隔5秒写一次mailslot

/
hSMS_Slot=CreateFile("\\\\*\\mailslot\\sms",
GENERIC_WRITE, FILE_SHARE_READ,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);


if (hSMS_Slot == INVALID_HANDLE_&#118alue)
{
cerr << "ERROR: Unable to create mailslot "
<< GetLastError() << endl;
return (1);
}


GetComputerName(buffer, &bufferLen);
strcpy(toSendTxt, "Test string from ");
strcat(toSendTxt, buffer);


while(1)
{
cout << "Sending..." << endl;

Status=WriteFile(hSMS_Slot,
toSendTxt, (DWORD) strlen(toSendTxt)+1,
&NumBytesWritten, (LPOVERLAPPED) NULL);


if (!Status)
{
cerr << "ERROR: Unable to write to mailslot "
<< GetLastError() << endl;
CloseHandle(hSMS_Slot);
return (1);
}


Sleep(4800);
}
}

  列表4

  这个简单的程序用来创建一个命名管道服务器。该服务器将会等待并接受一个连接,然后从中接收信息。

/
 ssnpPipe=CreateNamedPipe("\\\\.\\pipe\\ssnp",PIPE_Access_INBOUND,
PIPE_TYPE_MESSAGE | PIPE_WAIT,1, 0, 0, 150,(LPSECURITY_ATTRIBUTES)NULL);

 
 if (ssnpPipe == INVALID_HANDLE_&#118alue)
 {
  cerr << "ERROR: Unable to create a named pipe. " <<endl;
  return (1);
 }

 
 cout << "Waiting for connection... " << endl;
 if(!ConnectNamedPipe(ssnpPipe, (LPOVERLAPPED) NULL))
 {
  cerr << "ERROR: Unable to connect a named pipe "<<GetLastError() << endl;
  CloseHandle(ssnpPipe);
  return (1);
 }

 
 while(1)
 {
  
  if (!ReadFile(ssnpPipe,toDisptxt,sizeof(toDisptxt),&NumBytesRead, (LPOVERLAPPED)NULL))
  {
   cerr << "ERROR: Unable to read from named pipe "<< GetLastError() << endl;
   CloseHandle(ssnpPipe);
   return (1);
  }

  
  cout << toDisptxt << endl;
 }
}

  列表4

  这个简单的程序用来创建一个命名管道服务器。该服务器将会等待并接受一个连接,然后从中接收信息。

/
 ssnpPipe=CreateNamedPipe("\\\\.\\pipe\\ssnp",PIPE_Access_INBOUND,
PIPE_TYPE_MESSAGE | PIPE_WAIT,1, 0, 0, 150,(LPSECURITY_ATTRIBUTES)NULL);

 
 if (ssnpPipe == INVALID_HANDLE_&#118alue)
 {
  cerr << "ERROR: Unable to create a named pipe. " <<endl;
  return (1);
 }

 
 cout << "Waiting for connection... " << endl;
 if(!ConnectNamedPipe(ssnpPipe, (LPOVERLAPPED) NULL))
 {
  cerr << "ERROR: Unable to connect a named pipe "<<GetLastError() << endl;
  CloseHandle(ssnpPipe);
  return (1);
 }

 
 while(1)
 {
  
  if (!ReadFile(ssnpPipe,toDisptxt,sizeof(toDisptxt),&NumBytesRead, (LPOVERLAPPED)NULL))
  {
   cerr << "ERROR: Unable to read from named pipe "<< GetLastError() << endl;
   CloseHandle(ssnpPipe);
   return (1);
  }

  
  cout << toDisptxt << endl;
 }
}

用命名管道实现局域网上两台主机间的文件拷贝

  能实现局域网上两台主机间文件拷贝的方法有很多种,这里介绍的“命名管道”(Named Pipe)是一种比较可靠的进程间通信机制,可用在同一台计算机不同进程间,也可用在不同计算机的不同进程间,可以是单向的,也可以是双向的,WindowsNT、Windows 2000、Windows 95和Windows98均提供了对它的支持,而且在Unix下也有类似的概念。它是在MicrosoftLAN管理器和IBMLAN服务器网络操作系统上实现的。

  命名管道使用了MSNP(微软网络提供者)重定向器,这样应用程序便可以不用了解网络协议的细节而利用该机制实现网络上的数据传输。它采用“命名管道文件系统”(NamedPipe FileSystem)接口,其命名是采用UNC(通用命名规范)格式的:

\\ServerName\Pipe\[pipename]
\\ServerName

  指明命名管道是在那个服务器上创建的,ServerName既可以是一个实际的计算机名,也可以是小数点(“.”)以指明是在本机上创建;\Pipe是一个硬编码(Hardcode)不用区分大小写的字符串用以指明这是一个管道名,该文件名从属于NPFS;[pipename]是实际的自定义的管道名,该名称在前面指定的服务器上必须是唯一的,该名称可以包含多级目录,但目录名必须不是已经创建的管道名,例:

\\.\Pipe\xyPipe’这是一个合法管道名

\\.\Pipe\xyPipe\Pipe’这不是一个合法的管道名,因为前面的目录\\.\Pipe\xyPipe是一个已经创建的管道名了。

\\.\Pipe\xxyPipe\Pipe’这也是一个合法的文件名

  命名管道有两种基本通信模式:字节模式和消息模式。在字节模式中,数据是以字节流的形式在管道种传输,数据之间没有边界,在管道写入和读出操作中是以字节流即数据块为基本单位操作的,这适合传输大容量数据;在消息模式中,数据是以一条条不连续的消息为基本传输单元,消息和消息之间有边界,在管道写入和读出操作中也是以消息为单位进行操作的,这种方式适合传输量小的数据。因为现在的文件大小常常有几百K甚至更大,所以程序中选择使用字节模式。

  下面详细介绍一下CreateNamedPipe()这个函数,该函数C原型如下:

HANDLE CreateNamedPipe(
LPCTSTR lpName, // pointer to pipe name
DWORD dwOpenMode, // pipe open mode
DWORD dwPipeMode, // pipe-specific modes
DWORD nMaxInstances, // maximum number of instances
DWORD nOutBufferSize, // output buffer size, in bytes
DWORD nInBufferSize, // input buffer size, in bytes
DWORD nDefaultTimeOut, // time-out time, in milliseconds
LPSECURITY_ATTRIBUTES lpSecurityAttributes // pointer to securityattributes
);

lpName:为前面所述的命名管道名。

dwOpenMode:为命名管道打开的模式,有PIPE_ACCESS_DUMPLEX(双向)、PIPE_ACCESS_INBOUND(输入)、PIPE_ACCESS_OUTBOUND(输出)这三种,这些标志还可以和一些附加的I/O控制和安全模式的常数组合使用,详细可参考MSDN。

dwPipeMode:为管道传输模式,有前面所述的PIPE_TYPE_BYTE(字节模式)和PIPE_TYPE_MESSAGE(消息模式)两种,可以和PIPE_READMODE_BYTE和PIPE_READMODE_MESSAGE常数组合使用以限定客户端的读取模式。可以使用PIPE_TYPE_MESSAGE 和PIPE_READMODE_BYTE组合来指定发送者以消息模式向管道发送数据,而接收者一次可以读取任意数量的字节。注意不可将PIPE_TYPE_BYTE和PIPE_READMODE_MESSAGE组合使用,这样会导致CreateNamedPipe()函数调用失败,因为字节模式没有边界,在接收端用消息模式读取的时候无法判断消息的边界。

nMaxInstances:管道最大的连接实例句柄,其范围在1到255之间。

nOutBufferSize和nInBufferSize分别指明管道输出和输入缓冲区的大小,如设为0则使用系统默认大小。

nDefaultTimeOut以毫秒为单位设定客户机等待同命名管道建立连接的最长时间。

LpSecurityAttruibutes为一个安全描述符,设为Null表示使用系统默认的描述符,同时句柄不可继承。

  要注意的是在程序中命名管道的写操作中一次最大只能写64K字节的数据,下面是服务器端程序(模块中):

Public Declare Function CreateNamedPipe Lib "kernel32" Alias"CreateNamedPipeA" (ByVal lpName As String, ByVal dwOpenMode AsLong, ByVal dwPipeMode As Long, ByVal nMaxInstances As Long, ByValnOutBufferSize As Long, ByVal nInBufferSize As Long, ByValnDefaultTimeOut As Long, ByVal lpSecurityAttributes As Long) AsLong
Public Declare Function ConnectNamedPipe Lib "kernel32" (ByValhNamedPipe As Long, ByVal lplong As Long) As Long
Public Declare Function ReadFile Lib "kernel32" (ByVal hFile AsLong, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long,lpNumberOfBytesRead As Long, ByVal lplong As Long) As Long
Public Declare Function WriteFile Lib "kernel32" (ByVal hFile AsLong, lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long,lpNumberOfBytesWritten As Long, ByVal lplong As Long) As Long
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObjectAs Long) As Long
Public Declare Function WaitNamedPipe Lib "kernel32" Alias"WaitNamedPipeA" (ByVal lpNamedPipeName As String, ByVal nTimeOutAs Long) As Long
Public Declare Function CreateFile Lib "kernel32" Alias"CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess AsLong, ByVal dwShareMode As Long, ByVal lpSecurityAttributes AsLong, ByVal dwCreationDisposition As Long, ByValdwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) AsLong
Public Declare Function DisconnectNamedPipe Lib "kernel32" (ByValhNamedPipe As Long) As Long
Public Declare Function GetFileSize Lib "kernel32" (ByVal hFile AsLong, lpFileSizeHigh As Long) As Long

Public Const PIPE_ACCESS_DUPLEX = &H3
Public Const PIPE_ACCESS_INBOUND = &H1
Public Const PIPE_ACCESS_OUTBOUND = &H2
Public Const PIPE_CLIENT_END = &H0
Public Const PIPE_NOWAIT = &H1
Public Const PIPE_READMODE_BYTE = &H0
Public Const PIPE_READMODE_MESSAGE = &H2
Public Const PIPE_SERVER_END = &H1
Public Const PIPE_TYPE_BYTE = &H0
Public Const PIPE_TYPE_MESSAGE = &H4
Public Const PIPE_UNLIMITED_INSTANCES = 255
Public Const PIPE_WAIT = &H0
Public Const FILE_SHARE_READ = &H1
Public Const FILE_SHARE_WRITE = &H2
Public Const GENERIC_READ = &H80000000
Public Const GENERIC_WRITE = &H40000000
Public Const GENERIC_EXECUTE = &H20000000
Public Const GENERIC_ALL = &H10000000
Public Const OPEN_EXISTING = 3
Public Const ERROR_PIPE_BUSY = 231&
Public Const ERROR_PIPE_CONNECTED = 535&
Public Const ERROR_PIPE_LISTENING = 536&
Public Const ERROR_PIPE_NOT_CONNECTED = 233&
Public Const ERROR_NO_DATA = 232&

Public Const BufferSize& = 51200
Public hNamePipe&, hFile&, strNamePipe$

  Form中有三个按钮,分别是“创建命名管道”(CreateNPipe)、“发送文件”(SendFile)、“关闭命名管道”(CloseNamePipe),窗口中还有一个CommonDialog控件,命名为“CDlg1”。Form中代码:

Dim outBuffer() As Byte, inBuffer() As Byte, BytesRead As Long,BytesWrite As Long, BytesReaded As Long, BytesWrited As Long
Private Sub CloseNamePipe_Click()
DisconnectNamedPipe hNamePipe
CloseHandle hNamePipe
CreateNPipe.Enabled = True
SendFile.Enabled = False
CloseNamePipe.Enabled = False
End Sub

Private Sub CreateNPipe_Click()
Dim hReturn&
strNamePipe = "\\.\pipe\xyvanPipe"
hNamePipe = CreateNamedPipe(strNamePipe, PIPE_ACCESS_DUPLEX,PIPE_TYPE_BYTE Or PIPE_READMODE_BYTE, 1, 0, 0, 0, 0)
If hNamePipe <> -1 Then
hReturn = ConnectNamedPipe(hNamePipe, 0)
If hReturn = 0 Then
MsgBox "管道无法等待客户端的连接!", vbInformation OrvbOKOnly
Unload Me
Else
Label1 = "已同客户机连接上!"
End If
CreateNPipe.Enabled = False
SendFile.Enabled = True
CloseNamePipe.Enabled = True
Else
MsgBox "无法创建命名管道!", vbInformation Or vbOKOnly
Unload Me
End If
End Sub

Private Sub Form_Load()
With CDlg1
.CancelError = True
.DialogTitle = "请选择要传输的文件:"
.filename = ""
.Filter = "所有文件(*.*)|*.*"
.Flags = cdlOFNExplorer Or cdlOFNFileMustExist OrcdlOFNPathMustExist
.InitDir = "d:\"
End With
SendFile.Enabled = False
CloseNamePipe.Enabled = False
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode AsInteger)
DisconnectNamedPipe hNamePipe
CloseHandle hFile
CloseHandle hNamePipe
End Sub
 
Private SubSendFile_Click()
On Error Resume Next
Dim strFileName$, lpFileSize&, lpFileSizeHigh&,lpFileSizeLeast&, byteEnd() As Byte
Dim strShortName$
CDlg1.ShowOpen
If Err.Number = 32755 Then Exit Sub
strFileName = CDlg1.filename
strShortName = CDlg1.FileTitle
hFile = CreateFile(strFileName, GENERIC_READ, FILE_SHARE_READ OrFILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0)
If hFile = -1 Then
MsgBox "无法打开文件" & strFileName, vbInformation OrvbOKOnly
Exit Sub
End If
lpFileSize = GetFileSize(hFile, lpFileSizeHigh)
If lpFileSize = 0 Then
MsgBox "该文件大小为零,不用发送!", vbInformation OrvbOKOnly
CloseHandle hFile
Exit Sub
End If
lpFileSizeLeast = lpFileSize

byteEnd() = StrConv(strShortName, vbFromUnicode)
ReDim outBuffer(UBound(byteEnd))
ByteCopy byteEnd, outBuffer
WriteFile hNamePipe, byteEnd(0), UBound(byteEnd) + 1, BytesWrited,0 '发送短文件名
ReDim inBuffer(5)
ReadFile hNamePipe, inBuffer(0), 6, BytesReaded, 0'读取客户端对话信息
If StrConv(inBuffer, vbUnicode) = "Cancel" Then
MsgBox "客户端保存时选择了取消,发送终止!", vbInformation OrvbOKOnly
CloseHandle hFile
Exit Sub
End If
Label1.Caption = "正在传输中…"
While lpFileSize > 0
If lpFileSize > BufferSize Then
ReDim outBuffer(BufferSize - 1)
ReadFile hFile, outBuffer(0), BufferSize, BytesReaded, 0
WriteFile hNamePipe, outBuffer(0), BytesReaded, BytesWrited,0
Else
ReDim outBuffer(lpFileSize - 1)
ReadFile hFile, outBuffer(0), lpFileSize, BytesReaded, 0
WriteFile hNamePipe, outBuffer(0), lpFileSize, BytesWrited, 0
End If
lpFileSize = lpFileSize - BytesReaded
ReadFile hNamePipe, inBuffer(0), 6, BytesReaded, 0
Wend

byteEnd() = StrConv("EOF", vbFromUnicode)
ReDim outBuffer(UBound(byteEnd))
ByteCopy byteEnd, outBuffer
WriteFile hNamePipe, outBuffer(0), 3, BytesWrited, 0
CloseHandle hFile
Label1 = "传送文件完毕!"
End Sub
Public Sub ByteCopy(bySrc() As Byte, byDes() As Byte)
Dim I As Long
For i = LBound(bySrc) To UBound(bySrc)
byDes(i) = bySrc(i)
Next
End Sub

  客户端程序(模块中程序和服务器端是一样的,这里省略不写了),Form中有一个Text框,用以输入要打开连接的服务器端的命名管道的名称,一个CommonDialog(CDlg1)控件,另还有一“连接命名管道”(Connect)按钮和“断开连接”(Disconnect)按钮,程序如下:

Dim inBuffer() As Byte, BytesRead&, BytesReaded&,BytesWrited&, strFileName$
Private Sub Connect_Click()
Dim hRes&
strNamePipe = Text1
hRes = WaitNamedPipe(strNamePipe, -1)
If hRes = 0 Then
MsgBox "没有可用的命名管道以供连接!", vbInformation OrvbOKOnly
Exit Sub
End If
hNamePipe = CreateFile(strNamePipe, GENERIC_READ Or GENERIC_WRITE,0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
If hNamePipe = 0 Then
MsgBox "无法打开指定的命名管道进行读写!", vbInformation OrvbOKOnly
Exit Sub
End If
FileSave
End Sub

Private Sub Disconnect_Click()
CloseHandle hFile
CloseHandle hNamePipe
End Sub

Private Sub Form_Load()
With CDlg1
.CancelError = True
.DialogTitle = "保存为:"
.FileName = ""
' .Filter = "所有文件(*.*)|*.*"
.Flags = cdlOFNExplorer Or cdlOFNOverwritePrompt
.InitDir = "d:\"
End With
End Sub
Private Sub FileSave()
BytesRead = 51200
Dim AckByte() As Byte
ReDim inBuffer(BytesRead - 1)
On Error Resume Next
Do
ReadFile hNamePipe, inBuffer(0), BytesRead, BytesReaded, 0
If BytesReaded < 258 Then
strFileName = Trim(StrConv(inBuffer, vbUnicode))
strFileName = Left(strFileName, InStr(strFileName, Chr(0)) -1)
If strFileName Like "EOF*" And BytesReaded = 3 Then
CloseHandle hFile
MsgBox "文件接收完毕!", vbInformation Or vbOKOnly OrvbSystemModal
Exit Sub
Else
CDlg1.Filter = UCase(GetExtension(strFileName)) & "文件(*."& GetExtension(strFileName) & ")|*." &GetExtension(strFileName)
CDlg1.FileName = Left(strFileName, InStr(strFileName, ".") -1)
ReSelect: CDlg1.ShowSave
If Err.Number = 32755 Then
AckByte() = StrConv("Cancel", vbFromUnicode)
WriteFile hNamePipe, AckByte(0), UBound(AckByte()) + 1,BytesWrited, 0
MsgBox "你选择了取消键!", vbInformation Or vbOKOnly
Exit Sub
End If
hFile = CreateFile(CDlg1.FileName, GENERIC_READ Or GENERIC_WRITE,0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)
If hFile = -1 Then
MsgBox "无法创建指定文件,请重新选择文件名!", vbInformation OrvbOKOnly
GoTo ReSelect
End If
AckByte() = StrConv("RecvOk", vbFromUnicode)
WriteFile hNamePipe, AckByte(0), UBound(AckByte()) + 1,BytesWrited, 0
End If
Else
WriteFile hFile, inBuffer(0), BytesReaded, BytesWrited, 0
AckByte() = StrConv("RecvOk", vbFromUnicode)
WriteFile hNamePipe, AckByte(0), UBound(AckByte()) + 1,BytesWrited, 0
End If
Loop Until 1 = 0
End Sub
Private Function GetExtension(ByVal FileName$) As String
GetExtension = Right(FileName, Len(FileName) - InStr(FileName,"."))
End Function
Public Sub ByteCopy(bySrc() As Byte, byDes() As Byte)
Dim i&
For i = LBound(bySrc) To UBound(bySrc)
byDes(i) = bySrc(i)
Next
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode AsInteger)
CloseHandle hFile
CloseHandle hNamePipe
End Sub

  该程序在VB5、Windows NT 4.0上调试通过。

  在处理网络事务上,命名管道接口比NetBIOS要好,而且只需使用一个简单的调用就可达到目的,而无需通过NetBIOS执行许多操作。然而,命名管道接口并不提供NetBIOS的一些特征,如无连接数据报服务和允许向一个组发送消息的命名功能。

用命名管道实现局域网上两台主机间的文件拷贝

  能实现局域网上两台主机间文件拷贝的方法有很多种,这里介绍的“命名管道”(Named Pipe)是一种比较可靠的进程间通信机制,可用在同一台计算机不同进程间,也可用在不同计算机的不同进程间,可以是单向的,也可以是双向的,WindowsNT、Windows 2000、Windows 95和Windows98均提供了对它的支持,而且在Unix下也有类似的概念。它是在MicrosoftLAN管理器和IBMLAN服务器网络操作系统上实现的。

  命名管道使用了MSNP(微软网络提供者)重定向器,这样应用程序便可以不用了解网络协议的细节而利用该机制实现网络上的数据传输。它采用“命名管道文件系统”(NamedPipe FileSystem)接口,其命名是采用UNC(通用命名规范)格式的:

\\ServerName\Pipe\[pipename]
\\ServerName

  指明命名管道是在那个服务器上创建的,ServerName既可以是一个实际的计算机名,也可以是小数点(“.”)以指明是在本机上创建;\Pipe是一个硬编码(Hardcode)不用区分大小写的字符串用以指明这是一个管道名,该文件名从属于NPFS;[pipename]是实际的自定义的管道名,该名称在前面指定的服务器上必须是唯一的,该名称可以包含多级目录,但目录名必须不是已经创建的管道名,例:

\\.\Pipe\xyPipe’这是一个合法管道名

\\.\Pipe\xyPipe\Pipe’这不是一个合法的管道名,因为前面的目录\\.\Pipe\xyPipe是一个已经创建的管道名了。

\\.\Pipe\xxyPipe\Pipe’这也是一个合法的文件名

  命名管道有两种基本通信模式:字节模式和消息模式。在字节模式中,数据是以字节流的形式在管道种传输,数据之间没有边界,在管道写入和读出操作中是以字节流即数据块为基本单位操作的,这适合传输大容量数据;在消息模式中,数据是以一条条不连续的消息为基本传输单元,消息和消息之间有边界,在管道写入和读出操作中也是以消息为单位进行操作的,这种方式适合传输量小的数据。因为现在的文件大小常常有几百K甚至更大,所以程序中选择使用字节模式。

  下面详细介绍一下CreateNamedPipe()这个函数,该函数C原型如下:

HANDLE CreateNamedPipe(
LPCTSTR lpName, // pointer to pipe name
DWORD dwOpenMode, // pipe open mode
DWORD dwPipeMode, // pipe-specific modes
DWORD nMaxInstances, // maximum number of instances
DWORD nOutBufferSize, // output buffer size, in bytes
DWORD nInBufferSize, // input buffer size, in bytes
DWORD nDefaultTimeOut, // time-out time, in milliseconds
LPSECURITY_ATTRIBUTES lpSecurityAttributes // pointer to securityattributes
);

lpName:为前面所述的命名管道名。

dwOpenMode:为命名管道打开的模式,有PIPE_ACCESS_DUMPLEX(双向)、PIPE_ACCESS_INBOUND(输入)、PIPE_ACCESS_OUTBOUND(输出)这三种,这些标志还可以和一些附加的I/O控制和安全模式的常数组合使用,详细可参考MSDN。

dwPipeMode:为管道传输模式,有前面所述的PIPE_TYPE_BYTE(字节模式)和PIPE_TYPE_MESSAGE(消息模式)两种,可以和PIPE_READMODE_BYTE和PIPE_READMODE_MESSAGE常数组合使用以限定客户端的读取模式。可以使用PIPE_TYPE_MESSAGE 和PIPE_READMODE_BYTE组合来指定发送者以消息模式向管道发送数据,而接收者一次可以读取任意数量的字节。注意不可将PIPE_TYPE_BYTE和PIPE_READMODE_MESSAGE组合使用,这样会导致CreateNamedPipe()函数调用失败,因为字节模式没有边界,在接收端用消息模式读取的时候无法判断消息的边界。

nMaxInstances:管道最大的连接实例句柄,其范围在1到255之间。

nOutBufferSize和nInBufferSize分别指明管道输出和输入缓冲区的大小,如设为0则使用系统默认大小。

nDefaultTimeOut以毫秒为单位设定客户机等待同命名管道建立连接的最长时间。

LpSecurityAttruibutes为一个安全描述符,设为Null表示使用系统默认的描述符,同时句柄不可继承。

  要注意的是在程序中命名管道的写操作中一次最大只能写64K字节的数据,下面是服务器端程序(模块中):

Public Declare Function CreateNamedPipe Lib "kernel32" Alias"CreateNamedPipeA" (ByVal lpName As String, ByVal dwOpenMode AsLong, ByVal dwPipeMode As Long, ByVal nMaxInstances As Long, ByValnOutBufferSize As Long, ByVal nInBufferSize As Long, ByValnDefaultTimeOut As Long, ByVal lpSecurityAttributes As Long) AsLong
Public Declare Function ConnectNamedPipe Lib "kernel32" (ByValhNamedPipe As Long, ByVal lplong As Long) As Long
Public Declare Function ReadFile Lib "kernel32" (ByVal hFile AsLong, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long,lpNumberOfBytesRead As Long, ByVal lplong As Long) As Long
Public Declare Function WriteFile Lib "kernel32" (ByVal hFile AsLong, lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long,lpNumberOfBytesWritten As Long, ByVal lplong As Long) As Long
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObjectAs Long) As Long
Public Declare Function WaitNamedPipe Lib "kernel32" Alias"WaitNamedPipeA" (ByVal lpNamedPipeName As String, ByVal nTimeOutAs Long) As Long
Public Declare Function CreateFile Lib "kernel32" Alias"CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess AsLong, ByVal dwShareMode As Long, ByVal lpSecurityAttributes AsLong, ByVal dwCreationDisposition As Long, ByValdwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) AsLong
Public Declare Function DisconnectNamedPipe Lib "kernel32" (ByValhNamedPipe As Long) As Long
Public Declare Function GetFileSize Lib "kernel32" (ByVal hFile AsLong, lpFileSizeHigh As Long) As Long

Public Const PIPE_ACCESS_DUPLEX = &H3
Public Const PIPE_ACCESS_INBOUND = &H1
Public Const PIPE_ACCESS_OUTBOUND = &H2
Public Const PIPE_CLIENT_END = &H0
Public Const PIPE_NOWAIT = &H1
Public Const PIPE_READMODE_BYTE = &H0
Public Const PIPE_READMODE_MESSAGE = &H2
Public Const PIPE_SERVER_END = &H1
Public Const PIPE_TYPE_BYTE = &H0
Public Const PIPE_TYPE_MESSAGE = &H4
Public Const PIPE_UNLIMITED_INSTANCES = 255
Public Const PIPE_WAIT = &H0
Public Const FILE_SHARE_READ = &H1
Public Const FILE_SHARE_WRITE = &H2
Public Const GENERIC_READ = &H80000000
Public Const GENERIC_WRITE = &H40000000
Public Const GENERIC_EXECUTE = &H20000000
Public Const GENERIC_ALL = &H10000000
Public Const OPEN_EXISTING = 3
Public Const ERROR_PIPE_BUSY = 231&
Public Const ERROR_PIPE_CONNECTED = 535&
Public Const ERROR_PIPE_LISTENING = 536&
Public Const ERROR_PIPE_NOT_CONNECTED = 233&
Public Const ERROR_NO_DATA = 232&

Public Const BufferSize& = 51200
Public hNamePipe&, hFile&, strNamePipe$

  Form中有三个按钮,分别是“创建命名管道”(CreateNPipe)、“发送文件”(SendFile)、“关闭命名管道”(CloseNamePipe),窗口中还有一个CommonDialog控件,命名为“CDlg1”。Form中代码:

Dim outBuffer() As Byte, inBuffer() As Byte, BytesRead As Long,BytesWrite As Long, BytesReaded As Long, BytesWrited As Long
Private Sub CloseNamePipe_Click()
DisconnectNamedPipe hNamePipe
CloseHandle hNamePipe
CreateNPipe.Enabled = True
SendFile.Enabled = False
CloseNamePipe.Enabled = False
End Sub

Private Sub CreateNPipe_Click()
Dim hReturn&
strNamePipe = "\\.\pipe\xyvanPipe"
hNamePipe = CreateNamedPipe(strNamePipe, PIPE_ACCESS_DUPLEX,PIPE_TYPE_BYTE Or PIPE_READMODE_BYTE, 1, 0, 0, 0, 0)
If hNamePipe <> -1 Then
hReturn = ConnectNamedPipe(hNamePipe, 0)
If hReturn = 0 Then
MsgBox "管道无法等待客户端的连接!", vbInformation OrvbOKOnly
Unload Me
Else
Label1 = "已同客户机连接上!"
End If
CreateNPipe.Enabled = False
SendFile.Enabled = True
CloseNamePipe.Enabled = True
Else
MsgBox "无法创建命名管道!", vbInformation Or vbOKOnly
Unload Me
End If
End Sub

Private Sub Form_Load()
With CDlg1
.CancelError = True
.DialogTitle = "请选择要传输的文件:"
.filename = ""
.Filter = "所有文件(*.*)|*.*"
.Flags = cdlOFNExplorer Or cdlOFNFileMustExist OrcdlOFNPathMustExist
.InitDir = "d:\"
End With
SendFile.Enabled = False
CloseNamePipe.Enabled = False
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode AsInteger)
DisconnectNamedPipe hNamePipe
CloseHandle hFile
CloseHandle hNamePipe
End Sub
 
Private SubSendFile_Click()
On Error Resume Next
Dim strFileName$, lpFileSize&, lpFileSizeHigh&,lpFileSizeLeast&, byteEnd() As Byte
Dim strShortName$
CDlg1.ShowOpen
If Err.Number = 32755 Then Exit Sub
strFileName = CDlg1.filename
strShortName = CDlg1.FileTitle
hFile = CreateFile(strFileName, GENERIC_READ, FILE_SHARE_READ OrFILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0)
If hFile = -1 Then
MsgBox "无法打开文件" & strFileName, vbInformation OrvbOKOnly
Exit Sub
End If
lpFileSize = GetFileSize(hFile, lpFileSizeHigh)
If lpFileSize = 0 Then
MsgBox "该文件大小为零,不用发送!", vbInformation OrvbOKOnly
CloseHandle hFile
Exit Sub
End If
lpFileSizeLeast = lpFileSize

byteEnd() = StrConv(strShortName, vbFromUnicode)
ReDim outBuffer(UBound(byteEnd))
ByteCopy byteEnd, outBuffer
WriteFile hNamePipe, byteEnd(0), UBound(byteEnd) + 1, BytesWrited,0 '发送短文件名
ReDim inBuffer(5)
ReadFile hNamePipe, inBuffer(0), 6, BytesReaded, 0'读取客户端对话信息
If StrConv(inBuffer, vbUnicode) = "Cancel" Then
MsgBox "客户端保存时选择了取消,发送终止!", vbInformation OrvbOKOnly
CloseHandle hFile
Exit Sub
End If
Label1.Caption = "正在传输中…"
While lpFileSize > 0
If lpFileSize > BufferSize Then
ReDim outBuffer(BufferSize - 1)
ReadFile hFile, outBuffer(0), BufferSize, BytesReaded, 0
WriteFile hNamePipe, outBuffer(0), BytesReaded, BytesWrited,0
Else
ReDim outBuffer(lpFileSize - 1)
ReadFile hFile, outBuffer(0), lpFileSize, BytesReaded, 0
WriteFile hNamePipe, outBuffer(0), lpFileSize, BytesWrited, 0
End If
lpFileSize = lpFileSize - BytesReaded
ReadFile hNamePipe, inBuffer(0), 6, BytesReaded, 0
Wend

byteEnd() = StrConv("EOF", vbFromUnicode)
ReDim outBuffer(UBound(byteEnd))
ByteCopy byteEnd, outBuffer
WriteFile hNamePipe, outBuffer(0), 3, BytesWrited, 0
CloseHandle hFile
Label1 = "传送文件完毕!"
End Sub
Public Sub ByteCopy(bySrc() As Byte, byDes() As Byte)
Dim I As Long
For i = LBound(bySrc) To UBound(bySrc)
byDes(i) = bySrc(i)
Next
End Sub

  客户端程序(模块中程序和服务器端是一样的,这里省略不写了),Form中有一个Text框,用以输入要打开连接的服务器端的命名管道的名称,一个CommonDialog(CDlg1)控件,另还有一“连接命名管道”(Connect)按钮和“断开连接”(Disconnect)按钮,程序如下:

Dim inBuffer() As Byte, BytesRead&, BytesReaded&,BytesWrited&, strFileName$
Private Sub Connect_Click()
Dim hRes&
strNamePipe = Text1
hRes = WaitNamedPipe(strNamePipe, -1)
If hRes = 0 Then
MsgBox "没有可用的命名管道以供连接!", vbInformation OrvbOKOnly
Exit Sub
End If
hNamePipe = CreateFile(strNamePipe, GENERIC_READ Or GENERIC_WRITE,0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
If hNamePipe = 0 Then
MsgBox "无法打开指定的命名管道进行读写!", vbInformation OrvbOKOnly
Exit Sub
End If
FileSave
End Sub

Private Sub Disconnect_Click()
CloseHandle hFile
CloseHandle hNamePipe
End Sub

Private Sub Form_Load()
With CDlg1
.CancelError = True
.DialogTitle = "保存为:"
.FileName = ""
' .Filter = "所有文件(*.*)|*.*"
.Flags = cdlOFNExplorer Or cdlOFNOverwritePrompt
.InitDir = "d:\"
End With
End Sub
Private Sub FileSave()
BytesRead = 51200
Dim AckByte() As Byte
ReDim inBuffer(BytesRead - 1)
On Error Resume Next
Do
ReadFile hNamePipe, inBuffer(0), BytesRead, BytesReaded, 0
If BytesReaded < 258 Then
strFileName = Trim(StrConv(inBuffer, vbUnicode))
strFileName = Left(strFileName, InStr(strFileName, Chr(0)) -1)
If strFileName Like "EOF*" And BytesReaded = 3 Then
CloseHandle hFile
MsgBox "文件接收完毕!", vbInformation Or vbOKOnly OrvbSystemModal
Exit Sub
Else
CDlg1.Filter = UCase(GetExtension(strFileName)) & "文件(*."& GetExtension(strFileName) & ")|*." &GetExtension(strFileName)
CDlg1.FileName = Left(strFileName, InStr(strFileName, ".") -1)
ReSelect: CDlg1.ShowSave
If Err.Number = 32755 Then
AckByte() = StrConv("Cancel", vbFromUnicode)
WriteFile hNamePipe, AckByte(0), UBound(AckByte()) + 1,BytesWrited, 0
MsgBox "你选择了取消键!", vbInformation Or vbOKOnly
Exit Sub
End If
hFile = CreateFile(CDlg1.FileName, GENERIC_READ Or GENERIC_WRITE,0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)
If hFile = -1 Then
MsgBox "无法创建指定文件,请重新选择文件名!", vbInformation OrvbOKOnly
GoTo ReSelect
End If
AckByte() = StrConv("RecvOk", vbFromUnicode)
WriteFile hNamePipe, AckByte(0), UBound(AckByte()) + 1,BytesWrited, 0
End If
Else
WriteFile hFile, inBuffer(0), BytesReaded, BytesWrited, 0
AckByte() = StrConv("RecvOk", vbFromUnicode)
WriteFile hNamePipe, AckByte(0), UBound(AckByte()) + 1,BytesWrited, 0
End If
Loop Until 1 = 0
End Sub
Private Function GetExtension(ByVal FileName$) As String
GetExtension = Right(FileName, Len(FileName) - InStr(FileName,"."))
End Function
Public Sub ByteCopy(bySrc() As Byte, byDes() As Byte)
Dim i&
For i = LBound(bySrc) To UBound(bySrc)
byDes(i) = bySrc(i)
Next
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode AsInteger)
CloseHandle hFile
CloseHandle hNamePipe
End Sub

  该程序在VB5、Windows NT 4.0上调试通过。

  在处理网络事务上,命名管道接口比NetBIOS要好,而且只需使用一个简单的调用就可达到目的,而无需通过NetBIOS执行许多操作。然而,命名管道接口并不提供NetBIOS的一些特征,如无连接数据报服务和允许向一个组发送消息的命名功能。
2. 匿名管道(Pipe)
 
    现在大多数都是基于管道通信的,因为每两个进程都可以共享一个管道来进行单独的对话,就象打电话单独占用一条线路一样,而不必担心像剪贴板一样会有串音,匿名管道是一种只能在本地机器上实现两个进程间通信的管道,它只能用来实现一个父进程和一个子进程之间实现数据传输.其实它是非常有用的,我做过一个实际的项目就是利用匿名管道,项目就是让我写一个Ping程序来监测网络的通信状况,并且要把统计结果和执行过程显示在我们的软件里,windows有一个自带的ping程序,而且有执行过程和统计,所以我没必要再发明一个(重复发明就等于犯罪----程序员要牢记阿),只是windows的那个Ping程序的执行结果都显示在了CMD的界面上了,我需要把它提取出来显示在我们的软件界面上,于是我就利用了匿名管道实现了这个程序,当我们的软件要启动Ping任务时,我就先CreatePipe创建匿名管道,再CreateProcess启动了windows下面的Ping程序(它作为我们软件的子进程),当然要把管道的读写句柄一起传给子进程,这样我就可以轻松的把Ping的执行结果了写入到我的Buffer里了,是不是很easy。
      BOOLCreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe,LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize)
      这个API函数是有用来创建匿名管道的,它返回管道的读写句柄(hReadPipe,hWritePipe),记住lpPipeAttributes不能为NULL,因为这意味着函数的返回句柄不能被子进程所继承,你要知道匿名管道可是实现父子进程通信的阿,只有当一个子进程从其父进程中继承了匿名管道句柄后,这两个进程才可以通信,lpPipeAttributes不为NULL还远不够,LPSECURITY_ATTRIBUTES这个结构体的内容去查MSDN吧,我只告诉你其中的BOOLbInheritHandle这个成员变量要赋值为TRUE,这样才真正实现了子进程可以从父进程中继承匿名管道.
      BOOLCreateProcess(...)     
      这个系统API函数是用来在你的进程中启动一个子进程用的,它的参数实在太多了,你还是去查MSDN吧,别怪我太懒惰,我只说几个关键的地方,不想说的太详细.
      下面我就在写一个程序利用匿名管道来通信
      父进程的实现:
      ClassCParent
      {
            ....
      private:
            HANDLEm_hWrite;
            HANDLEm_hRead;
      }
      voidCParent::onCreatePipe()
      {
            SECURITY_ATTRIBUTES sa;    //父进程传递给子进程的一些信息
            sa.bInheritHandle = TRUE; //还记得我上面的提醒吧,这个来允许子进程继承父进程的管道句柄
            sa.lpSecurityDescriptor = NULL
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
            if(!CreatePipe(&m_hRead, &m_hWrite, &sa, 0)
            {
                  return;
            }

            STARTUPINFOsui;     
            PROCESS_INFOMATION pi;  //保存了所创建子进程的信息
            ZeroMemory(&sui, sizeof(STARTUPINFO)); //对一个内存区清零,最好用ZeroMemory, 它的速度要快于memset
            sui.cb =sizeof(STARTUPINFO);
            sui.dwFlags= STARTF_USESTDHANDLES;
           
            sui.hStdInput = m_hRead;
            sui.hstdOutput = m_hWrite;
            /×以上两行也许大家要有些疑问,为什么把管道读句柄(m_hRead)赋值给了hStdInput,因为管道是双向的,对于父进程写的一端正好是子进程读的一端,而m_hRead就是父进程中对管道读的一端,自然要把这个句柄给子进程让它来写数据了(sui是父进程传给子进程的数据结构,里面包含了一些父进程要告诉子进程的一些信息),反之一样×/
                              sui.hStdError = GetStdHandle(STD_ERROR_HANDLE);
           
            if(!CreateProcess("Child.exe", NULL, NULL, NULL, TRUE, 0, NULL, NULL,&sui, &pi))
            {
                  CloseHandle(m_hRead);
                  CLoseHandle(m_hWrite);
                  return;
            }
            else
            {
                  CloseHandle(pi.hProcess); // 子进程的进程句柄
                  Closehandle(pi.hThread);  //子进程的线程句柄,windows中进程就是一个线程的容器,每个进程至少有一个线程在执行
            }
           
      }
void CPraent::OnPiepRead()
      {
            charbuf[100];
            DWORDdwRead;
            if(!ReadFile(hRead, buf, 100, &dwRead, NULL))//从管道中读取数据
            {
           
                  return;
            }
            Messagebox(buf)
      }
           
      voidCParent::onPipeWrite(char *pBuf)
      {
            ASSERT(pBuf!= NULL); // 这个很重要
            DWORDdwWrite;
            if(!WriteFile(hWrite, pBuf, strlen(pBuf) + 1, &dwWrite, NULL))//向管道中写数据
            {
                  return;
            }
      }

      子进程的实现:
      ClassChild
      {
      ......
      private:
            HANDLEm_hRead;
            HANDLEm_hWrite;           
      }
      void CChild:: CChild()
      {
            m_hRead =GetStdHandle(STD_INPUT_HANDLE);
            m_hWrite =GetStdhandle(STD_OUTPUT_HANDLE);
            /×GetStdhandle获得标准输入输出句柄,如果你希望你的程序也能跟其他父进程通信的话最好也这么作,并不是所有的程序被创建了后都能跟父进程通信的,我用过很多老外写的小程序,它们都提供了标准的对外通信接口,这样很便于你的使用特别对程序员×/
      }
              void CChild::OnReadPipe()
      voi dCChild::OnWritePipe()
匿名管道由于是匿名的方式所以它不能实现两个同级的进程进行通信,因为一个进程创建了一个管道后,另一个线程并不知道如何找到这个管道,所以它只能通过父进程直接把管道读写柄直接传递给子进程的方式进行进程通信,至于为什么有了命名管道还要保留匿名管道的问题,我想主要是因为父子进程通信的方式已然被广泛的采用,而这种方式无疑要比命名管道消耗的资源更少,效率更高,就像自己自己写的进程调用了自己写的一个函数一样。

3. 命名管道(Pipe)
      命名管道不仅可以在本机上实现两个进程间的通信,还可以跨网络实现两个进程间的通信,就像我现在正使用MSN跟我远方的同学聊天一样!其实如果你用过Socket编写网络程序的话,你就会明白所谓的命名管道之间的通信就相当于把计算机低层网络网络通信部分给封装了起来,使用户使用起来不必了解那么多网络通信的知识,总之一句话就是用起来简单,其实我们在为别人提供函数库的时候都应该遵循这个规律,把低层烦琐,复杂,抽象的都封装起来,对高层提供统一的接口.
      在Windows2000/NT以后,都可以在创建管道时指定据有访问权限的用户使用管道,进一步保证了安全性,而如果你要是自己使用Socket实现这个功能的话就太麻烦了,当然很多程序员已然会自己实现它,他们的理由很可能是因为windows都不安全.命名管道实现进程间的通信也跟网络通信一样是C/S结构的,服务器进程负责创建命名管道及接受客户机的连接请求,就象socket中Server部分要实现bind、linstening和accept一样, 而客户端只负责连接,对应于socket中的connect一样.
      命名管道提供了两种基本通信模式:字节模式和消息模式,在字节模式下,数据以一个连续的字节流的形式在server于client之间流动,而消息模式下,客户机和服务器则通过一系列不连续的数据单位进行数据收发,每次管道上发出了一条消息后,它必须作为一条完整的消息读入,是不是很像TCP和UDP.
     
      HANDLECreateNamePipe(....)
      创建命名管道的API,我依然不想解释它的具体参数含义,我只解释它的第一个参数LPCTSTRlpName,它的字符串格式是"\\\\.\\pipe\\pipename"
为 什么这么多\,其实一共就4个,可你看到有8个是因为C/C++中字符串中如果包含一个'\'就必须"\\"才能表达它的意思,你还记得吗?它的实际格式是"\\.\pipe\pipename",它的'.'表示的是本机地址,如果是要与远程服务器连接,就在这个'.'处指定服务器的名称,接下来的pipe是固定的不要改,pipename就是你要命名的管道名字.

      BOOLConnectNamedPipe(HANDLE hNamePipe, LPOVERLAPPED lpOverlapped)
      初看这个函数的名字你一定认为这个是客户端用来连接服务器管道的,事物的表面总是欺骗我们,恰恰相反它是服务器用来等待远程连接的,类似于socket中的listen.
      BOOLWaitNamedPipe(LPCTSTR lpNamedPipeName, DWORD nTimeOut)
      有了上面那个函数的教训,如果我问题这个函数是作什么的你一定不会立即回答,是的,它是在客户端来判断是否有可以利用的命名管道的,每个客户端最开始都应该使用它判断一些,就像socket中的connect要判断一下server是否已经启动了.

      下面是服务器代码:
      classCNamePipeServer
      {
      ...
      private:
            HANDLEm_hPipe;
         
     
      voidCNamePipeServer::NamePipeCreated()
      {
            m_hPipe =CreateNamedPipe("\\\\.\\pipe\\MyPipe", PIPE_ACCESS_DUPLEX |FILE_FLAG_OVERLAPPED,
                              0, 1, 1024,1024, 0, NULL);
            if(INVALID_HANDLE_VALUE == m_hPipe)
            {
                  return;
            }
            HANDLEhEvent;
            hEvent =CreateEvent(NULL, TRUE, FALSE, NULL); // 创建一个事件
            if(INVALID_HANDLE_VALUE == hEvent)
            {
                  return;
            }

            OVERLAPPEDovlap;
            ZeroMemory(&ovlap, sizeof(OVERLAPPED));
            ovlap。hEvent = hEvent;
           
            if(!ConnectNamePipe(hPipe, &ovlap))
            {
                 
                  if(ERROR_IO_PENDING != GetLastError())
                  {
                        ....
                        return;
                  }
            }
           
            if(WAIT_FAILED == WaitForSingleObject(hEvent, INFINTE))
            {
                  ...
                  return;
            }
            CloseHandle(hEvent);
      }
void CNamePipeServer::OnReadPipe()
      voidCNamePipeServer::OnWritePipe()
      命名管道读写的方式与匿名管道的相同, 不再冗述。
      客户端实现:
      claseCNamePipeClient
      {
      ...
      private:
            HANDLEm_hPipe;
      }
      voidCNamePipeClient::OnPipeConnect()
      {
            if(!WaitNamedPipe("\\\\.\\pipe\\MyPipe", NMPWAIT_WAIT_FOREVER))
            {
                  return;
            }
           
            m_hPipe =CreateFile("\\\\.\\pipe\\MyPipe", GENERIC_READ | GENERIC_WRITE, 0,NULL, OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL, NULL);
            if(INVALID_HANDLE_VALUE == m_hPipe)
            {
                  reutrn;
            }
      }
      voidCNamePipeClient::OnReadPipe()
      voidCNamePipeClient::OnWritePipe()
      同上.

      命名管道我没有在实际中使用过,所以对它的一些特点理解的并不是很透彻,不能为大家提供更多的建议了.
4. 邮槽(Mailslot)
      邮槽是基于广播通信设计出来的,采用不可靠的数据传输,它是一种单向通信机制,创建邮槽的服务器进程读取数据,打开邮槽的客户端进程写入数据,据说邮槽广泛的应用于网络会议系统.
      服务器进程
      voidMailslotRecv()
      {
            HANDLEhMailslot;
           
            hMailslot =Createmailslot("\\\\.\\mailsolt\\MyMailslot", 0,MAILSLOT_WAIT_FOREVER, NULL);
            if(INVALID_HANDLE_VALUE
== hMailslot)
            {
                  return;
            }

            charbuf[100]
            DWORDdwRead;
           
            if(!ReadFile(hMailslot, buf, 100, &dwRead, NULL))
            {
                  ...
                  return;
            }
            MessageBox(buf);
            CloseHandle(hMailslot);
      }
      客户端进程:
      voidMailslotSnd(char *pBuf)
      {
            ASERRT(pBuf!= NULL);
            HANDLEhMailslot;
           
            hMailslot =CreateFile("\\\\.\\mailslot\\MyMailslot", ENERIC_READ |GENERIC_WRITE, 0, NULL,                              OPEN_EXISTING,      FILE_ATTRIBUTE_NORMAL, NULL);
            if(INVALID_HANDLE_VALUE == hMailslot)
            {
                  return;
            }

            DWORDdwWrite;
            if(!WriteFile(hMailslot, pBuf, strlen(pBuf) + 1, &dwWrite,NULL))
            {
                  ....
                  return;
            }
            CloseHandle(hMailslot);
           
      }

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值