win32管道技术和进程通信实例(二)

《win32内核对象共享和进程通信实例(一)》,先继续了解一下windows匿名管道。

windows管道实质是一块共享内存,可用于进程间通信。windows管道分为匿名管道和命名管道。匿名管道用户本地进程间通信;命名管道可以用户网络通信。

匿名管道

①dos下的管道操作符|

最开始接触管道这个概念是dos命令或者linux命令吧。比如dos下可以使用type test.txt|more实现对于test.txt的逐屏显示。中间的“|”即为管道操作符,对其解释是:把左边的输出作为右边的输入。

好好理解这句话,管道操作符“|”的作用是“把左边的输出作为右边的输入”,确切地来说,应该是把type test.txt的内容先输出到管道,然后把管道的内容作为more命令的输入(?这里可能有问题,但是先让我这么认为)。

本来type test.txt默认是输出到屏幕设备(执行type test.txt命令,它在屏幕打印出test.txt的内容),这个是它的标准输出,使用管道操作符之后,把它的从原来的输出到屏幕设备变成输出到管道。这种改变原来标准输入输出方式的行为,也被称为输入输出重定向。管道操作符是输入输出重定向的一种。一个重定向的简单的例子是,执行 type test.txt>1.txt,这个就是把type test.txt的输出在屏幕设备上变成输出在1.txt文件中。重定向操作符参看下表:

重定向操作符

说明

>

将命令输出写入到文件或设备(例如打印机)中,而不是写在命令提示符窗口中

<

从文件中而不是从键盘中读入命令输入

>>

将命令输出添加到文件末尾而不删除文件中的信息

>&

将前一个句柄的输出写成后一个句柄的输入

<&

从后一个句柄读取输入并写入到前一个句柄输出中

|

从一个命令中读取输出并将其写入另一个命令的输入中。也称作管道

那么问题又来了,这些操作符是怎么改变命令的输入输出方式的?

应该是通过改变它们的输入输出句柄。可以这么认为,标准输入输出指的是设备,比如屏幕设备,键盘设备,通过输入输出句柄可以引用这些设备。如键盘输入用0表示,窗口用1表示,具体可以参照下面的表格:

句柄

数值

说明

STDIN

0

键盘输入

STDOUT

1

输出到命令提示符窗口

STDERR

2

错误输出到命令提示符窗口

UNDEFINED

3~9

程序自定义

 
当执行 type test.txt>1.txt的时候,等同于type test.txt 1>1.txt,把输出句柄指向1.txt文件;可以改变一下这个命令,type test.txt 2>1.txt,这就是把错误输出句柄指向1.txt,这个命令的标准输出还是屏幕,所以会在屏幕输出内容,与此同时,会把错误信息输出到1.txt,当然这个命令没有错误。不妨换个error 2>1.txt。
再来测试一个命令:error >c.txt 2>&1。error是一个错误的命令,直接运行error会将错误输出到命令提示窗口,这里使用2>&1,就是把error命令的标准错误输出变成标准输出。所以它的错误输出结果能保存在c.txt中。
另外,也可以尝试直接把标准输出变成标准输入,标准输入变成标准输出。但是这应该是不可能的?因为怎么把数据输出到键盘,而又让屏幕输入数据呢?所以就使用一块内存,把一个进程的数据输出到这块内存,另一个进程从这块内容中读取数据,但是剪切板好像也是这样,既然管道的实质是一块内存,那本质上跟剪切板应该是一样:用一块内存来保存数据。区别也许是网内容读取和写入数据的方式不一样。
 ②win32匿名管道
