说说I/O与IPC

程间通信IPC,也就是Inter-Process Communication的缩写。一个进程其实就是一个狭义上的程序。


广义上:一个服务器也就是一个进程。比如客户端和服务器的连接就是两个进程在通信,只是这两个进程并不在同一台计算机上,它们进程间的通信方式就是我们非常熟悉的sockt接口,更下层一些可能就是TCP/IP协议。这样进程与进程间通信就组成了更大的一个系统,也就是说,在网络系统上,我们需要进程通信完成协作任务或者交互任务。比如P2P的软件就是两个不同计算机的程序进行数据交互,从而完成下载或通信的任务。


狭义上:我们一台主机上需要两个进程共享数据,如:我们需要打开另一个进程保存的文件,我们有时候需要访问正在运行的另外进程的数据对象或者通知另外进程具体完成任务等。

进程由于是内核(OS kernel)的功能,因此进程的创建和通信其实和各种OS紧密相关的。当然现在OS在架构上有很多思想是想通的,所以进程间通信在各种操作系统上有不同也有相同的地方。如window和Linux有很多类似的地方,也有较多的区别,Android系统也提供自己的IPC机制,iOS也有自己的类似的IPC机制。

另外,当前很多语言和框架(Framework)为了实现跨平台性,都封装了进程的操作,将线程的使用提高到了语言的层面。而不用developer再去进行系统调用(或系统API)来操作进程了。比如Java语言,进程和线程就是其语言提供的。再比如QT框架,也提供了进程线程的操作,弥补了C/C++语言上没有提供进程线程的遗憾。我们这里还是有必要了解下各个OS在系统调用层次的进程操作。


一. 在Windows OS上,有几种常用的进程间通信方式:
1 .文件映射
文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待。因此,进程不必使用文件I/O操作,只需简单的指针操作就可读取和修改文件的内容。
Win32 API允许多个进程访问同一文件映射对象,各个进程在它自己的地址空间里接收内存的指针。通过使用这些指针,不同进程就可以读或修改文件的内容,实现了对文件中数据的共享。
应用程序有三种方法来使多个进程共享一个文件映射对象。
(1)继承:第一个进程建立文件映射对象,它的子进程继承该对象的句柄。
(2)命名文件映射:第一个进程在建立文件映射对象时可以给该对象指定一个名字(可与文件名不同)。第二个进程可通过这个名字打开此文件映射对象。另外,第一个进程也可以通过一些其它IPC机制(有名管道、邮件槽等)把名字传给第二个进程。
(3)句柄复制:第一个进程建立文件映射对象,然后通过其它IPC机制(有名管道、邮件槽等)把对象句柄传递给第二个进程。第二个进程复制该句柄就取得对该文件映射对象的访问权限。
文件映射是在多个进程间共享数据的非常有效方法,有较好的安全性。但文件映射只能用于本地机器的进程之间,不能用于网络中,而开发者还必须控制进程间的同步。

2. 共享内存
Win32 API中共享内存(Shared Memory)实际就是文件映射的一种特殊情况。进程在创建文件映射对象时用0xFFFFFFFF来代替文件句柄(HANDLE),就表示了对应的文件映射对象是从操作系统页面文件访问内存,其它进程打开该文件映射对象就可以访问该内存块。由于共享内存是用文件映射实现的,所以它也有较好的安全性,也只能运行于同一计算机上的进程之间。

3. 匿名管道
管道(Pipe)是一种具有两个端点的通信通道:有一端句柄的进程可以和有另一端句柄的进程通信。管道可以是单向-一端是只读的,另一端点是只写的;也可以是双向的一管道的两端点既可读也可写。
匿名管道(Anonymous Pipe)是 在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。通常由父进程创建管道,然后由要通信的子进程继承通道的读端点句柄或写 端点句柄,然后实现通信。父进程还可以建立两个或更多个继承匿名管道读和写句柄的子进程。这些子进程可以使用管道直接通信,不需要通过父进程。
匿名管道是单机上实现子进程标准I/O重定向的有效方法,它不能在网上使用,也不能用于两个不相关的进程之间。
4 .命名管道
命名管道(Named Pipe)是服务器进程和一个或多个客户进程之间通信的单向或双向管道。不同于匿名管道的是命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。
命名管道提供了相对简单的编程接口,使通过网络传输数据并不比同一计算机上两进程之间通信更困难,不过如果要同时和多个进程通信它就力不从心了。

