操作系统实验四、使用命名管道实现进程通信

实验四:使用命名管道实现进程通信

一、实验目的

(1)了解Windows系统环境下的进程通讯机制
(2)熟悉Windows系统提供的进程通信API

二、实验准备知识:相关API函数介绍

1.建立命名通道

函数CreateNamePipe()创建一个命名管道实例,并返回该管道的句柄。

原型:

HANDLE CreateNamePipe(
LPCTSTR lpName,//命名管道的名字
DWORD dwOpenMode,//命名管道的访问模式
DWORD dwPipeMode,//命名管道的模式
DWORD nMaxInstances,//可创建实例的最大值
DWORD nInBufferSize,//以字节为单位的输入缓冲区的大小
DWORD nOutBufferSize,//以字节为单位的输出缓冲区的大小
DWORD nDefaultTimeOut,//默认超时时间
LPSECURITY_ATTRIBUTES lpSecurityAttributes//安全属性
);

参数说明:

lpName:为命名管道的名字,管道的命名方式为\ServerName\pipe\pipename](file:///\ServerName\pipe\pipename),其中ServerName为用命名管道通信时服务器的主机名或IP地址,pipename为命名管道的名字,用户可自行定义。

dwOpenMode:指出命名管道的访问模式。模式如表2-5所示。

dwPipeMode:指出管道的模式。模式如表2-6所示、

nMaxInstances:该命名管道可以创建实例的最大值。

nOutBufferSize:输出缓冲区的大小,以字节为单位。

nInBufferSize:输入缓冲区的大小,以字节为单位。

nDefaultTimeOut:默认的超时时间,以ms为单位。如果函数WaitNamePipe()指出NMWAIT_USE_DEFAULT_WAIT,每个管道实例必须指定同一值的名字。

lpSecurityAttributes:为管道指定安全属性,为NULL时,管道得到一个默认的安全描述符。

返回值:

如果管道创建成功,将返回服务器命名管道实例的句柄。如果失败,返回INVALID_HANDLE_VALUE,可以调用函数GetLastError()查询失败的原因;当返回ERROR_INVALID_PARAMETER时,表明参数nMaxInstances指定的值大于PIPE_UNLIMTED_INSTANCES。

2.连接命名管道

服务器用函数ConnectNamePipe()连接命名管道。创建后命名管道也等待客户端的连接,客户端可以使用函数CreateFile()和CallNamedPipe()进行连接。

原型:

BOOL ConnectNamedPipe(

HANDLE hNamePipe,     //命名管道实例句柄

LPOVERLAPPED lpOver lapped     //指向Overlapped结构的指针

);

参数说明:

hNamedPipe:为命名管道创建时得到的一个命名管道实例句柄。

lpOverlapped:指向Overlapped结构的指针,可设其为NULL。

返回值:

成功,将返回一个非0值;失败,系统返回0,可以调用函数GetLastError()查询失败的原因。

3.拆除命名管道的连接

函数DisconnectNamePipe()拆除命名管道服务器与客户端的连接。

原型:

BOOL DisconnectNamePipe(

HANDLE hNamePipe

);

参数说明:

hNamedPipe:为命名管道创建时得到的一个命名管道实例句柄。

返回值:

成功,将返回一个非0值;失败,系统返回0,可以调用函数GerLasrError()查询失败的原因。

4.客户端连接服务器已建立的命名管道

客户端使用函数CallNamePipe()连接服务器建立的命名管道。

原型:

BOOL CallNamePipe(

  LPCTSTR lpNamePipeName,      //命名管道的名字

  LPVOID lpInBuffer,          //输出数据缓冲区

  DWORD nInBufferSize,        //以字节为单位的输出数据缓冲区的大小

  LPVOID lpOurBuffer,         //输入数据缓冲区指针

  DWORD nOurBufferSize,      //以字节为单位的输入数据缓冲区的大小

  LPDWORD lpBytesRead,        //输入字节数指针

  DWORD nTimeOUT          //等待时间

);

参数说明:

lpNamedPipeName:命名管道的名字。

lpInBuffer:指出用于输出数据(向管道写数据)的缓冲区指针。

nInBufferSize:用于输出数据缓冲区的大小,以字节为单位。

lpOutBuffer:指出用于接收数据(从管道读出数据)的缓冲区指针。

nOutBufferSize:指向用于接收数据缓冲区的大小,以字节为单位。

lpBytesRead:一个32位的变量,改变量用于存储从管道读出的字节数。

nTimeOut:等待命名管道成为可用状态的时间,单位为ms。

返回值:

成功,将返回一个非0值;失败,系统返回0,可用调用函数GetLastError()查询失败的原因。

5.客户端等待命名管道

客户端使用函数WaitNamedPipe()等待服务器连接命名管道。

原型:

BOOL WaitNamedPipe(

LPCTSTR lpNamedPipeName,     //要等待的命名管道名

DWORD nTimeOut          //等待时间

);

参数说明:

lpNamedPipeName:要等待的命名管道的名字。

nTimeOut:等待命名管道成为可用状态的时间,单位为ms.

返回值:

在等待时间内要连接的命名管道可以使用,将返回一个非0值。在等待时间内要连接的命名管道不可以使用,系统返回0,可用调用GetLastError()查询失败的原因。

三、实验内容

使用命名管道完成两个进程之间的通信:

服务器端代码:

#include "stdafx.h"
#include "PipeServe.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/
// The one and only application object

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	int nRetCode = 0;
    int err; 
    bool rc; 
    HANDLE hPipeHandle1; 
      
    char lpName[] = "\\\\.\\pipe\\myPipe"; 
    char InBuffer[50] = ""; 
    char OutBuffer[50] = ""; 
    DWORD BytesRead, BytesWrite; 
      
    //创建一个命名管道  
    hPipeHandle1 = CreateNamedPipe((LPCTSTR)lpName,  
        PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | WRITE_DAC , 
        PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 20, 30,  
        NMPWAIT_USE_DEFAULT_WAIT,
		(LPSECURITY_ATTRIBUTES)NULL); 
      
    if((hPipeHandle1 == INVALID_HANDLE_VALUE) || (hPipeHandle1 == NULL)) 
    { 
        err = GetLastError(); 
        printf("Server Pipe Create Fail! err = %d\n", err); 
        exit(1);  
    } 
    else
        printf("Server Pipe Create Success!"); 
          
    while(TRUE) 
    { 
        //连接命名管道  
        rc = ConnectNamedPipe(hPipeHandle1, NULL); 
        if (rc == 0) 
        { 
            err = GetLastError(); 
            printf("Server Pipe Connect Fail! err = %d\n", err); 
            exit(2); 
        } 
        else
            printf("Server Pipe Connect Success!\n"); 
        strcpy(InBuffer, ""); 
		strcpy(OutBuffer, ""); 
        //向命名管道中读数据  
        rc = ReadFile(hPipeHandle1,InBuffer, sizeof(InBuffer), &BytesRead,  
        (LPOVERLAPPED)NULL); 
        if (rc == 0 && BytesRead == 0) 
        { 
            err = GetLastError(); 
            printf("Server Read Pipe Fail! err = %d\n", err); 
            exit(2); 
        } 
        else
            printf("Server Read Pipe Success!\nDATA from Client is = %s\n",  
            InBuffer); 
        rc = strcmp(InBuffer, "end"); 
        if (rc == 0) 
            break; 
        printf("Please Input Data to Send"); 
        scanf("%s", OutBuffer); 
        //向命名管道中写数据  
        rc = WriteFile(hPipeHandle1, OutBuffer, sizeof(OutBuffer), &BytesWrite,  
        (LPOVERLAPPED)NULL); 
        if (rc == 0) 
            puts("Server Write Pipe Fail!"); 
        else
            puts("Server Write Pipe Success!"); 
        //拆除与命名管道的连接  
        DisconnectNamedPipe(hPipeHandle1); 
        rc = strcmp(OutBuffer, "end"); 
        if (rc == 0) 
            break; 
    } 
    printf("Now Server be END!"); 
    CloseHandle(hPipeHandle1); 
    return nRetCode; 
}

