进程间通信

本文详细介绍了Windows环境下进程间通信的六种方式:共享内存、命名管道、邮件槽、socket连接、DLL共享数据段及WM_COPYDATA消息。每种方式都提供了具体的代码示例,帮助读者理解如何在不同场景下实现进程间数据交换。


1、共享内存
使用CreateFileMapping函数创建文件映射,指定第一个参数为INVALID_HANDLE_VALUE表明在系统页文件中开辟共享内存,最后一个参数指定文件映射的名字。
使用MapViewOfFile将已创建的文件映射映射到当前进程地址空间,可以读写访问。

另一个进程可以使用OpenFileMapping通过制定文件映射名字,打开一个已经创建的文件映射。
使用MapViewOfFile将已创建的文件映射映射到当前进程地址空间,可以读写访问。

这样两个进程都可以讲共享内存映射到自己的进程地址空间,进行通信。
通信完毕后需要使用UnmapViewOfFile取消映射,CloseHandle销毁文件映射。
只适用于同一机器上的进程间通信。

写数据的进程:

#include  < iostream >
#include  < windows.h >
#include  < process.h >
using   namespace  std;



int  main()
{
    //创建文件映射,指向操作系统页面文件
    HANDLE hFileMapping = ::CreateFileMapping((HANDLE)INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(int), "TestMem");

    //将共享内存映射到当前进程地址空间
    int* addr = (int*)::MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 0, sizeof(int));
    
    //向共享内存中写数据
    *addr = 23;
    getchar();

    //解除共享内存到当前进程地址空间的映射
   :: UnmapViewOfFile(addr);

    //销毁文件映射
    ::CloseHandle(hFileMapping);
    return 0;
}


读数据的进程:
#include  < iostream >
#include  < windows.h >
#include  < process.h >
using   namespace  std;



int  main()
{
    //打开一个已命名的文件映射
    HANDLE hFileMapping = ::OpenFileMapping(FILE_MAP_READ, false, "TestMem");

    //将其映射到进程地址空间
    int* addr = (int*)::MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, sizeof(int));
    
    //读取共享内存中的数据
    cout<<*addr<<endl;
    getchar();


    //解除到进程地址空间的映射
    :: UnmapViewOfFile(addr);

    //关闭文件映射
    ::CloseHandle(hFileMapping);
    return 0;
}





2、命名管道
服务器是唯一一个有权创建命名管道的进程,也只有它才能接受管道客户机的连接请求。
对一个客户机应用来说,它只能同一个已命名的管道服务器建立连接。
在客户机和服务器之间,一旦建好连接,两个进程都能对标准的Win32函数,在管道上进行数据的读取与写入。这些函数包括ReadFile和WriteFile等等。
命名管道的命名格式:    //ServerName/Pipe/PipeName
其中ServerName是服务器名字,Pipe固定表示管道,PipeName是管道名称。
命名管道适用于单机或网络进程间通信。

服务器端
#include  < iostream >
#include  < windows.h >
#include  < process.h >
using   namespace  std;



int  main()
{
    //创建一个命名管道
    HANDLE hPipe = ::CreateNamedPipe("////.//pipe//MyPipe", PIPE_ACCESS_DUPLEX, 0, 2, 0, 0, 0, NULL);

    //将服务器端连接到命名管道,并监听是否有客户端连接到已创建的命名管道,若有则返回
    ::ConnectNamedPipe(hPipe, NULL);

    //从管道中读数据
    char cBuffer[1024] = {0}
    DWORD dwSize = 0;
    ::ReadFile(hPipe, cBuffer, 1024, &dwSize, NULL);
    cout<<cBuffer<<endl;

    //断开服务器端与命名管道的连接
    ::DisconnectNamedPipe(hPipe);

    //销毁管道
    ::CloseHandle(hPipe);
    return 0;
}


客户端
#include  < iostream >
#include  < windows.h >
#include  < process.h >
using   namespace  std;



