一种网络进程间通信的方式—— 管道
摘要: 文章主要介绍了计算机网络进程间通信的必要性以及进程间通信所采用的几种方式,重点说明了管道通信的原理及命名管道的实现方法。
关键词:管道 命名管道 进程
一、概述
进程间通信的主要目的是实现同一计算机系统内部相互协作的进程之间的数据共享与信息交换。由于这些进程处于同一应用软件和硬件环境下,因而利用操作系统提供的编程接口,用户可以方便地在程序中实现这种通信。而应用程序间通信的主要目的是实现不同计算机系统中相互协作的应用程序之间的数据共享与信息交换。由于应用程序分别运行在不同计算机系统中,所以它们之间要通过网络间的协议才能实现数据共享与信息交换。进程间的通信和应用程序间的通信,以及它们相应的实现技术既有许多相同之处,也有各自的特点,因为即使是同一类型的通信也会有多种实现的方法,以适应不同情况的需要。
二、 进程间通信的必要性
用户提交给计算机的任务最终都是通过一个个进程来完成的。在一组并发进程中的任何两个进程之间,如果都不存在公共变量,则称该组进程为不相交的。在不相交的进程组中,每个进程都独立于其它进程,它的运行环境与顺序程序一样,而且也不为别的进程所改变,其运行的结果是确定的,不会发生与时间相关的错误。
但是,在实际中,并发进程的各个进程之间并不是完全互相独立的,它们之间往往存在着相互制约的关系。进程之间的相互制约关系表现为以下两种方式:
● 间接相互制约 指共享CPU方式;
● 直接相互制约 指竞争和协作方式。
其中,竞争指进程对共享资源的竞争。为保证进程互斥地访问共享资源,各进程必须互斥地进入各自的临界段。
协作指进程之间交换数据。为完成一个共同任务而同时运行的一组进程称为同组进程,它们之间必须交换数据,以达到协作完成任务的目的。
共享CPU问题由操作系统的进程调度来解决,进程间的竞争和协作由进程间的通信来完成。
Win32下提供的进程间通信方式有:剪贴板、COM/DCOM、DDE、文件映射、邮件槽、管道、远地过程调用、网络套接字和消息。
三、无名管道和命名管道
无名管道实际上是内存中的一个临时存储区,它由系统安全控制,并且独立于创建它的进程的内存区。管道对数据采用先进先出方式管理,并严格按顺序操作,如不能对管道进行搜索,管道中的信息只能读一次。
无名管道只能用于两个相互协作的进程之间的通信,并且访问无名管道的进程必须有共同的祖先。
命名管道的操作与无名管道类似,不同的地方在于使用有名管道的进程不需要具有共同的祖先,其它进程,只要知道该管道的名字,就可以访问它。管道非常适合进程之间快速交换信息。
四、命名管道的连接和通讯方式
在服务器端第一次创建命名管道后等待连接,当客户端连接成功后服务器端的命名管道就用作通讯用途。如果需要再次等待连接,服务器端就需要再次打开命名管道(创建一个命名管道的实例)并等待连接。
对于客户端每次打开命名管道后可建立与服务器间的连接,然后就可以利用命名管道进行通信,如果需要建立第二个连接,则需要再次打开管道并再次建立连接。
创建命名管道时需要指定一个主机名和管道名,客户端可以采用以下格式完成:
\\[host_name]\pipe\[pipe_name]\;也可以是:
\\.\pipe\pipe_name\(其中:. 表示本机)。
而服务器端只能够以指定本机作为主机名,即只能使用 \\.\ pipe_name\格式。此外,需要注意的是:在同一主机上的管道名称是唯一的,一个命名管道一旦被创建,就不允许再创建相同名称的管道。
服务器方通过下列方式创建命名管道和打开已经存在的命名管道:
HANDLE CreateNamedPipe(
LPCTSTR lpName, //管道
DWORD dwOpenMode, // 打开方式
DWORD dwPipeMode, // 管道类型
DWORD nMaxInstances, // 管道的最大数量
DWORD nOutBufferSize, // 写缓冲区大小
DWORD nInBufferSize, // 读缓冲区大小
DWORD nDefaultTimeOut, // 最长的等待时间
LPSECURITY_ATTRIBUTES lpSecurityAttributes // 安全属性
);
其中,lpName为管道名称,dwOpenMode为创建方式,可以是下面值的组合:
PIPE_ACCESS_INBOUND:管道只能用作接收数据。
PIPE-ACCESS-OUTBOUND:管道只能用作发送数据。
PIPE-ACCESS-DUPLEX:管道既可以发送也可以接收数据(只能取三个中的一个)。
FILE-FLAG-WRITE-THROUGH:管道用于同步发送和接收数据,只有当数据被发送到目标地址时,发送函数才会返回。如果不设置这个参数,那么在系统内部对于命名管道的处理上可能会因为减少网络负荷而在数据积累到一定量时才发送,并且对于发送函数的调用会马上返回。
FILE-FLAG-OVERLAPPED:管道可以用于异步输入和输出,异步读写的有关方法和文件异步读写是相同的。
dwPipeMode指定管道类型,可以是下面值的组合:
PIPE-TYPE-BYTE:数据在通过管道发送时作为字节流发送,不能与PIPE-READMODE-MESSAGE共用。
PIPE-TYPE-MESSAGE:数据在通过管道发送时作为消息发送,不能与PIPE-READMODE-BYTE共用。
PIPE-READMODE-BYTE:在接收数据时接收字节流。
PIPE-READMODE-MESSAGE:在接收数据时接收消息。
PIPE-WAIT:使用等待模式,在读、写和建立连接时都需要管道的另一方完成相应动作后才会返回。
PIPE-NOWAIT:使用非等待模式,在读、写和建立连接时不需要管道的另一方完成相应动作后就可立即返回。
nMaxInstances为管道的最大数量,在第一次建立服务器方管道时,这个参数表明该管道可以同时存在的数量。PIPE- UNLIMITED-INSTANCES表明不对数量进行限制。nOutBufferSize和nInBufferSize表示缓冲区的大小。nDefaultTimeOut表示在等待连接时最长的等待时间(以毫秒为单位)。如果在创建时设置为NMPWAIT-USE-DEFAULT-WAIT,表明无限制的等待,而以后服务器方的其他管道实例也需要设置相同的值。lpSecurityAttributes为安全属性,一般设置为NULL。如果创建或打开失败,则返回INVALID-HANDLE-VALUE。可以通过GetLastError查到错误。
客户方通过下列方式创建客户端命名管道:
HANDLE CreateFile(
LPCTSTR lpFileName, // 文件名
DWORD dwDesiredAccess, // 存取方式
DWORD dwShareMode, // 共享方式
LPSECURITY-ATTRIBUTES lpSecurityAttributes, // 安全属性
DWORD dwCreationDisposition, // 创建方式
DWORD dwFlagsAndAttributes, // 文件属性
HANDLE hTemplateFile // 临时文件据柄
);
其中,CreateFile可以有很多用途,可以用来创建文件、管道、邮件槽、目录等,这里介绍用CreateFile打开客户端命名管道;lpFileName用于指明管道名称;dwDesiredAccess用于表明使用方式,可以使用下面的值:
GENERIC-READ:打开一个只用于读的管道。
GENERIC-WRITE:打开一个只用于写的管道。
GENERIC-READ | GENERIC-WRITE:打开一个用于读和写的管道。
dwShareMode指定共享方式,一般指定为0;lpSecurityAttributes为安全属性,一般设置为NULL; dwCreationDisposition设置为OPEN_EXISTING; dwFlagsAndAttributes设置为FILE-ATTRIBUTE-NORMAL, 还可以设置为FILE-FLAG-OVERLAPPED来进行异步通讯;hTemplateFile设置为NULL。如果打开失败,则返回INVALID-HANDLE-VALUE,并 可以通过GetLastError找到存在的错误。
此外客户方可以利用下列方式创建一个发送消息的管道:
BOOL CallNamedPipe(
LPCTSTR lpNamedPipeName, // 管道名
LPVOID lpInBuffer, // 写缓冲区
DWORD nInBufferSize, // 写缓冲区大小
LPVOID lpOutBuffer, // 读缓冲区大小
DWORD nOutBufferSize, // 读缓冲区大小
LPDWORD lpBytesRead, // 可读字节数
DWORD nTimeOut // 最长的等待时间
);
管道的连接管理,客户方在调用CreateFile后可立即建立与服务器的连接,而服务器方一旦管道打开或创建后,就可以用下列方式等待客户端的连接建立 :
BOOLConnectNamedPipe(HANDLE hNamedPipe,
LPOVERLAPPED lpOverlapped);
如果希望在服务器方检测是否有连接到达,可以调用:
BOOL WaitNamedPipe(LPCTSTR lpNamedPipeName,
DWORD nTimeOut );
这里的lpNamePipeName直接使用创建管道时的名称。如果在服务器方希望关闭连接,则调用BOOL DisconnectNamedPipe( HANDLE hNamedPipe ),一旦连接被关闭,服务器方可以再次调用ConnectNamedPipe来建立连接;如果要关闭管道,则可直接调用CloseHandle。请注意:这里提到的关闭管道和关闭连接具有不同的意思,在同一个管道上可以依次反复建立连接,而且可以减小系统的负荷。如果指定了管道最大数量限制,那么在打开的管道达到最大限制后如果不关闭旧管道,就无法打开新管道。
客户方则无法关闭连接,而只能直接调用CloseHandle关闭管道。
数据的发送,不论是服务器还是客户方都可以通过ReadFile和WriteFile进行管道读写来达到通讯的目的。
下面是一个例子,服务器方创建或打开一个管道并读入对方发送的数据,将小写字母转换成大写字母后返回;而客户方创建一个到服务器的连接并发送一个字符串,同时读回经过转换的数据。
在使用这个例子时,只可运行三个服务端进程,如果运行第四个进程,就会因达到管道最大数量限制而导致打开管道失败。
服务端程序:
void CNamed-pipeDlg::OnCreateP()
{
DWORD dwTO = NMPWAIT-USE-DEFAULT-WAIT;//设置连接等待时间
HANDLEhSvr=
CreateNamedPipe("\\\\.\\pipe\\test-pipe\\",PIPE-ACCESS-DUPLEX,
PIPE-TYPE-BYTE,3,256,256,dwTO,NULL);
if( INVALID-HANDLE-VALUE == hSvr)
AfxMessageBox("创建管道失败!");
else
{
if (ConnectNamedPipe(hSvr,NULL))
{
BYTE bRead;
DWORD dwRead,dwWritten;
while (ReadFile(hSvr,&bRead,1,&dwRead,NULL))
{
if(bRead >= 'a' && bRead $lt;='z')
bRead = 'A'+ (bRead-'a');
WriteFile(hSvr,&bRead,1,&dwWritten,NULL);
}
}
else
AfxMessageBox("连接失败!");
CloseHandle(hSvr);
}
}
客户端程序:
void CNamed-pipe-cDlg::OnConn()
{
HANDLE hClient=CreateFile("\\\\.\\pipe\\test-pipe\\",GENERIC-WRITE
|GENERIC-READ,0,NULL,OPEN-EXISTING,
FILE-ATTRIBUTE-NORMAL,NULL);
if(hClient == INVALID-HANDLE-VALUE)
AfxMessageBox("打开管道失败!");
else
{
DWORD dwRead,dwWritten;
char szSend[10]="send...";
char szRecv[10];
for(int i=0;i<strlen(szSend)+1;i++)
{
WriteFile(hClient,szSend+i,1,&dwWritten,NULL);
ReadFile(hClient,szRecv+i,1,&dwRead,NULL);
}
CloseHandle(hClient); // 关闭管道
AfxMessageBox(szRecv);
}
}
该程序在VC5.0、Win98下调试通过。▲