5. 动态连接库
Win32动态连接库(DLL)中的全局数据可以被调用DLL的所有进程共享,这就又给进程间通信开辟了一条新的途径,当然访问时要注意同步问题。虽然可以通过DLL进行进程间数据共享,但从数据安全的角度考虑,我们并不提倡这种方法,使用带有访问权限控制的共享内存的方法更好一些。
6 .远程过程调用
Win32 API提供的远程过程调用(RPC)使应用程序可以使用远程调用函数,这使在网络上用RPC进行进程通信就像函数调用那样简单。RPC既可以在单机不同进程间使用也可以在网络中使用。
由于Win32 API提供的RPC服从OSF-DCE(Open Software Foundation Distributed Computing Environment)标准。所以通过Win32 API编写的RPC应用程序能与其它操作系统上支持DEC的RPC应用程序通信。使用RPC开发者可以建立高性能、紧密耦合的分布式应用程序。
7 .Sockets
Windows Sockets规范是以U.C.Berkeley大学BSD UNIX中流行的Socket接口为范例定义的一套Windows下的网络编程接口。除了Berkeley Socket原有的库函数以外,还扩展了一组针对Windows的函数,使程序员可以充分利用Windows的消息机制进行编程。
现在通过Sockets实现进程通信的网络应用越来越多,这主要的原因是Sockets的跨平台性要比其它IPC机制好得多,另外WinSock 2.0不仅支持TCP/IP协议,而且还支持其它协议(如IPX)。Sockets的唯一缺点是它支持的是底层通信操作,这使得在单机的进程间进行简单数据传递不太方便,这时使用下面将介绍的WM_COPYDATA消息将更合适些。
8 .WM_COPYDATA消息
WM_COPYDATA是一种非常强大却鲜为人知的消息。当一个应用向另一个应用传送数据时,发送方只需使用调用SendMessage函数,参数是目的窗口的句柄、传递数据的起始地址、WM_COPYDATA消息。接收方只需像处理其它消息那样处理WM_COPY DATA消息,这样收发双方就实现了数据共享。
WM_COPYDATA是一种非常简单的方法,它在底层实际上是通过文件映射来实现的。它的缺点是灵活性不高,并且它只能用于Windows平台的单机环境下。

下面是c实现socket UDP,TCP传输数据:

tcp server:

#include "stdafx.h"  
#include <stdio.h>  
#include <winsock2.h>  
  
#pragma comment(lib,"ws2_32.lib")  
  
int main(int argc, char* argv[])  
{  
    //初始化WSA  
    WORD sockVersion = MAKEWORD(2,2);  
    WSADATA wsaData;  
    if(WSAStartup(sockVersion, &wsaData)!=0)  
    {  
        return 0;  
    }  
  
    //创建套接字  
    SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  
    if(slisten == INVALID_SOCKET)  
    {  
        printf("socket error !");  
        return 0;  
    }  
  
    //绑定IP和端口  
    sockaddr_in sin;  
    sin.sin_family = AF_INET;  
    sin.sin_port = htons(8888);  
    sin.sin_addr.S_un.S_addr = INADDR_ANY;   
    if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)  
    {  
        printf("bind error !");  
    }  
  
    //开始监听  
    if(listen(slisten, 5) == SOCKET_ERROR)  
    {  
        printf("listen error !");  
        return 0;  
    }  
  
    //循环接收数据  
    SOCKET sClient;  
    sockaddr_in remoteAddr;  
    int nAddrlen = sizeof(remoteAddr);  
    char revData[255];   
    while (true)  
    {  
        printf("等待连接...\n");  
        sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);  
        if(sClient == INVALID_SOCKET)  
        {  
            printf("accept error !");  
            continue;  
        }  
        printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));  
          
        //接收数据  
        int ret = recv(sClient, revData, 255, 0);          
        if(ret > 0)  
        {  
            revData[ret] = 0x00;  
            printf(revData);  
        }  
  
        //发送数据  
        char * sendData = "你好,TCP客户端!\n";  
        send(sClient, sendData, strlen(sendData), 0);  
        closesocket(sClient);  
    }  
      
    closesocket(slisten);  
    WSACleanup();  
    return 0;  
}  
tcp client:

#include "stdafx.h"  
#include <WINSOCK2.H>  
#include <STDIO.H>  
  