int  main()
{
    //客户端等待,直到指定的命名管道可用
    ::WaitNamedPipe(L"////.//pipe//MyPipe", NMPWAIT_WAIT_FOREVER);

    //创建一个命名管道的实例
    HANDLE hPipe = ::CreateFile(L"////.//pipe//MyPipe", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    //写数据
    const char* cWrite = "test pipe";
    DWORD dwSize = 0;
    ::WriteFile(hPipe, cWrite, (DWORD)strlen(cWrite)+1, &dwSize, NULL);

    //关闭管道
    ::CloseHandle(hPipe);
    return 0;
}





3、邮件槽
邮件槽(Mailslots)提供进程间单向通信能力,服务器端创建邮件槽,客户端通过名字连接邮件槽,同一个邮件槽只允许客户端向服务器端发送数据。
邮件槽命名与命名管道类似。
支持单机或不同计算机之间的进程间通信。

服务器端
#include  < iostream >
#include  < windows.h >
#include  < process.h >
using   namespace  std;



int  main()
{
    //创建一个邮件槽
    //第三个参数若为0,ReadFile即使读不到数据也立刻返回;若为MAILSLOT_WAIT_FOREVER,ReadFile将等待知道读到数据
    HANDLE hMail = ::CreateMailslot("////.//mailslot//MyMail", 0, MAILSLOT_WAIT_FOREVER, NULL);

    //从邮件槽中读数据
    char cBuffer[1024] = {0}
    DWORD dwSize = 0;
    ::ReadFile(hMail, cBuffer, 1024, &dwSize, NULL);
    cout<<cBuffer<<endl;

    //销毁邮件槽
    ::CloseHandle(hMail);
    return 0;
}


客户端
#include  < iostream >
#include  < windows.h >
#include  < process.h >
using   namespace  std;



int  main()
{
    //打开一个已命名的邮件槽
    //第二个参数必须设为GENERIC_WRITE,因为客户机只能向服务器写入数据。
    //第三个参数必须设为FILE_SHARE_READ,允许服务器在邮槽上打开和进行读操作。
    HANDLE hMail = ::CreateFile(L"////.//mailslot//MyMail", GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    //写数据
    const char* cWrite = "test mail";
    DWORD dwSize = 0;
    ::WriteFile(hMail, cWrite, (DWORD)strlen(cWrite)+1, &dwSize, NULL);

    //关闭邮件槽
    ::CloseHandle(hMail);
    return 0;
}




4、socket连接
服务器端和客户端通过socket连接通信,适用于网络间进程通信。

服务器端
#include  < iostream >
#include  < winsock.h >
#pragma comment (lib,  " Ws2_32.lib " )

using   namespace  std;


int  main()
{
    //初始化使用socket函数要用到的dll
    WSADATA wd;
    ::WSAStartup(MAKEWORD(2,2), &wd);

    //本机地址信息
    sockaddr_in addr;
    addr.sin_family            = AF_INET;
    addr.sin_port            = htons(1555);
    addr.sin_addr.s_addr    = inet_addr("127.0.0.1");

    //创建套接字,并与本机地址绑定
    SOCKET s = ::socket(AF_INET, SOCK_STREAM, 0);
    ::bind(s, (const sockaddr*)&addr, sizeof(addr));
    
    //监听该套接字,准备接收连接
    ::listen(s, 2);

    //等待客户端连接,若客户端连接则返回,否则一直等待
    //返回结果是用来通信的新套接字
    sockaddr_in client_addr;
    SOCKET ns = ::accept(s, (sockaddr*)&client_addr, NULL);

    //在新套接字上收数据
    while(1)
    {
        char cBuffer[1024] = {0};
        ::recv(ns, cBuffer, 1024, 0);
        cout<<cBuffer<<endl;
    }


    //关闭套接字
    ::closesocket(ns);
    ::closesocket(s);

    //ws2.dll的收尾工作
    ::WSACleanup();

    return 0;
}


客户端
#include  < iostream >
#include  < winsock.h >
#pragma comment (lib,  " Ws2_32.lib " )

using   namespace  std;


int  main()
{
    //初始化使用socket函数要用到的dll
    WSADATA wd;
    WSAStartup(MAKEWORD(2,2), &wd);

    //服务器地址信息
    sockaddr_in addr;
    addr.sin_family            = AF_INET;
    addr.sin_port            = htons(1555);
    addr.sin_addr.s_addr    = inet_addr("127.0.0.1");

    //创建套接字,并与服务器连接
    SOCKET s = ::socket(AF_INET, SOCK_STREAM, 0);
    ::connect(s, (const sockaddr*)&addr, sizeof(addr));

    //发送数据
    while(1)
    {
        char cData[1024] = {0};
        cin>>cData;
        ::send(s, cData, (int)strlen(cData)+1, 0);
    }


    //关闭套接字
    ::closesocket(s);

    //ws2.dll的收尾工作
    ::WSACleanup();

    return 0;
}




5、dll共享数据段
动态链接库被加载后,映射到各自进程地址空间,但位于共享数据段内的变量为所有加载该dll的进程所共享。
把需要共享的数据变量放入自定义的数据段中, 需要初始化的变量放入   #pragma   data_seg()定义的数据段中;不需初始化的变量放入#pragma   bss_seg()定义的数据段中。
然后打开/SECTION 开关,请连接器为这些数据段定义共享属性。
  
dll文件
// 共享数据段
#pragma data_seg( " Shared " ) 
__declspec(dllexport)  char  g_SharedBuffer[ 1024 ]  =   {0}
#pragma data_seg() 

#pragma comment(linker,  " /Section:Shared,rws " )

// 非共享数据段
__declspec(dllexport)  char  g_Buffer[ 1024 ]  =   {0}

进程1写数据
#include  < iostream >
#pragma comment (lib,  " ..//..//dll//release//dll " )

using   namespace  std;


int  main()
{
    //导入dll中的变量
    __declspec(dllimport) char g_Buffer[];
    __declspec(dllimport) char g_SharedBuffer[];

    //分别向两个变量中写入数据
    strcpy(g_Buffer, "not shared");
    strcpy(g_SharedBuffer, "shared");

    cout<<g_Buffer<<endl;
    cout<<g_SharedBuffer<<endl;

    getchar();
    return 0;
}


进程2读数据
#include  < iostream >
#pragma comment (lib,  " ..//..//dll//release//dll " )

using   namespace  std;


int  main()
{
    //导入dll中的变量
    __declspec(dllimport) char g_Buffer[];
    __declspec(dllimport) char g_SharedBuffer[];

    //输出结果
    cout<<g_Buffer<<endl;            //非共享数据段内的变量没有进程1写的值
    cout<<g_SharedBuffer<<endl;        //共享数据段内的变量读出了进程1写的值

    getchar();
    return 0;
}



6、WM_COPYDATA消息
发送方只需使用调用SendMessage函数,参数是目的窗口的句柄、传递数据的起始地址、WM_COPYDATA消息。
接收方只需像处理其它消息那样处理WM_COPY DATA消息,这样收发双方就实现了数据共享。

7、剪切板

8、匿名管道
在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。
通常由父进程创建管道,然后由要通信的子进程继承通道的读端点句柄或写端点句柄,然后实现通信。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值