客户端代码:

#include "stdafx.h"
#include "PipeClient.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/
// The one and only application object

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{

    bool rc = 0; 
      
    char lpName[] = "\\\\.\\pipe\\myPipe"; 
    char InBuffer[50] = ""; 
    char OutBuffer[50] = ""; 
    DWORD BytesRead; 
     int nRetCode = 0;     int err = 0; 
    while(1) 
    { 
        strcpy(InBuffer, "");
		strcpy(OutBuffer, ""); 
        printf("Input Data Please!"); 
        scanf("%s", InBuffer); 
        rc = strcmp(InBuffer, "end"); 
        if (rc == 0) 
        { 
            //连接命名管道  
            rc = CallNamedPipe(lpName, InBuffer, sizeof(InBuffer), OutBuffer,  
                sizeof(OutBuffer), &BytesRead, NMPWAIT_USE_DEFAULT_WAIT); 
            break; 
        } 
        //等待命名管道  
        rc = WaitNamedPipe(lpName, NMPWAIT_WAIT_FOREVER); 
        if (rc == 0) 
        { 
            err = GetLastError(); 
            printf("Wait Pipe Fail! err = %d\n", err); 
            exit(1); 
        } 
        else
            printf("Wait Pipe Success!\n"); 
        rc = CallNamedPipe(lpName, InBuffer, sizeof(InBuffer), OutBuffer,  
            sizeof(OutBuffer), &BytesRead, NMPWAIT_USE_DEFAULT_WAIT); 
        rc = strcmp(OutBuffer, "end");   
        if (rc == 0) 
            break; 
        if (rc == 0) 
        { 
            err = GetLastError(); 
            printf("Pipe Call Fail! err = %d\n", err); 
            exit(1); 
        } 
        else
            printf("Pipe Call Success!\nData from Server is %s\n", OutBuffer); 
    } 
    puts("Now Client to be End!"); 
    return nRetCode; 
}