回顾一下win32创建匿名管道的过程。管道是核心对象,核心对象是一块内存,这样说好像没有问题。主进程先创建一个匿名管道,得到管道的读写句柄,那可以认为管道有两个接口,读和写接口;怎么读,当然就是使用ReadFile和WriteFile读写。
主线程把这两个句柄设置为可继承的,然后创建一个子进程,让子进程继承自己可以操作的内核对象(当然也包括了管道的读写权限)。这样一来,有了三个对象,主进程、子进程和可以被两个进程读写的管道。
这样看起来就结束了。父子进程都可以通过管道读写句柄往管道里读写数据了。但是问题是,虽然子进程从父进程继承了对于管道的读写权限,但是它怎么获得这两个句柄?
对于子进程来说,它并不知道自己继承了哪些核心对象。所以要做的就是在主进程创建子进程,子进程生成窗口的时候,把管道的读写句柄赋值给子进程的标准输入输出句柄。这里有两层含义:一、父进程显示地告诉子进程,你能继承这两个句柄;二、子进程用来接收句柄的恰好是它的标准输入输出。
另外我也注意到创建管道的时候,并不能为管道取一个名字,所以使用为对象命名的方法实现内核对象在进程中的共享就不能了。那子进程能不能通过读自己的句柄表来获得管道的读写句柄呢?我觉得应该不可以吧,因为句柄表的结构没有官方的说明文档,是不是意味着也没有官方的API来读取它里面的数据?
另外把子进程的标准输入和标准输出设置成管道的读写句柄,这意味着什么?我开始想的是那子进程按照标准输入输出的数据,就会变成往管道取数据和往管道存数据(这样理解应该是对的);但是我之前测试的程序,是子进程通过GetStdHandle获得它的标准输入输出句柄,而在父进程中已经把他们设置成了管道的读写句柄,所以子进程获取到的就是管道的读写句柄。从这个角度来说,把这一过程理解为单纯传值好像要好一些。
那么问题来了,管道和剪贴板有啥区别?
剪贴板是系统设置一段内存空间,应用程序只能打开,不能创建一个剪贴板,而且要自己开辟一块全局的内存,然后使用SetClipboardData只是把内存的句柄给剪贴板。管道是内核对象,自己本身是应用创建的一块内存,这个内存并不是全局的,而是进程间共享的。
③说明
关于匿名管道就先这么理解吧,不知道有没有理解错。
命名管道
 匿名管道只能用于相关进程(父子进程和兄弟进程)之间的通信,不能用于网络环境,即跨网络传输数据。
命名管道则用于非关联进程和不同计算机上的进程通信。一个管道的所有实例共享同一个命名管道,但是每一个实例均拥有独立的缓存与句柄,并且为“客户-服务”通信提供一个分离的管道。命名管道是围绕Windows文件系统而设计的一种机制,采用的是命名管道文件系统(Named Pipe File System,NPFS)接口,对数据的收发也采用文件读写函数来完成。在设计上,由于命名管道也利用了微软网络提供者(MSNP)重定向器,因此无需设计底层的通信协议细节。在Windows2000下命名管道与I/0子系统紧密联系在一起,实际上它实现的是一个文件系统,用户看到的管道只不过是另一个文件系统。《Window命名管道技术的分析与实现》
命令管道可以说是一种网络编程方案,它实际上建立了一个客户机/服务器通信体系,并在其中可靠地传输数据。实现命名管道通信,需要有一个服务端和至少一个客户端。
编写命名管道服务端程序

 ①使用CreateNamedPipe函数创建一个命名管道实例,并且为随后的管道操作返回一个句柄。一个命名管道服务进程调用该函数可以创建一个特定的命名管道的第一个实例,并设置它的基本属性,也可以创建一个已经存在的命名管道的新的实例。

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 security attributes
);

第一个参数,指向管道名字字符串指针,字符串必须是如下格式:
\\.\pipe\pipename
第二个参数,管道打开模式,每一个管道的实例必须设置成相同的模式。当它设置为PIPE_ACCESS_DUPLEX,表示管道是双向的,服务端和客户端都可以从管道读写数据。

与此同时还可以设置FILE_FLAG_OVERLAPPED标识。表示开启重叠模式,重叠模式开启之后,那些可能需要花很多时间去操作的读写和连接操作能够立即返回。该模式可以让前台的线程把耗时的操作放在后台执行而去执行其他的操作。例如在重叠模式下,一个线程可以处理多个管道实例的同步输入输出(I/O)操作,或者在相同的管道上同步读写操作。如果没有设置重叠模式,管道上的读写和连接操作要等操作完成之后才返回。