#pragma  comment(lib,"ws2_32.lib")  
  
  
int main(int argc, char* argv[])  
{  
    WORD sockVersion = MAKEWORD(2,2);  
    WSADATA data;   
    if(WSAStartup(sockVersion, &data) != 0)  
    {  
        return 0;  
    }  
  
    SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  
    if(sclient == INVALID_SOCKET)  
    {  
        printf("invalid socket !");  
        return 0;  
    }  
  
    sockaddr_in serAddr;  
    serAddr.sin_family = AF_INET;  
    serAddr.sin_port = htons(8888);  
    serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");   
    if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)  
    {  
        printf("connect error !");  
        closesocket(sclient);  
        return 0;  
    }  
    char * sendData = "你好,TCP服务端,我是客户端!\n";  
    send(sclient, sendData, strlen(sendData), 0);  
  
    char recData[255];  
    int ret = recv(sclient, recData, 255, 0);  
    if(ret > 0)  
    {  
        recData[ret] = 0x00;  
        printf(recData);  
    }  
    closesocket(sclient);  
    WSACleanup();  
    return 0;  
}  

UDP server:

#include "stdafx.h"  
#include <stdio.h>  
#include <winsock2.h>  
  
#pragma comment(lib, "ws2_32.lib")   
  
int main(int argc, char* argv[])  
{  
    WSADATA wsaData;  
    WORD sockVersion = MAKEWORD(2,2);  
    if(WSAStartup(sockVersion, &wsaData) != 0)  
    {  
        return 0;  
    }  
  
    SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);   
    if(serSocket == INVALID_SOCKET)  
    {  
        printf("socket error !");  
        return 0;  
    }  
  
    sockaddr_in serAddr;  
    serAddr.sin_family = AF_INET;  
    serAddr.sin_port = htons(8888);  
    serAddr.sin_addr.S_un.S_addr = INADDR_ANY;  
    if(bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)  
    {  
        printf("bind error !");  
        closesocket(serSocket);  
        return 0;  
    }  
      
    sockaddr_in remoteAddr;  
    int nAddrLen = sizeof(remoteAddr);   
    while (true)  
    {  
        char recvData[255];    
        int ret = recvfrom(serSocket, recvData, 255, 0, (sockaddr *)&remoteAddr, &nAddrLen);  
        if (ret > 0)  
        {  
            recvData[ret] = 0x00;  
            printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));  
            printf(recvData);              
        }  
  
        char * sendData = "一个来自服务端的UDP数据包\n";  
        sendto(serSocket, sendData, strlen(sendData), 0, (sockaddr *)&remoteAddr, nAddrLen);      
  
    }  
    closesocket(serSocket);   
    WSACleanup();  
    return 0;  
}  
UDP client:

#include "stdafx.h"  
#include <stdio.h>  
#include <winsock2.h>  
  
#pragma comment(lib, "ws2_32.lib")   
  
int main(int argc, char* argv[])  
{  
    WORD socketVersion = MAKEWORD(2,2);  
    WSADATA wsaData;   
    if(WSAStartup(socketVersion, &wsaData) != 0)  
    {  
        return 0;  
    }  
    SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);  
      
    sockaddr_in sin;  
    sin.sin_family = AF_INET;  
    sin.sin_port = htons(8888);  
    sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");  
    int len = sizeof(sin);  
      
    char * sendData = "来自客户端的数据包.\n";  
    sendto(sclient, sendData, strlen(sendData), 0, (sockaddr *)&sin, len);  
  
    char recvData[255];       
    int ret = recvfrom(sclient, recvData, 255, 0, (sockaddr *)&sin, &len);  
    if(ret > 0)  
    {  
        recvData[ret] = 0x00;  
        printf(recvData);  
    }  
  
    closesocket(sclient);  
    WSACleanup();  
    return 0;  
}  

系统监听端口的方式就是socket实现。如果深入了解系统的socket机制,阅读《操作系统源码解读 reactOS》-- 毛德操。

上面的IPC通信中的文件读写和网络交互数据读写就是I/O部分。

二. Linux下进程间通信的几种主要手段简介:
管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和其变种都支持套接字。


三.Android 系统IPC

1.broadcast 广播,Android 组件。

2.contentprovider Android组件。

3.socket 网络交互。

4.binder机制,此机制基于管道,信号机制。

5.AIDL 机制,其实基于对象数据序列化的机制。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值