四、实验结果与总结

image

这次实验使我对Windows系统提供的进程通信API有了一定的了解。

Windows提供的进程通讯工具:

文件映射
  文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待。因此,进程不必使用文件I/O操作,只需简单的指针操作就可读取和修改文件的内容。
  Win32 API允许多个进程访问同一文件映射对象,各个进程在它自己的地址空间里接收内存的指针。通过使用这些指针,不同进程就可以读或修改文件的内容,实现了对文件中数据的共享。
  应用程序有三种方法来使多个进程共享一个文件映射对象。
  (1)继承:第一个进程建立文件映射对象,它的子进程继承该对象的句柄。
  (2)命名文件映射:第一个进程在建立文件映射对象时可以给该对象指定一个名字(可与文件名不同)。第二个进程可通过这个名字打开此文件映射对象。另外,第一个进程也可以通过一些其它IPC机制(有名管道、邮件槽等)把名字传给第二个进程。
  (3)句柄复制:第一个进程建立文件映射对象,然后通过其它IPC机制(有名管道、邮件槽等)把对象句柄传递给第二个进程。第二个进程复制该句柄就取得对该文件映射对象的访问权限。
  文件映射是在多个进程间共享数据的非常有效方法,有较好的安全性。但文件映射只能用于本地机器的进程之间,不能用于网络中,而开发者还必须控制进程间的同步。