第三个参数,管道特性模式。指定管道句柄的类型,读和写。这对应命名管道的两种通信模式,即字节模式和消息模式,第三个参数设置为PIPE_TYPE_BYTE表示使用字节模式通信;设置为PIPE_TYPE_MESSAGE表示使用消息模式通信。
第四个参数,指定管道最多可以创建多少个实例。所有实例必须指定相同的数目。可接受的值为1到PIPE_UNLIMITED_INSTANCES,如果设置为PIPE_UNLIMITED_INSTANCES,表示可根据系统资源的能力上限创建实例个数。
第五个参数,为输出缓存预留字节数。
第六个参数,为输入缓存预留的字节数。
第七个参数,指定一个超时值,以毫秒为单位,如果后面使用WaitNamedPipe指定了NMPWAIT_USE_DEFAULT_WAIT,每个实例必须设置相同的值。
第八个参数,指向安全属性的结构。主要用来确认创建的管道对象能否被子进程继承。

②使用ConnectNamedPipe去等待一个客户端进程连接一个命名管道的实例。

BOOL ConnectNamedPipe(
  HANDLE hNamedPipe,          // handle to named pipe to connect
  LPOVERLAPPED lpOverlapped   // pointer to overlapped structure
);

第一个参数,命名管道实例的句柄,即CreateNamedPipi的返回值。
第二个参数,指向OVERLAPPED结构体。

如果在CreateNamedPipe的时候设置了FILE_FLAG_OVERLAPPED标识,即开启了重叠模式,这里就要传入一个OVERLAPPED的结构体指针,同时OVERLAPPED结构体必须包含一个人工重置对象的事件句柄(MSDN上是这么说的)。

OVERLAPPED结构体包含了用于异步输入/输出(I/0)的信息。这里只用到最后一个成员即hEvent。

typedef struct _OVERLAPPED { // o 
    DWORD  Internal; 
    DWORD  InternalHigh; 
    DWORD  Offset; 
    DWORD  OffsetHigh; 
    HANDLE hEvent; 
} OVERLAPPED; 

这个过程是怎么样的呢?我们首先用CreateEvent创建一个人工重置事件,并设置成无信号,并将返回的event对象句柄赋值给OVERLAPPED结构体的hEvent,这样一来,有客户端连接管道实例的时候,event就会变成有信号状态。我们使用WaitForSingleObject等待event变为有信号状态,然后让程序返回。

之前有说过,管道的等待连接操作是一个耗时操作,如果不设置为重叠模式,就会一直等待,服务端也会阻塞。当然也可以创建一个新线程,让程序做别的事。

③使用ReadFile和WriteFile读写数据。

编写命名管道客户端程序

①使用WaitNamedPipe等待一个指定的可连接的命名管道(也就是,管道的服务端进程有一个在等待的ConnectNamedPipe操作),直到超时

BOOL WaitNamedPipe(
  LPCTSTR lpNamedPipeName,  // pointer to name of pipe for which to wait 
  DWORD nTimeOut            // time-out interval, in milliseconds
);

 第一个参数,指向在等待连接的管道的名称,要使用这样的格式:

\\servername\pipe\pipename
如果服务端在本地,那么servername设置为.
第二个参数,设置以毫秒为单位的超时。除了使用毫秒数字之外,还可以使用下面两个值:
NMPWAIT_USE_DEFAULT_WAIT,表示使用服务端CreateNamePipe指定的超时值。
NMPWAIT_WAIT_FOREVER,如果没有管道可用那就一直等待。

 ②使用CreateFile打开管道

HANDLE CreateFile(
  LPCTSTR lpFileName,          // pointer to name of the file
  DWORD dwDesiredAccess,       // access (read-write) mode
  DWORD dwShareMode,           // share mode
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                               // pointer to security attributes
  DWORD dwCreationDisposition,  // how to create
  DWORD dwFlagsAndAttributes,  // file attributes
  HANDLE hTemplateFile         // handle to file with attributes to 
                               // copy
);

 最后完成一个实例吧:

