一、文件映射
文件映射是在多个进程间共享数据的非常有效方法,有较好的安全性。但文件映射只能用于本地机器的进程之间,不能用于网络中,而开发者还必须控制进程间的同步。
参考连接:https://blog.csdn.net/qq_20183489/article/details/54646794
另外,内存映射文件在处理大数据量的文件时表现出了良好的性能(实际上,文件越大,内存映射的优势就越明显)。
参考连接:https://blog.csdn.net/zzq060143/article/details/54619571
二、共享内存
Win32 API中共享内存(Shared Memory)实际就是文件映射的一种特殊情况。进程在创建文件映射对象时用0xFFFFFFFF(INVALID_HANDLE_VALUE)来代替文件句柄(HANDLE),就表示了对应的文件映射对象是从操作系统页面文件访问内存,其它进程打开该文件映射对象就可以访问该内存块。由于共享内存是用文件映射实现的,所以它也有较好的安全性,也只能运行于同一计算机上的进程之间。
共享内存实现数据共享示例如下:
//发送数据的进程先启动,用于发送数据,即将数据写入视图 。
#include "stdafx.h"
#include <Windows.h>
#include <conio.h>
#define BUFFER_SIZE 256
TCHAR szMapFileName[] = TEXT("MyFileMappingName"); //映射文件名,即共享内存的名称
TCHAR szSendData[] = TEXT("Message from the send process.");
int main()
{
HANDLE hMapFile = NULL;
LPCTSTR pBuf = NULL;
//1. 创建一个文件映射内核对象
hMapFile = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUFFER_SIZE, szMapFileName); //INVALID_HANDLE_VALUE表示创建一个进程间共享的对象
if (NULL == hMapFile)
{
_tprintf(TEXT("Could not create file mapping object (%d).\n"), GetLastError());
return -1;
}
//2. 将文件数据映射到进程的地址空间
pBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFFER_SIZE);
if (NULL == pBuf)
{
_tprintf(TEXT("Could not map view of file (%d). \n"), GetLastError());
CloseHandle(hMapFile);
hMapFile = NULL;
return -1;
}
//3. 写入到内存中
CopyMemory((void*)pBuf, szSendData, _tcslen(szSendData) * sizeof(TCHAR));
_getch(); //这个函数是一个不回显函数,当用户按下某个字符时,函数自动读取,无需按回车
//4. 从进程的地址空间中撤消文件数据的映像
UnmapViewOfFile(pBuf);
//5. 关闭文件映射对象和文件对象
CloseHandle(hMapFile);
getchar();
return 0;
}
//接收数据的进程后启动,用于接收数据,即读取视图的数据
#include "stdafx.h"
#include <Windows.h>
#define BUFFER_SIZE 256
TCHAR szMapFileName[] = TEXT("MyFileMappingName");
int main()
{
HANDLE hMapFile = NULL;
LPCTSTR pBuf = NULL;
//1. 打开一个命名的文件映射内核对象
hMapFile = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, szMapFileName);
if (NULL == hMapFile)
{
_tprintf(TEXT("Could not open file mapping object (%d).\n"), GetLastError());
return -1;
}
//2. 将文件映射内核对象hFileMapping映射到当前应用程序的进程地址pBuf,通过该指针可以读写共享的内存区域
pBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFFER_SIZE);
if (NULL == pBuf)
{
_tprintf(TEXT("Could not map view of file (%d). \n"), GetLastError());
CloseHandle(hMapFile);
hMapFile = NULL;
return -1;
}
//3. 显示接收到的数据
for (int i = 0; i < _tcsclen(pBuf); i++)
{
_tprintf(TEXT("%c"), *(pBuf + i));
}
printf("\n");
//4. 从进程的地址空间中撤消文件数据的映像
UnmapViewOfFile(pBuf);
//5. 关闭文件映射对象和文件对象
CloseHandle(hMapFile);
getchar();
return 0;
}
三、管道
管道(Pipe)是一种具有两个端点的通信通道:有一端句柄的进程可以和有另一端句柄的进程通信。管道可以是单向-一端是只读的,另一端点是只写的;也可以是双向的-管道的两端点既可读也可写。
匿名管道
匿名管道(Anonymous Pipe)是在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。通常由父进程创建管道,然后由要通信的子进程继承通道的读端点句柄或写 端点句柄,然后实现通信。父进程还可以建立两个或更多个继承匿名管道读和写句柄的子进程。这些子进程可以使用管道直接通信,不需要通过父进程。
匿名管道是单机上实现子进程标准I/O重定向的有效方法,它不能在网上使用,也不能用于两个不相关的进程之间。
匿名管道通信过程:
>>父进程读写过程
①创建匿名管道
②创建子进程,并对子进程相关数据进行初始化(用匿名管道的读取/写入句柄赋值给子进程的输入/输出句柄)。
③关闭子进程相关句柄。(进程句柄,主线程句柄)
④对管道读写
>>子进程读写过程
①获得输入输出句柄
②对管道读写
相关函数:CreatePipe()创建管道
函数原型:
BOOL CreatePipe( PHANDLE hReadPipe, // pointer to read handle PHANDLE hWritePipe, // pointer to write handle LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes DWORD nSize // pipe size );
参数说明:
hReadPipe 作为返回类型使用,返回管道读取句柄
hWritePipe 作为返回类型使用,返回管道写入句柄
lpPipeAttributes 指向SECURITY_ATTRIBUTES结构体的指针,检测返回句柄是否能被子进程继承,如果此参数为NULL,则句柄不能被继承
nSize 指定管道的缓冲区大小,改大小只是个建议值,系统将用这个值来计算一个适当的缓存区大小,如果此参数是0,系统会使用默认的缓冲区大小
返回值: 若函数成功返回非零值,若函数失败返回0,详细消息可以调用GetLastError函数获得
代码实例:
// 父进程.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;
int main()
{
const int nBufferLen = 256;
SECURITY_ATTRIBUTES sa;
HANDLE hRead = NULL;
HANDLE hWrite = NULL;
STARTUPINFO sui;
PROCESS_INFORMATION pi;
char szBuffer[nBufferLen] = { 0 };
DWORD dwReadLen = 0;
BOOL bRet = FALSE;
//1. 创建匿名管道
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
bRet = ::CreatePipe(&hRead, &hWrite, &sa, 0);
if (!bRet)
{
cout << "创建匿名管道失败!" << endl;
system("pause");
return -1;
}
//2. 创建子进程,并对子进程相关数据进行初始化(用匿名管道的读取写入句柄赋予子进程的输入输出句柄)
ZeroMemory(&sui, sizeof(STARTUPINFO));
sui.cb = sizeof(STARTUPINFO);
sui.dwFlags = STARTF_USESTDHANDLES;
sui.hStdInput = hRead;
sui.hStdOutput = hWrite;
sui.hStdError = GetStdHandle(STD_ERROR_HANDLE);
bRet = ::CreateProcess(L"..\\x64\\Debug\\子进程.exe", NULL, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &sui, &pi);
if (!bRet)
{
cout << "创建子进程失败!" << endl;
system("pause");
return -1;
}
//3. 关闭子进程相关句柄(进行句柄,进程主线程句柄)
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
Sleep(2000);
//4. 读取数据
bRet = ::ReadFile(hRead, szBuffer, nBufferLen, &dwReadLen, NULL);
if (!bRet)
{
cout << "读取数据失败!" << endl;
system("pause");
return -1;
}
cout << "从子进程接收到到数据: " << szBuffer << endl;
system("pause");
return 0;
}
// 子进程.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hRead = NULL;
HANDLE hWrite = NULL;
BOOL bRet = FALSE;
//1. 获得匿名管道输入输出句柄
hRead = GetStdHandle(STD_INPUT_HANDLE);
hWrite = GetStdHandle(STD_OUTPUT_HANDLE);
char szSendBuffer[] = "子进程写入管道成功!";
DWORD dwWriteLen = 0;
//2. 写入数据
bRet = WriteFile(hWrite, szSendBuffer, (DWORD)strlen(szSendBuffer), &dwWriteLen, NULL);
if (!bRet)
{
system("pause");
return -1;
}
Sleep(500);
system("pause");
return 0;
}
命名(有名)管道
命名管道(Named Pipe)是服务器进程和一个或多个客户进程之间通信的单向或双向管道。命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。
命名管道提供了相对简单的编程接口,使通过网络传输数据并不比同一计算机上两进程之间通信更困难,不过如果要同时和多个进程通信它就力不从心了。
命名管道有两种通信方式:
A. 字节模式:在字节模式下,数据以一个连续的字节流的形式在客户机和服务器之间流动
B. 消息模式:在消息模式下,客户机和服务器则通过一系列不连续的数据单位,进行数据收发,每次在管道上发一条消息后,它必须作为一条完整的消息读入
通信流程:
服务器端: 创建命名管道 -> 服务器等待用户连接 -> 读写数据
客户端:连接命名管道 -> 打开命名管道 -> 读写数据
相关函数:CreatePipe()创建管道
函数原型:
HANDLE CreateNamedPipe( LPCTSTR lpName, // pipe name DWORD dwOpenMode, // pipe open mode DWORD dwPipeMode, // pipe-specific modes DWORD nMaxInstances, // maximum number of instances DWORD nOutBufferSize, // output buffer size DWORD nInBufferSize, // input buffer size DWORD nDefaultTimeOut, // time-out interval LPSECURITY_ATTRIBUTES lpSecurityAttributes // SD );
参数说明:
代码实例:
// 服务端.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hPipe = NULL;
HANDLE hEvent = NULL;
DWORD dwReadLen = 0;
DWORD dwWriteLen = 0;
OVERLAPPED ovlap;
char senbuf[] = "This is server!";
char rebuf[100];
//1. 创建命名管道
hPipe = CreateNamedPipe(L"\\\\.\\pipe\\Communication", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0, 1, 1024, 1024, 0, NULL);
if (INVALID_HANDLE_VALUE == hPipe)
{
cout << "创建命名管道失败!" << endl;
hPipe = NULL;
system("pause");
return -1;
}
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == hEvent)
{
cout << "创建事件对象失败!" << endl;
CloseHandle(hPipe);
hPipe = NULL;
system("pause");
return -1;
}
ZeroMemory(&ovlap, sizeof(OVERLAPPED));
ovlap.hEvent = hEvent;
//2. 创建管道连接
if (!ConnectNamedPipe(hPipe, &ovlap))
{
if (ERROR_IO_PENDING != GetLastError())
{
cout << "等待客户端连接失败!" << endl;
CloseHandle(hPipe);
CloseHandle(hEvent);
hPipe = NULL;
system("pause");
return -1;
}
}
//3. 等待客户端连接
if ( WAIT_FAILED == WaitForSingleObject(hEvent, INFINITE))
{
cout << "等待对象失败!" << endl;
CloseHandle(hPipe);
CloseHandle(hEvent);
hPipe = NULL;
system("pause");
return -1;
}
CloseHandle(hEvent);
//4. 读写管道数据
//4.1 读取数据
if (!ReadFile(hPipe, rebuf, 100, &dwReadLen, NULL))
{
cout << "读取数据失败!" << endl;
system("pause");
return -1;
}
cout << rebuf << endl;
//4.2 写入数据
if (!WriteFile(hPipe, senbuf, (DWORD)strlen(senbuf) + 1, &dwWriteLen, NULL))
{
cout << "写入数据失败!" << endl;
system("pause");
return -1;
}
system("pause");
return 0;
}
// 客户端.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hPipe = NULL;
HANDLE hEvent = NULL;
DWORD dwReadLen = 0;
DWORD dwWriteLen = 0;
char senbuf[] = "This is client!";
char rebuf[100];
//1. 连接命名管道
if (!WaitNamedPipe(L"\\\\.\\pipe\\Communication", NMPWAIT_WAIT_FOREVER))
{
cout << "当前没有可利用的命名管道实例!" << endl;
system("pause");
return -1;
}
//2. 打开命名管道
hPipe = CreateFile(L"\\\\.\\pipe\\Communication", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hPipe)
{
cout << "打开命名管道失败!" << endl;
hPipe = NULL;
system("pause");
return -1;
}
//3. 读写管道数据
//3.1 写入数据
if (!WriteFile(hPipe, senbuf, strlen(senbuf) + 1, &dwWriteLen, NULL))
{
cout << "写入数据失败!" << endl;
system("pause");
return -1;
}
//3.2 读取数据
if (!ReadFile(hPipe, rebuf, 100, &dwReadLen, NULL))
{
cout << "读取数据失败!" << endl;
system("pause");
return -1;
}
cout << rebuf << endl;
system("pause");
return 0;
}
正在更新中。。。