共享内存
  Win32 API中共享内存(Shared Memory)实际就是文件映射的一种特殊情况。进程在创建文件映射对象时用0xFFFFFFFF来代替文件句柄(HANDLE),就表示了对应的文件映射对象是从操作系统页面文件访问内存,其它进程打开该文件映射对象就可以访问该内存块。由于共享内存是用文件映射实现的,所以它也有较好的安全性,也只能运行于同一计算机上的进程之间。
匿名管道
  管道(Pipe)是一种具有两个端点的通信通道:有一端句柄的进程可以和有另一端句柄的进程通信。管道可以是单向-一端是只读的,另一端点是只写的;也可以是双向的一管道的两端点既可读也可写。
  匿名管道(Anonymous Pipe)是 在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。通常由父进程创建管道,然后由要通信的子进程继承通道的读端点句柄或写 端点句柄,然后实现通信。父进程还可以建立两个或更多个继承匿名管道读和写句柄的子进程。这些子进程可以使用管道直接通信,不需要通过父进程。
  匿名管道是单机上实现子进程标准I/O重定向的有效方法,它不能在网上使用,也不能用于两个不相关的进程之间。
命名管道
  命名管道(Named Pipe)是服务器进程和一个或多个客户进程之间通信的单向或双向管道。不同于匿名管道的是命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。
  命名管道提供了相对简单的编程接口,使通过网络传输数据并不比同一计算机上两进程之间通信更困难,不过如果要同时和多个进程通信它就力不从心了。
邮件槽
  邮件槽(Mailslots)提 供进程间单向通信能力,任何进程都能建立邮件槽成为邮件槽服务器。其它进程,称为邮件槽客户,可以通过邮件槽的名字给邮件槽服务器进程发送消息。进来的消 息一直放在邮件槽中,直到服务器进程读取它为止。一个进程既可以是邮件槽服务器也可以是邮件槽客户,因此可建立多个邮件槽实现进程间的双向通信。
  通过邮件槽可以给本地计算机上的邮件槽、其它计算机上的邮件槽或指定网络区域中所有计算机上有同样名字的邮件槽发送消息。广播通信的消息长度不能超过400字节,非广播消息的长度则受邮件槽服务器指定的最大消息长度的限制。
  邮件槽与命名管道相似,不过它传输数据是通过不可靠的数据报(如TCP/IP协议中的UDP包)完成的,一旦网络发生错误则无法保证消息正确地接收,而命名管道传输数据则是建立在可靠连接基础上的。不过邮件槽有简化的编程接口和给指定网络区域内的所有计算机广播消息的能力,所以邮件槽不失为应用程序发送和接收消息的另一种选择。
剪贴板
  剪贴板(Clipped Board)实质是Win32 API中一组用来传输数据的函数和消息,为Windows应用程序之间进行数据共享提供了一个中介,Windows已建立的剪切(复制)-粘贴的机制为不同应用程序之间共享不同格式数据提供了一条捷径。当用户在应用程序中执行剪切或复制操作时,应用程序把选取的数据用一种或多种格式放在剪贴板上。然后任何其它应用程序都可以从剪贴板上拾取数据,从给定格式中选择适合自己的格式。
  剪贴板是一个非常松散的交换媒介,可以支持任何数据格式,每一格式由一无符号整数标识,对标准(预定义)剪贴板格式,该值是Win32 API定义的常量;对非标准格式可以使用Register Clipboard Format函数注册为新的剪贴板格式。利用剪贴板进行交换的数据只需在数据格式上一致或都可以转化为某种格式就行。但剪贴板只能在基于Windows的程序中使用,不能在网络上使用。
