选择特殊符号
选择搜索类型
请输入搜索
我们会对命名管道已知的所有问题及限制进行总结.
在一个程序中实现命名管道的创建与使用
此示例代码意在体现出命名管道与普通管道的区别,命名管道是以一个普通文件的形式出现的,包括三个文件,创建命名管道、写管道、读管道
1. 创建命名管道
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
int main(void){
char buf[80];
int fd;
unlink("zieckey_fifo");
mkfifo("zieckey_fifo",0777);}写命名管道代码
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
int main(void){
int fd;
char s[]="Hello!\n";
fd=open("zieckey_fifo",O_WRONLY);
while(1){
write(fd,s,sizeof(s));
sleep(1);}
return0;
}
读命名管道代码
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
int main(void){
int fd;
char buf[80];
fd = open("zieckey_fifo",O_RDONLY);
while(1){
read(fd,buf,sizeof(buf));
printf("%s\n",buf);
sleep(1); }
return0;
}
命名管道(NamedPipe)是服务器进程和一个或多个客户进程之间通信的单向或双向管道。不同于匿名管道的是:命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。命名管道提供了相对简单的编程接口,使通过网络传输数据并不比同一计算机上两进程之间通信更困难,不过如果要同时和多个进程通信它就力不从心了。
在计算机编程里,命名管道是一种从一个进程到另一个进程用内核对象来进行信息传输。和一般的管道不同,命名管道可以被不同进程以不同的方式方法调用(可以跨权限、跨语言、跨平台)。只要程序知道命名管道的名字,发送到命名管道里的信息可以被一切拥有指定授权的程序读取,但对不具有制定授权的。命名管道是一种FIFO(先进先出,First-In First-Out)对象。
假定有一台服务器保存着公司的秘密,我们要求只有公司的管理人员才能访问或编辑这些秘密(高权限信息)。而在自己的工作网络,公司内的每名员工都可看到网络上的这台计算机(低权限访问)。然而,我们并不希望普通员工(低权限组)取得对机密材料的访问权。公司要求我们开发一个数据管理系统,此系统只允许一个指定的用户组(高权限组)进行操作。
在这种情况下,命名管道等包含ACL的数据通信系统可发挥作用。因为我们可利用ACL,使只有拥有特别权限的用户(高权限组)与指定服务器发送控制信息,以此对公司的秘密进行操作。在此要记住的一个重点是:将命名管道作为一种网络编程方案使用时,它实际上建立一个简单的客户机/服务器数据通信体系(通常是TCP/IP,TCP协议具有良好的稳定性与数据安全性)。
要学习开发一组命名管道应用(可能是一个进程的不同线程、同一个可执行性文件的不同实例或完全不同的程序),首先要了解命名管道的命名规范(命名协议),然后了解基本的管道类型,接着实现一组简单的服务器应用与一个客户端应用。然后再以它为基础,深入研究高级的服务器编程技术,了解更复杂的通信系统与简单的通信协议。
在一个程序中实现命名管道的创建与使用。
#include#include #include #include intmain( void) { charbuf[80]; intfd; unlink("zieckey_fifo");mkfifo("zieckey_fifo",0777); if( fork()>0) { chars[]="Hello! "; fd=open("zieckey_fifo",O_WRONLY); write(fd,s,sizeof(s)); //close(fd); } else { fd=open("zieckey_fifo",O_RDONLY); read(fd,buf,sizeof(buf));printf("Themessagefromthepipes:%s ",buf); //close(fd); } return0; } /*执行结果为 Themessagefromthepipes:Hello! 并且可以在程序执行目录生成管道文件zieckey_fifo */
此示例代码意在体现出命名管道与普通管道的区别,命名管道是以一个普通文件的形式出现的,包括三个文件操作:创建命名管道、写管道、读管道。
1. 创建命名管道
#include#include #include #include intmain(void){charbuf[80];intfd;unlink("zieckey_fifo");mkfifo("zieckey_fifo",0777);}
写命名管道代码
#include#include #include #include intmain(void){intfd;chars[]="Hello! ";fd=open("zieckey_fifo",O_WRONLY); while (1){ write(fd,s,sizeof(s)); sleep(1); }return0;}
读命名管道代码
#include#include #include #include intmain(void){intfd;charbuf[80];fd=open("zieckey_fifo",O_RDONLY);while(1){read(fd,buf,sizeof(buf));printf("%s ",buf);sleep(1);}return0;}
PDMS管道命名规则
PLEASE NOTE: CADCentre has a policy of continuing product development: therefore, the information contained in this document may be subject to change without notice. CADCENTRE MAKES NO WARRANTY OF ANY KIND WITH REGARD TO THIS DOCUMENT, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. While every effort has been made to verify the
命名管道程序设计的实现
1.命名管道Server和Client间通信的实现流程
(1)建立连接:服务端通过函数CreateNamedPipe创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。如果在已定义超时值变为零以前,有一个实例管道可以使用,则创建成功并返回管道句柄,并用以侦听来自客户端的连接请求,该功能通过ConnectNamedPipe函数实现。
另一方面,客户端通过函数WaitNamedPipe使服务进程等待来自客户的实例连接,如果在超时值变为零以前,有一个管道可以为连接使用,则WaitNamedPipe将返回True,并通过调用CreateFile或CallNamedPipe来呼叫对服务端的连接。此时服务端将接受客户端的连接请求,成功建立连接,服务端ConnectNamedPipe返回True,客户端CreateFile将返回一指向管道文件的句柄。
从时序上讲,首先是客户端通过WaitNamedPipe使服务端的CreateFile在限时时间内创建实例成功,然后双方通过ConnectNamedPipe和CreateFile成功连接,并返回用以通信的文件句柄,此时双方即可进行通信。
(2)通信实现:建立连接之后,客户端与服务器端即可通过ReadFile和WriteFile,利用得到的管道文件句柄,彼此间进行信息交换。
(3)连接终止:当客户端与服务端的通信结束,或由于某种原因一方需要断开时,客户端应调用CloseFile,而服务端应接着调用DisconnectNamedPipe。当然服务端亦可通过单方面调用DisconnectNamedPipe终止连接。最后应调用函数CloseHandle来关闭该管道。
命名管道服务器端和客户端代码实现
客户端
HANDLE CltHandle;
char pipenamestr[30];
sprintf(pipenamestr,″\\servername\pipe\pipename″)
if (WaitNamedPipe( pipenamestr, NMPWAIT—WAIT—FOREVER)==FALSE
// 管道名要遵循UNC,格式为\ \.\pipe\pipname,名字不分大小写。
AfxMessageBox(″操作失败,请确定服务端正确建立管道实例!″);
Else
CltHandle=CreateFile(pipenamestr, GENERIC—READ|GENERIC—WRITE, FILE—SHARE—READ| FILE—SHARE—WRITE,NULL, OPEN—EXISTING,
//为了与命名管道连接,此参数应一直为OPEN—EXISTING
FILE—ATTRIBUTE—ARCHIVE|FILE—FLAG—WRITE—THROUGH,
// FILE—FLAG—WRITE—THROUGH会使管道WriteFile调用处于阻塞状态,直到数据传送成功。
NULL);
If (CltHandle== INVALID—HANDLE—VALUE)
AfxMessageBox(″管道连接失败″);
Else
DoUsertTransactInfo();
//执行用户自定义信息交换函数——从管道读、写信息。
……
服务端
HANDLE SvrHandle;
char pipenamestr[30];
sprintf(pipenamestr,″\\.\pipe\pipename″)
SvrHandle=CreateNamedPipe(pipenamestr,
PIPE—ACCESS—DUPLEX|FILE—FLAG—WRITE—THROUGH,
//阻塞模式,这种模式仅对″字节传输管道″操作有效。
FILE—WAIT|PIPE—TYPE—BYTE,
//字节模式
PIPE—UNLIMITED—INSTANCES,
128,128,
NULL,NULL);
// SECURITY—ATTRIBUTES结构指针,描述一个新管道,确定子进程的继承权,如果为NULL则该命名管道不能被继承。
If (SvrHandle==INVALID—HANDLE—VALUE)
AfxMessageBox(″管道创建失败,请确定客户端提供连接可能!″);
Else
If (ConnectNamedPipe(SvrHandle,NULL)==FALSE)
AfxMessageBox(″建立连接失败!″);
Else
DoUsertTransactInfo();
//用户自定义信息交换函数
……
使用灵活性
命名管道具有很好的使用灵活性,表现在:
1) 既可用于本地,又可用于网络。
2) 可以通过它的名称而被引用。
3) 支持多客户机连接。
4) 支持双向通信。
5) 支持异步重叠I/O操作。
不过,当前只有Windows NT支持服务端的命名管道技术。
命名管道是由服务器端的进程建立的,管道的命名必须遵循特定的命名方法,就是 "\\.\pipe\管道名",当作为客户端的进程要使用时,使用"\\计算机名\\pipe\管道名" 来打开使用,具体步骤如下:
服务端通过函数 CreateNamedPipe 创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。 服务端侦听来自客户端的连接请求,该功能通过 ConnectNamedPipe 函数实现。 客户端通过函数 WaitNamedPipe 来等待管道的出现,如果在超时值变为零以前,有一个管道可以使用,则 WaitNamedPipe 将返回 True,并通过调用 CreateFile 或 CallNamedPipe 来呼叫对服务端的连接。 此时服务端将接受客户端的连接请求,成功建立连接,服务端 ConnectNamedPipe 返回 True 建立连接之后,客户端与服务器端即可通过 ReadFile 和 WriteFile,利用得到的管道文件句柄,彼此间进行信息交换。 当客户端与服务端的通信结束,客户端调用 CloseFile,服务端接着调用 DisconnectNamedPipe。最后调用函数CloseHandle来关闭该管道。 由于命名管道使用时作为客户端的程序必须知道管道的名称,所以更多的用在同一"作者"编写的服务器/工作站程序中,你不可能随便找出一个程序来要求它和你写的程序来通过命名管道通信。而匿名管道的使用则完全不同,它允许你和完全不相干的进程通信,条件是这个进程通过控制台"console"来输入输出,典型的例子是老的 Dos 应用程序,它们在运行时 Windows 为它们开了个 Dos 窗口,它们的输入输出就是 console 方式的。还有一些标准的 Win32 程序也使用控制台输入输出,如果在 Win32 编程中不想使用图形界面,你照样可以使用 AllocConsole 得到一个控制台,然后通过 GetStdHandle 得到输入或输出句柄,再通过 WriteConsole 或 WriteFile 把结果输出到控制台(通常是一个象 Dos 窗口)的屏幕上。虽然这些程序看起来象 Dos 程序,但它们是不折不扣的 Win32 程序,如果你在纯 Dos 下使用,就会显示"The program must run under Windows!"。
一个控制台有三个句柄:标准输入、标准输出和和标准错误句柄,标准输入、标准输出句柄是可以重新定向的,你可以用匿名管道来代替它,这样一来,你可以在管道的另一端用别的进程来接收或输入,而控制台一方并没有感到什么不同,就象 Dos 下的 > 或者 < 可以重新定向输出或输入一样。通常控制台程序的输入输出如下:
(控制台进程output) write ----> 标准输出设备(一般是屏幕)
(控制台进程input) read <---- 标准输入设备(一般是键盘)
而用管道代替后:
(作为子进程的控制台进程output) write ----> 管道1 ----> read (父进程)
(作为子进程的控制台进程input) read <----> 管道2 <---- write (父进程)
使用匿名管道的步骤如下:
使用 CreatePipe 建立两个管道,得到管道句柄,一个用来输入,一个用来输出 准备执行控制台子进程,首先使用 GetStartupInfo 得到 StartupInfo 使用第一个管道句柄代替 StartupInfo 中的 hStdInput,第二个代替 hStdOutput、hStdError,即标准输入、输出、错误句柄 使用 CreateProcess 执行子进程,这样建立的子进程输入和输出就被定向到管道中 父进程通过 ReadFile 读第二个管道来获得子进程的输出,通过 WriteFile 写第一个管道来将输入写到子进程 父进程可以通过 PeekNamedPipe 来查询子进程有没有输出 子进程结束后,要通过 CloseHandle 来关闭两个管道。 下面是具体的说明和定义:
1. 建立匿名管道使用 CreatePipe 原形如下:
BOOL CreatePipe(
PHANDLE hReadPipe, // address of variable for read handle
PHANDLE hWritePipe, // address of variable for write handle
LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes
DWORD nSize // number of bytes reserved for pipe
);
当管道建立后,结构中指向的 hReadPipe 和 hWritePipe 可用来读写管道,当然由于匿名管道是单向的,你只能使用其中的一个句柄,参数中的 SECURITY_ATTRIBUTES 的结构必须填写,定义如下:
typedef struct_SECURITY_ATTRIBUTES{
DWORD nLength: //定义以字节为单位的此结构的长度
LPVOID lpSecurityDescriptor; //指向控制这个对象共享的安全描述符,如果为NULL这个对象将被分配一个缺省的安全描述
BOOL bInheritHandle; //当一个新过程被创建时,定义其返回是否是继承的.供系统API函数使用.
}SECURITY_ATTRIBUTES;
2. 填写创建子进程用的 STARTUPINFO 结构,一般我们可以先用 GetStartupInfo 来填写一个缺省的结构,然后改动我们用得到的地方,它们是:
hStdInput -- 用其中一个管道的 hWritePipe 代替 hStdOutput、hStdError -- 用另一个管道的 hReadPipe 代替 dwFlags -- 设置为 STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW 表示输入输出句柄及 wShowWindow 字段有效 wShowWindow -- 设置为 SW_HIDE,这样子进程执行时不显示窗口。 填写好以后,就可以用 CreateProcess 来执行子进程了,具体有关执行子进程的操作可以参考上一篇教程《进程控制》
3. 在程序中可以用 PeekNamedPipe 查询子进程有没有输出,原形如下:
BOOL PeekNamedPipe(
HANDLE hNamedPipe, // handle to pipe to copy from
LPVOID lpBuffer, // pointer to data buffer
DWORD nBufferSize, // size, in bytes, of data buffer
LPDWORD lpBytesRead, // pointer to number of bytes read
LPDWORD lpTotalBytesAvail, // pointer to total number of bytes available
LPDWORD lpBytesLeftThisMessage // pointer to unread bytes in this message
);
我们可以将尝试读取 nBuffersize 大小的数据,然后可以通过返回的 BytesRead 得到管道中有多少数据,如果不等于零,则表示有数据可以读取。
4. 用 ReadFile 和 WriteFile 来读写管道,它们的参数是完全一样的,原形如下:
ReadFile or WriteFile(
HANDLE hFile, // handle of file to read 在这里使用管道句柄
LPVOID lpBuffer, // address of buffer that receives data 缓冲区地址
DWORD nNumberOfBytesToRead, // number of bytes to read 准备读写的字节数
LPDWORD lpNumberOfBytesRead, // address of number of bytes read,实际读到的或写入的字节数
LPOVERLAPPED lpOverlapped // address of structure for data 在这里用 NULL
);
5. 用 CloseHandle 关闭管道一和管道二的 hReadPipe和 hWritePipe 这四个句柄。
下面给出了一个例子程序,这个程序是上篇教程《进程控制》的例子的扩充,如果你对有的 api 感到陌生的话,请先阅读上一篇教程。