热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Windows多进程通信API总结

在一个大型的应用系统中,往往需要多个进程相互协作,进程间通信(IPC,InterProcessCommunication)就显得比较重要了。在Linux系统中,有很多种IPC机制,

在一个大型的应用系统中,往往需要多个进程相互协作,进程间通信(IPC,Inter Process Communication)就显得比较重要了。在Linux系统中,有很多种IPC机制,比如说,信号(signal)、管道(pipe)、消息队列(message queue)、信号量(semaphore)和共享内存(shared memory)、套接字(socket)等,其实Windows操作系统也支持这些东西。在IBM的Developerworks发现了一篇关于Windows与Linux
之间IPC机制API比较的文章,写得很不错,链接


http://www.ibm.com/developerworks/cn/linux/l-ipc2lin1.html


下面大部分内容是关于这些机制的API的实现。


创建进程

进程的创建可以调用CreateProcess函数,CreateProcess有三个重要的参数,运行进程的名称、指向STARTUPINFO结构的指针、指向PROCESS_INFORMATION结构的指针。其原型如下:



BOOL CreateProcess
(
LPCTSTRlpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes。
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFOlpStartupInfo,
LPPROCESS_INFORMATIONlpProcessInformation
);



给个例子,如果启动时应用程序带有命令行参数,进程将输出命令行参数,并创建一个不带任何参数的子线程;如果不带有任何参数,则会输出一条提示消息。