服务端代码

#include<windows.h>
#define IDB_CREATE 1020
#define IDB_WRITE 1021
#define IDB_READ 1022
HINSTANCE hInstance;

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd){

    hInstance=hInstance;

    WNDCLASS wc;

    wc.cbClsExtra=0;
    wc.cbWndExtra=0;
    wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.hCursor=LoadCursor(NULL,IDC_ARROW);
    wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    wc.hInstance=hInstance;
    wc.lpfnWndProc=WndProc;
    wc.lpszClassName="NpipServ";
    wc.lpszMenuName=NULL;
    wc.style=CS_HREDRAW|CS_VREDRAW;

    if(!RegisterClass(&wc)){
        MessageBox(NULL,"Rigister Window Class failed","error",MB_ICONERROR);
        return 0;
    }

    HWND hwnd=CreateWindow("NpipServ","NamedPipe Server",WS_OVERLAPPEDWINDOW,300,300,350,150,NULL,NULL,hInstance,NULL);

    ShowWindow(hwnd,nShowCmd);
    UpdateWindow(hwnd);
    MSG msg;

    while(GetMessage(&msg,NULL,0,0)){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;

}

LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){
    HDC hdc;
    PAINTSTRUCT ps;
    static HANDLE hPipe;
    switch(message){
    case WM_CREATE:

        CreateWindow("Button","创建管道",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,10,10,100,100,hwnd,(HMENU)IDB_CREATE,hInstance,NULL);
        CreateWindow("Button","写入数据",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,120,10,100,100,hwnd,(HMENU)IDB_WRITE,hInstance,NULL);
        CreateWindow("Button","读取数据",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,230,10,100,100,hwnd,(HMENU)IDB_READ,hInstance,NULL);
        return 0;
    case WM_PAINT:
        hdc=BeginPaint(hwnd,&ps);
        EndPaint(hwnd,&ps);
        return 0;
    case WM_COMMAND:
        switch(LOWORD(wParam)){
        case IDB_CREATE:
        {
            //创建命名管道
            hPipe=CreateNamedPipe("\\\\.\\pipe\\NamedPipeForTest",PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,PIPE_TYPE_BYTE,1,1024,1024,0,NULL);
            if(INVALID_HANDLE_VALUE==hPipe){
                MessageBox(hwnd,"Fail to Create Namedpipe","",MB_ICONERROR);
                return 0;
            }

            //
            HANDLE hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
            if(!hEvent){
                CloseHandle(hPipe);
                MessageBox(hwnd,"Fail to Create Event","",MB_ICONERROR);
                return 0;
            }
        
            OVERLAPPED ov;
            ov.hEvent=hEvent;
            //等待连接
            if(!ConnectNamedPipe(hPipe,&ov)){
                if(ERROR_IO_PENDING!=GetLastError()){
                    CloseHandle(hPipe);
                    CloseHandle(hEvent);
                    MessageBox(hwnd,"Fail to wait a client connect","",MB_ICONERROR);
                    return 0;
                }
            }
            
            WaitForSingleObject(hEvent,INFINITE);
            return 0;
        }
        case IDB_WRITE:
        {
        //写入
            TCHAR writeBuff[]="The Sky Becomes lively Rise...(from server)";
            DWORD dwWrite;
            if(!WriteFile(hPipe,writeBuff,strlen(writeBuff),&dwWrite,NULL)){
                MessageBox(hwnd,"Fail to Write data to pipe","error",MB_ICONERROR);
                return 0;
            }
            
            return 0;
        }
        case IDB_READ:
        {
            //读取数据
            TCHAR readBuff[100];
            ZeroMemory(readBuff,sizeof(readBuff));
            DWORD dwRead;
            if(!ReadFile(hPipe,readBuff,100,&dwRead,NULL)){
                MessageBox(hwnd,"Fail to Read data from pipe","error",MB_ICONERROR);
                return 0;
            }else{
                MessageBox(hwnd,readBuff,"msg",MB_OK);
            }
            return 0;
        }
        }
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd,message,wParam,lParam);
}

