最近一直在找共享内存同步的操作,恰好这篇文章有讲解。本文转载:https://blog.csdn.net/bing_bing_bing_/article/details/82875302
方便记录,copy了一份。
2.进程间的通信
2.1进程
本章讲解windows平台下,进程间的通信方式。
进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是操作系统动态执行的基本单元。简单的说,进程就是一段程序的执行过程。
进程和线程:进程是系统动态执行的基本单位,也是系统分配资源的基本单位;线程是进程中执行的最小单位,它可以访问进程的共享资源。
进程之间对共享内存等进行读写操作,需要使用互斥机制,常使用Mutex;进程的同步机制包括Event、Semaphore,常使用Semaphore。
进程间的通信不仅包括进程的同步互斥,还包括进程间数据的传输。
进程间常用的通信方式:共享内存、管道、信号量、端口。其中Mutex和Event放在共享内存中讲解,端口会在网络编程那章讲解。
2.2 Shared Memory
Shared Memory称为共享内存,它是进程间数据传输最快的通信方式。由于共享内存是所有进程都可以访问,因此共享内存的操作需要加锁。
现在实现进程A和进程B,进程A对共享内存写数据,进程B对共享内存读数据。
//进程A
#include <iostream>
#include <Windows.h>
using namespace std;
#define BUF_SIZE 4096
HANDLE H_Mutex = NULL;
HANDLE H_Event = NULL;
int main(int argc,char ** argv)
{
//步骤1:创建共享文件句柄
HANDLE shared_file = CreateFileMapping(
INVALID_HANDLE_VALUE,//物理文件句柄
NULL, //默认安全级别
PAGE_READWRITE, //PAGE_READWRITE表示可读可写,PAGE_READONLY表示只读,PAGE_WRITECOPY表示只写
0, //高位文件大小
BUF_SIZE, //低位文件大小
L"ShareMemory" //共享内存名称
);
if (shared_file == NULL)
{
cout<<"Could not create file mapping object..."<<endl;
return 1;
}
//步骤2:映射缓存区视图,得到指向共享内存的指针
LPVOID lpBUF = MapViewOfFile(
shared_file, //已创建的文件映射对象句柄
FILE_MAP_ALL_ACCESS,//访问模式:可读写
0, //文件偏移的高32位
0, //文件偏移的低32位
BUF_SIZE //映射视图的大小
);
if (lpBUF == NULL)
{
cout << "Could not create file mapping object..." << endl;
CloseHandle(shared_file);
return 1;
}
H_Mutex = CreateMutex(NULL, FALSE, L"sm_mutex");//创建一个互斥器
H_Event = CreateEvent(NULL,//表示安全控制,一般为NULL
FALSE,//确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。
FALSE,//表示事件的初始状态,传入TRUE表示已触发
L"sm_event"//表示事件的名称,传入NULL表示匿名事件
);
//步骤3:操作共享内存
char Buffer[97];
while (1)
{
cout << "A proccess:Please input the content to the process B" << endl;
cin.getline(Buffer,97);
cout << "Buffer: " <<Buffer<< endl;
WaitForSingleObject(H_Mutex, INFINITE); //使用互斥体加锁 获得互斥器的拥有权
memcpy(lpBUF, Buffer, strlen(Buffer)+1);
ReleaseMutex(H_Mutex); //放锁
SetEvent(H_Event);/激活等待的进程
}
CloseHandle(H_Mutex);
CloseHandle(H_Event);
//步骤4:解除映射和关闭句柄
UnmapViewOfFile(lpBUF);
CloseHandle(shared_file);
return 0;
}
//进程B
#include <iostream>
#include <Windows.h>
using namespace std;
HANDLE H_Mutex = NULL;
HANDLE H_Event = NULL;
int main(int argc, char ** argv)
{
//步骤1:打开共享文件句柄
HANDLE shared_file = OpenFileMapping(
FILE_MAP_ALL_ACCESS,//访问模式:可读写
FALSE,
L"ShareMemory" //共享内存名称
);
if (shared_file == NULL)
{
cout << "Could not open file mapping object..." << endl;
return 1;
}
//步骤2:映射缓存区视图,得到指向共享内存的指针
LPVOID lpBUF = MapViewOfFile(
shared_file, //已创建的文件映射对象句柄
FILE_MAP_ALL_ACCESS,//访问模式:可读写
0, //文件偏移的高32位
0, //文件偏移的低32位
0 //映射视图的大小,0表示从偏移量到文件映射的末尾,因为共享文件open端不知道其大小,所以写0
);
if (lpBUF == NULL)
{
cout << "Could not create file mapping object..." << endl;
CloseHandle(shared_file);
return 1;
}
H_Mutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, L"sm_mutex");
if (H_Mutex == NULL)
{
cout << "open mutex failed..." <<endl;
return 1;
}
H_Event = OpenEvent(EVENT_ALL_ACCESS, FALSE, L"sm_event");
if (H_Event == NULL)
{
cout << "open mutex failed..." << endl;
return 1;
}
char Buffer[97];
//步骤3:操作共享内存
while (1)
{
cout << "B proccess:Receive data from process A" << endl;
WaitForSingleObject(H_Event, INFINITE);
WaitForSingleObject(H_Mutex, INFINITE); //使用互斥体加锁
memcpy(Buffer, lpBUF, strlen((char *)lpBUF) + 1);
ReleaseMutex(H_Mutex); //放锁
cout << Buffer << endl;
//system("PAUSE ");
}
CloseHandle(H_Event);
CloseHandle(H_Mutex);
//步骤4:解除映射和关闭句柄
UnmapViewOfFile(lpBUF);
CloseHandle(shared_file);
return 0;
}
以管理员的身份运行命令提示符,先执行进程A的exe文件,再执行进程B的exe文件。
进程A运行结果:
A proccess:Please input the content to the process B
Hello China
Buffer: Hello China
A proccess:Please input the content to the process B
你好,中国!
Buffer: 你好,中国!
A proccess:Please input the content to the process B
进程B运行结果:
B proccess:Receive data from process A
Hello China
B proccess:Receive data from process A
你好,中国!
B proccess:Receive data from process A
2.3 PIPE
pipe称为管道,其分为命名管道、匿名管道。
2.3.1命名管道
//Server.cpp
#include <Windows.h>
#include <iostream>
using namespace std;
#define BUF_SIZE 4096
HANDLE h_Mypipe = NULL;
//步骤1:定义管道名,点表示当前主机,pipe表示管道
#define MY_NAMED_PIPE L"\\.\pipe\Named_Pipe"
int main(int argc, char ** argv)
{
//步骤2:创建命名管道
h_Mypipe = CreateNamedPipe(
MY_NAMED_PIPE, //为命名管道创建名称
PIPE_ACCESS_DUPLEX, //管道访问方式:PIPE_ACCESS_DUPLEX指双向模式
PIPE_TYPE_MESSAGE | //命名管道句柄的写入方式:以数据块的方式写入管道
PIPE_READMODE_MESSAGE | //命名管道句柄的读取方式:以数据块的方式从管道读取
PIPE_WAIT, //命名管道句柄的等待方式:阻塞方式
PIPE_UNLIMITED_INSTANCES, //管道所能创建的最大实例个数:1~255,
0, //管道的输出缓冲区容量,0表示默认大小
0, //管道的输入缓冲区容量,0表示默认大小 1000, //管道的默认等待超时,单位毫秒
NULL); //管道的安全性,NULL表示windows提供的默认安全
//INVALID_HANDLE_VALUE是CreateNamedPipe返回值,表示创建失败
if (h_Mypipe == INVALID_HANDLE_VALUE)
{
cout << "Create Named_Pipe Failed..." << endl;
return 1;
}
//步骤3:等待客户端的连接
if (!ConnectNamedPipe(h_Mypipe, NULL))
{
cout << "Connect Failed..." << endl;
return 1;
}
else
cout << "Connect Successed..." << endl;
DWORD wLen = 0;
DWORD rLen = 0;
char szBuffer[BUF_SIZE] = { 0 };
//步骤4:读写管道
while (1)
{
//向客户端发送数据
cin.getline(szBuffer, BUF_SIZE);
cout << "服务器端发送数据:" << szBuffer<< endl;
if (!WriteFile(h_Mypipe, szBuffer, strlen(szBuffer) + 1, &wLen, NULL))
cout << "Write Failed..." << endl;
else
cout<<"服务器端发送成功:共"<< wLen<<"byte"<<endl;
//清除缓冲区
//memset(szBuffer, 0, BUF_SIZE);
//读取客户端数据
if (!ReadFile(h_Mypipe, szBuffer, BUF_SIZE, &rLen, NULL))
cout << "Read Failed..." << endl;
else
cout << "服务器接收客户端数据:" << szBuffer << ", 共" << rLen << "byte" << endl;
}
//步骤5:关闭管道
DisconnectNamedPipe(h_Mypipe);
CloseHandle(h_Mypipe);
return 0;
}
//Client.cpp
#include <Windows.h>
#include <iostream>
using namespace std;
#define BUF_SIZE 4096
HANDLE h_Mypipe = NULL;
//步骤1:定义管道名,点表示当前主机,pipe表示管道
#define MY_NAMED_PIPE L"\\.\pipe\Named_Pipe"
int main(int argc, char ** argv)
{
//步骤2:判断是否有可用的命名管道
//函数WaitNamedPipe:等待某个管道变成可用状态
//形参1:表示命名管道的名称
//形参2:NMPWAIT_USE_DEFAULT_WAIT使用管道创建时的默认超时设定;NMPWAIT_WAIT_FOREVER永远等待
if (!WaitNamedPipe(MY_NAMED_PIPE, NMPWAIT_USE_DEFAULT_WAIT))
{
cout << "No Named_Pipe Accessible..." << endl;
return 1;
}
else
cout << "Named_Pipe Accessible..." << endl;
//步骤3:打开指定命名管道
//函数CreateFile:创建或打开对象(这里对象指的是管道)
h_Mypipe = CreateFile(
MY_NAMED_PIPE, //创建或打开的对象(管道)名称
GENERIC_READ | //对象的访问方式:读访问
GENERIC_WRITE, //对象的访问方式:写访问
0, //对象是否共享:0表示不共享
NULL, //指向一个SECURITY_ATTRIBUTES结构的指针
OPEN_EXISTING, //对象的创建方式:OPEN_EXISTING表示打开对象(管道)
FILE_ATTRIBUTE_NORMAL, //设置对象的属性和标志
NULL);
if (h_Mypipe == INVALID_HANDLE_VALUE)
{
cout << "Open Named_Pipe Failed..." << endl;
return 1;
}
DWORD wLen = 0;
DWORD rLen = 0;
char szBuffer[BUF_SIZE] = { 0 };
//步骤4:读写管道
while (1)
{
//读取服务器端数据
if (!ReadFile(h_Mypipe, szBuffer, BUF_SIZE, &rLen, NULL))
cout << "Read Failed..." << endl;
else
cout << "客户端接收服务器端数据:" << szBuffer << ", 共" << rLen << "byte" << endl;
//清除缓冲区
//memset(szBuffer, 0, BUF_SIZE);
//客户端发送数据
cin.getline(szBuffer, BUF_SIZE);
cout << "客户端发送数据:" << szBuffer << endl;
if (!WriteFile(h_Mypipe, szBuffer, strlen(szBuffer) + 1, &wLen, NULL))
cout << "Write Failed..." << endl;
else
cout << "客户端发送成功:共" << wLen << "byte" << endl;
}
//步骤5:关闭管道
CloseHandle(h_Mypipe);
return 0;
}
服务器端运行结果:
Connect Successed...
hello world
服务器端发送数据:hello world
服务器端发送成功:共12byte
服务器接收客户端数据:你好,中国, 共11byte
客户端运行结果:
Named_Pipe Accessible...
客户端接收服务器端数据:hello world, 共12byte
你好,中国
客户端发送数据:你好,中国
客户端发送成功:共11byte
命名管道可以实现进程之间的全双工通信。服务器端唯一有权创建管道,并等待客户端的连接请求;客户端只能使用已创建的管道,并打开管道与服务器端通信。服务器端和客户端都是可读可写的。
CreateNamedPipe(参数1,参数2,参数3,参数4,参数5,参数6,参数7,参数8)函数作用是创建命名管道。
参数1为命名管道创建名称。
参数2是命名管道的访问方式,PIPE_ACCESS_DUPLEX是双向模式,即服务器进程和客户端进程都可以从管道读写数据,PIPE_ACCESS_INBOUND是服务器只能从管道读数据,客户端只能向管道写数据,PIPE_ACCESS_OUTBOUND是服务器只能向管道写数据,客户端只能从管道读数据。
参数3分为3部分,分别是命名管道句柄的写入方式、命名管道句柄的读取方式、命名管道句柄的等待方式。命名管道句柄的写入方式分为PIPE_TYPE_BYTE和PIPE_TYPE_MESSAGE,PIPE_TYPE_BYTE是以字节流的形式写入管道,PIPE_TYPE_MESSAGE是以数据块(名为消息或报文)的形式写入管道。命名管道句柄的读取方式分为PIPE_READMODE_BYTE和PIPE_READMODE_MESSAGE,PIPE_READMODE_BYTE是以字节流的形式从管道读取数据,PIPE_READMODE_MESSAGE是以数据块(名为消息或报文)的形式从管道读取数据。在字节流模式中,数据以连续的字节在管道中传输,数据之间没有边界,适合大容量数据的传输;在数据块(消息)模式,数据以不连续的消息为基本单位在管道中传输,消息与消息之间有边界,适合小容量数据的传输。命名管道句柄的等待方式分为PIPE_WAIT和PIPE_NOWAIT,PIPE_WAIT是阻塞模式,PIPE_NOWAIT是非阻塞模式,这是对函数WaitNamedPipe()的设置,一般都设置为阻塞模式。
参数4是命名管道能创建的最大实例个数,范围是1~255。它的意思是有多少进程可以等待使用此命名管道,其中PIPE_UNLIMITED_INSTANCES表示255。
参数5是管道的输出缓冲区容量,0表示默认大小;也可以设为实际数据,如4096。
参数6是管道的输入缓冲区容量,0表示默认大小;也可以设为实际数据,如4096。
参数7是管道的默认等待超时,单位毫秒。这是对函数WaitNamedPipe()的等待时间设置。
参数8表示管道的安全性,NULL表示windows提供的默认安全。
返回值:执行成功,返回管道的句柄;执行失败,返回INVALID_HANDLE_VALUE。
WriteFile(参数1, 参数2, 参数3, 参数4, 参数5)函数是将数据写入一个文件或I/O设备。这里的作用是向管道写数据。
参数1是写入数据的文件句柄,这里指的是命名管道的句柄。
参数2是数据存放的缓冲区地址。
参数3是写入的数据长度,单位是字节。
参数4是实际写入管道的字节数。
参数5是指向OVERLAPPED结构体的指针,默认为NULL,表明使用默认的同步I/O方式。
ReadFile()同理,需要注意的是参数3不能使用strlen,因为数据还没读取出来,无法得知数据的大小,必须手动设置需要读取的数据长度或使用sizeof。
WaitNamedPipe(参数1,参数2)函数的作用是等待某个管道变成可用状态。
参数1是管道的名称。
参数2中NMPWAIT_USE_DEFAULT_WAIT使用管道创建时的默认超时设定,NMPWAIT_WAIT_FOREVER表示一直等待。
CreateFile(参数1, 参数2,参数3,参数4,参数5,参数6,参数7)函数的作用是打开或创建文件或I/O设备。
参数1是打开或创建的对象名称,这里指的是命名管道的名称。
参数2是对象的访问方式,GENERIC_READ是读访问,GENERIC_WRITE是写访问。
参数3表示对象是否共享,0表示不共享。
参数4是指向一个SECURITY_ATTRIBUTES结构的指针,不需要了解。
参数5是对象的打开或创建方式,OPEN_EXISTING表示打开已存在的对象。
参数6是设置对象的属性和标志,FILE_ATTRIBUTE_NORMAL表示该对象没有其他属性设置。
参数7是指定具有GENERIC_READ访问方式的模板文件的句柄,不需要了解。
注:以上函数都是windows平台提供的API。