动态数据交换
  动态数据交换(DDE)是使用共享内存在应用程序之间进行数据交换的一种进程间通信形式。应用程序可以使用DDE进行一次性数据传输,也可以当出现新数据时,通过发送更新值在应用程序间动态交换数据。
  DDE和剪贴板一样既支持标准数据格式(如文本、位图等),又可以支持自己定义的数据格式。但它们的数据传输机制却不同,一个明显区别是剪贴板操作几乎总是用作对用户指定操作的一次性应答-如从菜单中选择Paste命令。尽管DDE也可以由用户启动,但它继续发挥作用一般不必用户进一步干预。DDE有三种数据交换方式:
  (1) 冷链:数据交换是一次性数据传输,与剪贴板相同。
  (2) 温链:当数据交换时服务器通知客户,然后客户必须请求新的数据。
  (3) 热链:当数据交换时服务器自动给客户发送数据。
  DDE交换可以发生在单机或网络中不同计算机的应用程序之间。开发者还可以定义定制的DDE数据格式进行应用程序之间特别目的IPC,它们有更紧密耦合的通信要求。大多数基于Windows的应用程序都支持DDE。
对象连接与嵌入
  应用程序利用对象连接与嵌入(OLE)技术管理复合文档(由多种数据格式组成的文档),OLE提供使某应用程序更容易调用其它应用程序进行数据编辑的服务。例如,OLE支持的字处理器可以嵌套电子表格,当用户要编辑电子表格时OLE库可自动启动电子表格编辑器。当用户退出电子表格编辑器时,该表格已在原始字处理器文档中得到更新。在这里电子表格编辑器变成了字处理器的扩展,而如果使用DDE,用户要显式地启动电子表格编辑器。
  同DDE技术相同,大多数基于Windows的应用程序都支持OLE技术。
动态连接库
  Win32动态连接库(DLL)中的全局数据可以被调用DLL的所有进程共享,这就又给进程间通信开辟了一条新的途径,当然访问时要注意同步问题。
  虽然可以通过DLL进行进程间数据共享,但从数据安全的角度考虑,我们并不提倡这种方法,使用带有访问权限控制的共享内存的方法更好一些。
远程过程调用
  Win32 API提供的远程过程调用(RPC)使应用程序可以使用远程调用函数,这使在网络上用RPC进行进程通信就像函数调用那样简单。RPC既可以在单机不同进程间使用也可以在网络中使用。
  由于Win32 API提供的RPC服从OSF-DCE(Open Software Foundation Distributed Computing Environment)标准。所以通过Win32 API编写的RPC应用程序能与其它操作系统上支持DEC的RPC应用程序通信。使用RPC开发者可以建立高性能、紧密耦合的分布式应用程序。
NetBios函数
  Win32 API提供NetBios函数用于处理低级网络控制,这主要是为IBM NetBios系统编写与Windows的接口。除非那些有特殊低级网络功能要求的应用程序,其它应用程序最好不要使用NetBios函数来进行进程间通信。
Sockets
  Windows Sockets规范是以U.C.Berkeley大学BSD UNIX中流行的Socket接口为范例定义的一套Windows下的网络编程接口。除了Berkeley Socket原有的库函数以外,还扩展了一组针对Windows的函数,使程序员可以充分利用Windows的消息机制进行编程。
  现在通过Sockets实现进程通信的网络应用越来越多,这主要的原因是Sockets的跨平台性要比其它IPC机制好得多,另外WinSock 2.0不仅支持TCP/IP协议,而且还支持其它协议(如IPX)。Sockets的唯一缺点是它支持的是底层通信操作,这使得在单机的进程间进行简单数据传递不太方便,这时使用下面将介绍的WM_COPYDATA消息将更合适些。
WM_COPYDATA消息
  WM_COPYDATA是一种非常强大却鲜为人知的消息。当一个应用向另一个应用传送数据时,发送方只需使用调用SendMessage函数,参数是目的窗口的句柄、传递数据的起始地址、WM_COPYDATA消息。接收方只需像处理其它消息那样处理WM_COPY DATA消息,这样收发双方就实现了数据共享。
  WM_COPYDATA是一种非常简单的方法,它在底层实际上是通过文件映射来实现的。它的缺点是灵活性不高,并且它只能用于Windows平台的单机环境下。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值