客户端代码

#include<windows.h>
#define IDB_CONNECT 1030
#define IDB_WRITE 1031
#define IDB_READ 1032
HINSTANCE hInstance;

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd){

    hInstance=hInstance;

    WNDCLASS wc;

    wc.cbClsExtra=0;
    wc.cbWndExtra=0;
    wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.hCursor=LoadCursor(NULL,IDC_ARROW);
    wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    wc.hInstance=hInstance;
    wc.lpfnWndProc=WndProc;
    wc.lpszClassName="NpipeClient";
    wc.lpszMenuName=NULL;
    wc.style=CS_HREDRAW|CS_VREDRAW;

    if(!RegisterClass(&wc)){
        MessageBox(NULL,"Rigister Window Class failed","error",MB_ICONERROR);
        return 0;
    }

    HWND hwnd=CreateWindow("NpipeClient","NamedPipe Client",WS_OVERLAPPEDWINDOW,700,300,350,150,NULL,NULL,hInstance,NULL);

    ShowWindow(hwnd,nShowCmd);
    UpdateWindow(hwnd);
    MSG msg;

    while(GetMessage(&msg,NULL,0,0)){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;

}

LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){
    HDC hdc;
    PAINTSTRUCT ps;
    static HANDLE hPipe;
    switch(message){
    case WM_CREATE:
        CreateWindow("Button","连接管道",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,10,10,100,100,hwnd,(HMENU)IDB_CONNECT,hInstance,NULL);
        CreateWindow("Button","写入数据",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,120,10,100,100,hwnd,(HMENU)IDB_WRITE,hInstance,NULL);
        CreateWindow("Button","读取数据",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,230,10,100,100,hwnd,(HMENU)IDB_READ,hInstance,NULL);
        return 0;
    case WM_PAINT:
        hdc=BeginPaint(hwnd,&ps);
        EndPaint(hwnd,&ps);
        return 0;
    case WM_COMMAND:
        switch(LOWORD(wParam)){
        case IDB_CONNECT:
        {
            //连接管道
            if(!WaitNamedPipe("\\\\.\\pipe\\NamedPipeForTest",NMPWAIT_WAIT_FOREVER)){
                MessageBox(hwnd,"Fail to connect namedpipe","",MB_ICONERROR);
                return 0;
            }

            //打开管道
            hPipe=CreateFile("\\\\.\\pipe\\NamedPipeForTest",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
            if(INVALID_HANDLE_VALUE==hPipe){
                MessageBox(hwnd,"Fail to open namedpipe","",MB_ICONERROR);
                return 0;
            }
            return 0;
         }
        case IDB_WRITE:
        {
        //写入
            TCHAR writeBuff[]="A Bird Fly To Sky..(from client)";
            DWORD dwWrite;
            if(!WriteFile(hPipe,writeBuff,strlen(writeBuff),&dwWrite,NULL)){
                MessageBox(hwnd,"Fail to Write data to pipe","error",MB_ICONERROR);
                return 0;
            }
            
            return 0;
        }
        case IDB_READ:
        {
            //读取数据
            TCHAR readBuff[100];
            ZeroMemory(readBuff,sizeof(readBuff));
            DWORD dwRead;
            if(!ReadFile(hPipe,readBuff,100,&dwRead,NULL)){
                MessageBox(hwnd,"Fail to Read data from pipe","error",MB_ICONERROR);
                return 0;
            }else{
                MessageBox(hwnd,readBuff,"msg",MB_OK);
            }
            return 0;
        }
        }
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd,message,wParam,lParam);
}

运行

 

推荐

window命名管道技术的分析与实现

posted @ 2020-05-11 22:02  vocus  阅读(1326)  评论(0编辑  收藏  举报