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 |
程序自定义 |
①使用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); }
运行
推荐