#include 
#include
#include
using namespace std;
int _tmain(int argc, _TCHAR* argv[]){
STARTUPINFO startup_info;
PROCESS_INFORMATION process_info;
if (argc>1)
{
cout<<"Argument"<

再给个例子,利用CreateProcess开启一个新线程,启动IE浏览器,打开百度的主页,5s后再将其关闭。





#include 
#include
#include
using namespace std;
#define IE L"C:\\Program Files\\Internet Explorer\\iexplore.exe"
#define CMD L"open http://www.baidu.com/"
int _tmain(int argc, _TCHAR* argv[]){
STARTUPINFO startup_info;
GetStartupInfo(&startup_info);
PROCESS_INFORMATION process_info;
startup_info.dwFlags=STARTF_USESHOWWINDOW;
startup_info.wShowWindow=SW_HIDE;
if (!CreateProcess(IE,CMD,NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL,&startup_info,&process_info))
{
cout<<"Create Process Error:"< return 0;
}
Sleep(5000);
TerminateProcess(process_info.hProcess,0);
return 0;
}


被创建的句柄通过process_info.hProcess返回。如果传递参数给新的进程,第一个命令行参数必须重复应用程序名称,整个命令行会被传递给子进程。





传递参数给新进程。



#include 
#include
#include
using namespace std;
int _tmain(int argc, _TCHAR* argv[]){
STARTUPINFO startup_info;
PROCESS_INFORMATION process_info;
if (argc==1)
{
cout<<"No arguments given starting child process"< wchar_t argument[256];
wsprintf(argument,L"\"%s\" Hello",argv[0]);
ZeroMemory(&process_info,sizeof(process_info));
ZeroMemory(&startup_info,sizeof(startup_info));
startup_info.cb=sizeof(startup_info);
if (CreateProcess(argv[0],argument,0,0,0,0,0,0,&startup_info,&process_info)==0)
{
cout<<"Error "< }
WaitForSingleObject(process_info.hProcess,INFINITE);
}
else{
cout<<"Argument "< }
getchar();
}


技术分享





IPC

进程间可以共享内存,进程建立具有共享属性的内存区域后,另一个进程可以打开此内存区域,并将其映射到自己的地址空间。共享内存可以使用文件映射函数CreateFileMapping,创建共享内存区域的句柄,通过MapViewOfFile()把这个区域映射到进程,然后再连接到现有的共享内存区域,可以通过OpenFileMapping获得句柄。在进程使用完共享内存后,需要调用UnmapViewOfFile()取消映射,再调用CloseHandle()关闭相应的句柄,避免内存泄露。


给个例子,如果启动时应用程序不带任何参数,应用程序会创建一个子进程。父进程也将建立一个共享内存区域,并将一个字符串保存到共享内存。共享内存取名为sharedmemory,在Local\命名空间中创建,即该共享内存对该用户所有的全部进程可见。(进程的命名空间分为两种,全局命名空间以Global\标识符开头,本地命名空间以Local\开头)


#include 
#include
#include
using namespace std;
int _tmain(int argc, _TCHAR* argv[]){
STARTUPINFO startup_info;
PROCESS_INFORMATION process_info;
HANDLE filehandle;
TCHAR ID[]=TEXT("Local\\sharedmemory");
char* memory;
if (argc==1)
{
filehandle=CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,1024,ID);
memory=(char*)MapViewOfFile(filehandle,FILE_MAP_ALL_ACCESS,0,0,0);
sprintf_s(memory,1024,"%s","Data from first process");
cout<<"First process:"< ZeroMemory(&process_info,sizeof(process_info));
ZeroMemory(&startup_info,sizeof(startup_info));
startup_info.cb=sizeof(startup_info);

wchar_t cmdline[256];
wsprintf(cmdline,L"\"%s\" Child\n",argv[0]);
CreateProcessW(argv[0],cmdline,0,0,0,0,0,0,&startup_info,&process_info);
WaitForSingleObject(process_info.hProcess,INFINITE);

UnmapViewOfFile(memory);
CloseHandle(filehandle);
}
else{
filehandle=OpenFileMapping(FILE_MAP_ALL_ACCESS,0,ID);
memory=(char*)MapViewOfFile(filehandle,FILE_MAP_ALL_ACCESS,0,0,0);
cout<<"Second process: "< UnmapViewOfFile(memory);
CloseHandle(filehandle);
}
getchar();
return 0;
}


技术分享






从结果可以看出,子进程连接到共享内存,并能输出父进程存储在那里的字符串。子进程输出字符串以后,就取消内存映射,关闭文件句柄,然后退出。子进程退出后,父进程就可以取消内存映射、关闭文件句柄并退出。






子进程可以继承父进程所有资源的句柄,最简单的方法是通过命令行传递值。





#include 
#include
#include
using namespace std;
int _tmain(int argc, _TCHAR* argv[]){
STARTUPINFO startup_info;
PROCESS_INFORMATION process_info;
SECURITY_ATTRIBUTES sa;
HANDLE filehandle;
TCHAR ID[]=TEXT("Local\\sharedmemory");
wchar_t* memory;
if (argc==1)
{
//父进程
sa.nLength=sizeof(sa);//设置安全属性
sa.bInheritHandle=TRUE;//使句柄可以被继承
sa.lpSecurityDescriptor=NULL;

filehandle=CreateFileMapping(INVALID_HANDLE_VALUE,&sa,PAGE_READWRITE,0,1024,ID);
memory=(wchar_t*)MapViewOfFile(filehandle,FILE_MAP_ALL_ACCESS,0,0,0);
//用共享内存设置命令行
swprintf(memory,1024,L"\"%s\" %i",argv[0],filehandle);
cout<<"First process memory:"< ZeroMemory(&process_info,sizeof(process_info));
ZeroMemory(&startup_info,sizeof(startup_info));
startup_info.cb=sizeof(startup_info);

//启动子进程
CreateProcess(NULL,memory,0,0,true,0,0,0,&startup_info,&process_info);
WaitForSingleObject(process_info.hProcess,INFINITE);
UnmapViewOfFile(memory);
CloseHandle(filehandle);
}
else{
filehandle=(HANDLE)_wtoi(argv[1]);//从argv[1]获得句柄
memory=(wchar_t*)MapViewOfFile(filehandle,FILE_MAP_ALL_ACCESS,0,0,0);
cout<<"Second process memory : "< UnmapViewOfFile(memory);
CloseHandle(filehandle);
}
getchar();
return 0;
}


技术分享






进程间共享互斥量,可以通过调用CreateMutex或者OpenMutex函数来获取互斥量的句柄。但是,只有一个进程可以创建互斥量,其他的进程只能打开现有的互斥量;互斥量的名称必须唯一;互斥量的名称必须传递给其他进程。


#include 
#include
#include
using namespace std;
int _tmain(int argc, _TCHAR* argv[]){
HANDLE sharedmutex;
STARTUPINFO startup_info;
PROCESS_INFORMATION process_info;
ZeroMemory(&process_info,sizeof(process_info));
ZeroMemory(&startup_info,sizeof(startup_info));
startup_info.cb=sizeof(startup_info);

sharedmutex=CreateMutex(0,0,L"mymutex");
if (GetLastError()!=ERROR_ALIAS_EXISTS)
{
if (CreateProcess(argv[0],0,0,0,0,0,0,0,&startup_info,&process_info)==0)
{
cout<<"Error : "< }
WaitForSingleObject(process_info.hProcess,INFINITE);
}

WaitForSingleObject(sharedmutex,INFINITE);
for (int i=0;i<100;i++)
{
cout<<"Process "< }
ReleaseMutex(sharedmutex);
CloseHandle(sharedmutex);
getchar();
return 0;
}


技术分享






使用共享互斥量来确保两个进程中一次只有一个能计数从0数到19,如果没有互斥量的话,那么两个进程可能同时在跑,则控制台的输出将是混合的输出,使用互斥量以后,一次只有一个进程在输出。



也可以用管道进行通信,管道是流式通信的一种方式,管道有两种命名管道和匿名管道。匿名管道的创建可以调用CreatePipe(),创建命名管道可以调用CreateNamedPipe(),调用WriteFile通过管道发送数据,ReadFile从管道读取数据。


#include 
#include
#include
#include
#include
using namespace std;
HANDLE readpipe,writepipe;
unsigned int __stdcall stage1(void * param)
{
char buf[200];
DWORD len;
for (int i=0;i<10;i++)
{
sprintf(buf,"Text %i",i);
WriteFile(writepipe,buf,strlen(buf)+1,&len,0);
}
CloseHandle(writepipe);
return 0;
}
unsigned int __stdcall stage2(void * param)
{
char buf[200];
DWORD len;
while(ReadFile(readpipe,buf,200,&len,0))
{
DWORD offset=0;
while(offset {
cout<<&buf[offset]< offset+=strlen(&buf[offset])+1;
}
}
CloseHandle(readpipe);
return 0;
}
int _tmain(int argc, _TCHAR* argv[]){
HANDLE thread1,thread2;
CreatePipe(&readpipe,&writepipe,0,0);
thread1=(HANDLE)_beginthreadex(0,0,&stage1,0,0,0);
thread2=(HANDLE)_beginthreadex(0,0,&stage2,0,0,0);
WaitForSingleObject(thread1,INFINITE);
WaitForSingleObject(thread2,INFINITE);
getchar();
return 0;
}


第一个线程将文本信息放入管道,第二个线程接收并输出这些信息。


还可以用套接字进行通信。WindowsSockets API以BSD Sockets API为基础,与类UNIX操作系统的代码很相似。


#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include
#include
#include
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
using namespace std;
HANDLE hevent;
//响应线程
void handleecho(void *data)
{
char buf[1024];
int count;
ZeroMemory(buf,sizeof(buf));
int socket=(int)data;
while((count=recv(socket,buf,1023,0))>0)
{
cout<<"received "< int ret=send(socket,buf,count,0);
}
cout<<"close echo thread"< shutdown(socket,SD_BOTH);
closesocket(socket);
}
//客户端线程
unsigned int __stdcall client(void *data)
{
SOCKET COnnectSockket=socket(AF_INET,SOCK_STREAM,0);

WaitForSingleObject(hevent,INFINITE);

struct sockaddr_in server;
ZeroMemory(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr("192.168.1.107");
server.sin_port=7780;

connect(ConnectSockket,(struct sockaddr*)&server,sizeof(server));

cout<<"send &#39;abcd&#39; to server"< char buf[1024];
ZeroMemory(buf,sizeof(buf));
strncpy_s(buf,1024,"abcd",5);
send(ConnectSockket,buf,strlen(buf)+1,0);

ZeroMemory(buf,sizeof(buf));
recv(ConnectSockket,buf,1024,0);
//cout<<"get "< printf("get &#39;%s&#39; from server\n",buf);

cout<<"close client"< shutdown(ConnectSockket,SD_BOTH);
closesocket(ConnectSockket);
return 0;
}
//服务器线程
unsigned int __stdcall server(void *data)
{
SOCKET newsocket;
SOCKET ServerSocket=socket(AF_INET,SOCK_STREAM,0);

struct sockaddr_in server;
ZeroMemory(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_addr.s_addr=INADDR_ANY;
server.sin_port=7780;

bind(ServerSocket,(struct sockaddr*)&server,sizeof(server));
listen(ServerSocket,SOMAXCONN);

SetEvent(hevent);

while((newsocket=accept(ServerSocket,0,0))!=INVALID_SOCKET)
{
HANDLE newthread;
newthread=(HANDLE)_beginthread(&handleecho,0,(void *)newsocket);
}

cout<<"close server"< shutdown(ServerSocket,SD_BOTH);
closesocket(ServerSocket);
return 0;
}
//主线程启动客户端线程和服务端线程
int _tmain(int argc, _TCHAR* argv[]){
HANDLE serverthread,clienthread;
WSADATA wsaData;

WSAStartup(MAKEWORD(2,2),&wsaData);
hevent=CreateEvent(0,true,0,0);

serverthread=(HANDLE)_beginthreadex(0,0,&server,0,0,0);
clienthread=(HANDLE)_beginthreadex(0,0,&client,0,0,0);
WaitForSingleObject(clienthread,INFINITE);
CloseHandle(clienthread);

CloseHandle(hevent);
getchar();
WSACleanup();
return 0;
}


服务器线程的第一个操作是打开一个套接字,接着绑定连接。套接字置于监听状态,值SOMAXCONN包含排队等待接受的连接的最大值。然后服务器发信号给事件,事件继而使客户端线程尝试连接。接着,主线程循环等待接受连接,直到收到INVALID_SOCKET的连接。Windows套接字关闭时会发生这种情况。服务器线程在其他线程退出后清理退出。服务器每次接受一个连接时都会创建一个新线程,且新连接的标识会传递给新创建的线程。当循环收到INVALID_SOCKET时,服务器线程关闭,然后关闭套接字。






Windows API也提供了很多原子操作,互锁函数。InterlockedIncrement就是一个互锁函数。



#include 
#include
#include
#include
using namespace std;
int isPrime(int num)
{
int i;
for (i=2;i<(int)(sqrt((float)num)+1.0);i++)
{
if (num%i==0)
return 0;
}
return 1;
}
volatile long counter=2;
unsigned int __stdcall test(void *)
{
while (counter<20)
{
int num=InterlockedIncrement(&counter);
//int num=counter++;
printf("Thread ID : %i; value = %i, is prime = %i\n",GetCurrentThreadId(),num,isPrime(num));
}
return 0;
}
int _tmain(int argc,_TCHAR* argv[])
{
HANDLE h1,h2;
h1=(HANDLE)_beginthreadex(0,0,&test,(void *)0,0,0);
h2=(HANDLE)_beginthreadex(0,0,&test,(void *)0,0,0);
WaitForSingleObject(h1,INFINITE);
WaitForSingleObject(h2,INFINITE);
CloseHandle(h1);
CloseHandle(h2);
getchar();
return 0;
}



还有一个问题就是线程本地存储(TLS, ThreadLocal Storage),TLS 是一个机制,利用该机制,程序可以拥有全局变量,但处于“每一线程各不相同”的状态。也就是说,进程中的所有线程都可以拥有全局变量,但这些变量其实是特定对某个线程才有意义,各个线程拥有全局变量的一个副本,各自之间不相影响。每个线程访问数据的方式相同,但看不到其他线程持有的值。比如说,定义一个全局变量int a=10,那么在线程1中对a进行操作a=a-1,如果没用TLS,那么线程2开始获得的a就是9。但是,如果采取了TLS,不管线程1中对a的值进行了如何的修改操作,其他的线程一开始获得的a还是10,不会被修改。这个全局的变量a是没有存储在线程堆栈中的,是在全局的堆栈中,但是却被各个线程“共享”且互不影响。可以认为线程本地存储的本质是“全局”数据的作用域受到了执行线程的限制。


线程本地分配可以调用__declspec、TlsAlloc()等函数。TlsAlloc可以分配全局索引,该索引由所有线程共享,但是每个线程存储在索引中的数据为调用的线程私有,也就是说其他线程看不到持有的值。当不再需要全局索引提供线程本地存储时,可以调用TlsFree来释放全局索引。


给个例子。


#include 
#include
#include
#include
using namespace std;
DWORD TLSIndex;
void setdata(int value)
{
cout<<"Thread "<#include
#include
#include
#include
#include
using namespace std;
unsigned int __stdcall fastthread(void *data)
{
double d=1.0;
cout<<"fast thread started"< SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_ABOVE_NORMAL);
clock_t start=clock();
for (int i=0;i<1000000000;i++)
{
d+=i;
}
clock_t end=clock();
cout<<"fast thread finished, it takes "<<(double)(end-start)/CLOCKS_PER_SEC<<"s to finish the task"< return 0;
}
unsigned int __stdcall slowthread(void *data)
{
double d=0.0;
cout<<"slow thread started"< SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_BELOW_NORMAL);
clock_t start=clock();
for (int i=0;i<1000000000;i++)
{
d+=i;
}
clock_t end=clock();
cout<<"slow thread finished, it takes "<<(double)(end-start)/CLOCKS_PER_SEC<<"s to finnish the task"< return 0;
}
int _tmain(int argc,_TCHAR* argv[])
{
HANDLE fast,slow;
slow=(HANDLE)_beginthreadex(0,0,&slowthread,0,0,0);
fast=(HANDLE)_beginthreadex(0,0,&fastthread,0,0,0);
WaitForSingleObject(fast,INFINITE);
WaitForSingleObject(slow,INFINITE);
getchar();
return 0;
}


技术分享


有时候调整线程的优先级会带来优先级反转的问题。


小结

主要实现了windows操作系统中IPC的API,主要有进程之间共享内存、子进程中继承句柄、互斥量、管道、套接字等。此外,还有Windows中的互锁函数。线程本地化存储(TLS)、线程的优先级等。













版权声明:本文为博主原创文章,未经博主允许不得转载。


Windows 多进程通信API总结



推荐阅读
  • Linux设备驱动程序:异步时间操作与调度机制
    本文介绍了Linux内核中的几种异步延迟操作方法,包括内核定时器、tasklet机制和工作队列。这些机制允许在未来的某个时间点执行任务,而无需阻塞当前线程,从而提高系统的响应性和效率。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 使用Vultr云服务器和Namesilo域名搭建个人网站
    本文详细介绍了如何通过Vultr云服务器和Namesilo域名搭建一个功能齐全的个人网站,包括购买、配置服务器以及绑定域名的具体步骤。文章还提供了详细的命令行操作指南,帮助读者顺利完成建站过程。 ... [详细]
  • 本文提供了使用Java实现Bellman-Ford算法解决POJ 3259问题的代码示例,详细解释了如何通过该算法检测负权环来判断时间旅行的可能性。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 深入探讨CPU虚拟化与KVM内存管理
    本文详细介绍了现代服务器架构中的CPU虚拟化技术,包括SMP、NUMA和MPP三种多处理器结构,并深入探讨了KVM的内存虚拟化机制。通过对比不同架构的特点和应用场景,帮助读者理解如何选择最适合的架构以优化性能。 ... [详细]
  • 微软Exchange服务器遭遇2022年版“千年虫”漏洞
    微软Exchange服务器在新年伊始遭遇了一个类似于‘千年虫’的日期处理漏洞,导致邮件传输受阻。该问题主要影响配置了FIP-FS恶意软件引擎的Exchange 2016和2019版本。 ... [详细]
  • 作者:守望者1028链接:https:www.nowcoder.comdiscuss55353来源:牛客网面试高频题:校招过程中参考过牛客诸位大佬的面经,但是具体哪一块是参考谁的我 ... [详细]
  • 在Ubuntu 16.04 LTS上配置Qt Creator开发环境
    本文详细介绍了如何在Ubuntu 16.04 LTS系统中安装和配置Qt Creator,涵盖了从下载到安装的全过程,并提供了常见问题的解决方案。 ... [详细]
author-avatar
yangyang19890811
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有