程序员必须让拥有依赖关系的进程集协调,这样才能达到进程的共同目标。可以使用两种技术来达到协调。第一种技术在具有通信依赖关系的两个进程间传递信息。这种技术称做进程间通信(interprocess communication)。第二种技术是同步,当进程间相互具有合作依赖时使用。这两种类型的依赖关系可以同时存在。
一般而言,进程有单独的地址空间。我们可以了解下可执行程序被装载到内存后建立的一系列映射等理解这一点。如此以来意味着如果我们有两个进程(进程A和进程B),那么,在进程A中声明的数据对于进程B是不可用的。而且,进程B看不到进程A中发生的事件,反之亦然。如果进程A和B一起工作来完成某个任务,必须有一个在两个进程间通信信息和时间的方法。我们这里可以去看看基本的进程组件。注意进程有一个文本、数据以及堆栈片断。进程可能也有从自由存储空间中分配的其它内存。进程所占有的数据一般位于数据片断、堆栈片断或进程的动态分配内存中。数据对于其它进程来说是受保护的。为了让一个进程访问另外一个进程的数据,必须最终使用操作系统调用。与之类似,为了让一个进程知道另一个进程中文本片断中发生的事件,必须在进程间建立一种通信方式。这也需要来自操作系统API的帮助。当进程将数据发送到另一进程时,称做IPC(interprocess communication,进程间通信)。下面先列举几种不同类型的进程间通信方式:
进程间通信 描述
环境变量/文件描述符 子进程接受父进程环境数据的拷贝以及所有文件描述符。父进程可以在它的数据片断或环境中设置一定的变量,同时子进程接收这些值。父进程可以打开文件,同时推进读/写指针的位置,而且子进程使用相同的偏移访问该文件。
命令行参数 在调用exec或派生函数期间,命令行参数可以传递给子进程。
管道 用于相关和无关进程间的通信,而且形成两个进程间的一个通信通道,通常使用文件读写程序访问。
共享内存 两个进程之外的内存块,两个进程均可以访问它。
DDE(动态数据交换, 使用客户机/服务器模型(C/S),服务器对客户的数据
Dynamic data exchange) 或动作请求作出反应。
一、环境变量、文件描述符:
当创建一个子进程时,它接受了父进程许多资源的拷贝。子进程接受了父进程的文本、堆栈
以及数据片断的拷贝。子进程也接受了父进程的环境数据以及所有文件描述符的拷贝。子进
程从父进程继承资源的过程创造了进程间通信的一个机会。父进程可以在它的数据片断或环
境中设置一定的变量,子进程于是接受这些值。同样,父进程也可以打开一个文件,推进到
文件内的期望位置,子进程接着就可以在父进程离开读/写指针的准确位置访问该文件。
这类通信的缺陷在于它是单向的、一次性的通信。也就是说,除了文件描述外,如果子进程
继承了任何其它数据,也仅仅是父进程拷贝的所有数据。 一旦创建了子进程,由子进程对
这些变量的任何改变都不会反映到父进程的数据中。同样,创建子进程后,对父进程数据的
任何改变也不会反映到子进程中。所以,这种类型的进程间通信更像指挥棒传递。一旦父进
程传递了某些资源的拷贝,子进程对它的使用就是独立的,必须使用原始传递资源。
二、命令行参数:
通过命令行参数(command-line argument)可以完成另一种单向、一次性的进程间通信
我前面的文章已经提到过使用命令行参数。命令行参数在调用一个exec或派生调用操作系
统时传递给子进程。命令行参数通常在其中一个参数中作为NULL终止字符串传递给exec
或派生函数调用。这些函数可以按单向、一次性方式给子进程传递值。WINDOWS有调用执行
exe程序的API。大家可以去参考一下ShellExecuteA函数相关。
三、管道通信:
继承资源以及命令行参数是最简单形式的进程间通信。它们同时有两个主要限制。除了文件
描述符外,继承资源是IPC的单向、一次性形式。传递命令参数也是单向、一次性的IPC
方法。这些方法也只有限制于关联进程,如果不关联,命令行参数和继承资源不能使用。还
有另一种结构,称做管道(Pipe),它可以用于在关联进程间以及无关联进程间进行通信。
管道是一种数据结构,像一个序列化文件一样访问。它形成了两个进程间的一种通信渠道。
管道结构通过使用文本和写方式来访问。如果进程A希望通过管道发送数据给进程B,那么
进程A向管道写入数据。为了让进程B接收此数据,进程B必须读取管道,与命令行参数的
IPC形式不一样。管道可以双向通信。两进程间的数据流是双向通信的。管道可以在程序的
整个执行期间使用,在进程间发送和接收数据。所以,管道充当可访问管道的进程间的一种
可活链接,有两种基本管道类型:
1. 匿名管道
2. 命名管道
上面的图可以看出在没有管道时,两进程是不能互写的。
建立管道后就可以相互通信了。
只有关联进程可以使用匿名管道来通信。无关联进程必须使用命名管道。
匿名管道:通过文件描述符或文件句柄提供对匿名管道的访问。对系统API的调用创建一个管道,并返回一个文件描述符。这个文件描述符是用作read()或write()函数的一个参数。当通过文件描述符调用read()或write()时,数据的源和目标就是管道。例如,在OS/2环境中使用操作系统函数DosCreatePipe()创建匿名管道:
int mian( void )
{
PFHILE readHandle;
PFHILE writeHandle;
DosCreatePipe( readHandle, writeHandle, size );
…
…
…
}
在WINDOWS下例如我写的ASM集成环境通过管道与DOS命令行通信的MFC下代码块:
void CIDEManager::Commond( CString cmd, char* buf, unsigned int bufsize )
{
SECURITY_ATTRIBUTES sa;
HANDLE hRead, hWrite;
sa.nLength = sizeof( SECURITY_ATTRIBUTES );
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
if ( !CreatePipe( &hRead, &hWrite, &sa, 0 ) ) // 创建管道
{
return;
}
STARTUPINFO si;
PROCESS_INFORMATION pi;
si.cb = sizeof( STARTUPINFO );
GetStartupInfo( &si );
si.hStdError = hWrite;
si.hStdOutput = hWrite;
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
if ( !CreateProcess( NULL, ( LPTSTR )( LPCTSTR )cmd, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi ) )
{
return;
}
CloseHandle( hWrite );
DWORD bytesRead;
while ( TRUE )
{
memset( buf, 0, bufsize );
if ( ReadFile( hRead, buf, bufsize, &bytesRead, NULL ) != NULL )
{
break;
}
Sleep( 200 );
}
CloseHandle( hRead );
return;
}
命名管道:将管道用作两个无关联进程间的通信渠道,程序员必须使用命名管道,它可以看作一种具有某名字的特殊类型文件。进程可以根据它的名字访问这个管道。通过匿名管道,父和子进程可以单独使用文件描述符来访问他们所共享的管道,因为子进程继承了父进程的文件描述符,同时文件描述符用read()或write()函数的参数。因为无关进程不能访问彼此的文件描述符,所以不能使用匿名管道。由于命名管道提供该管道的一个等价文件名,任何知道此管道名字的进程都可以访问它。下面是命名管道相对于匿名管道的优点:
命名管道可以被无关联进程使用。
命名管道可以持久。创建它的程序退出后,它们仍然可以存在。
命名管道可以在网络或分布环境中使用。
命名管道容易用于多对一关系中。
与访问匿名管道一样,命名管道也是通过read()或write()函数来访问。两者之间的主要区别在于命名管道的创建方式以及谁可以反问它们。命名管道可以建立一个进程间通信的C/S模型。访问命名管道的进程可能都位于同一台机器上,或位于通过网络通信的不同机器上。由于管道的名字可以通过管道所在服务器的逻辑名,所以能够跨网络访问管道。例如,////ServerName//Pipe//MyPipe(不区分大小写)可以作为一个管道名字。假如Server1是网络服务器的名字。当打开或访问这个管道的调用解析文件名时,首先应该定位Server1,然后访问MyPipe。例子如下:
服务器端:
int main( void )
{
HANDLE pipehandle;
char buf[ 256 ];
DWORD bytesRead;
if( ( pipehandle = CreateNamedPipe( "////.//Pipe//cao", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 5000, NULL ) ) == INVALID_HANDLE_VALUE )
{
printf( "CreateNamedPipe failed with error %d/n", GetLastError() );
system( "pause" );
return 0;
}
printf( "server is running/n" );
if( ConnectNamedPipe( pipehandle, NULL ) == 0 )
{
printf( "connectNamedPipe failed with error %d/n", GetLastError() );
CloseHandle( pipehandle );
system( "pause" );
return 0;
}
if( ReadFile( pipehandle, buf, sizeof( buf ), &bytesRead, NULL ) == 0 )
{
printf( "ReadFile failed with error %d/n", GetLastError() );
CloseHandle( pipehandle );
system( "pause" );
return 0;
}
printf( "%s/n", buf );
if ( DisconnectNamedPipe( pipehandle ) == 0 )
{
printf( "DisconnectNamedPipe failed with error %d/n", GetLastError() );
CloseHandle( pipehandle );
system( "pause" );
return 0;
}
system( "pause" );
return 0;
}
客户端:
int main( void )
{
HANDLE pipehandle;
DWORD writesbytes;
char buff[ 256 ];
if( WaitNamedPipe( "////.//Pipe//cao", NMPWAIT_WAIT_FOREVER ) == 0 )
{
printf( "WaitNamedPipe failed with error %d/n", GetLastError() );
system( "pause" );
return 0;
}
if( ( pipehandle = CreateFile( "////.//Pipe//cao", GENERIC_READ | GENERIC_WRITE, 0, ( LPSECURITY_ATTRIBUTES )NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, ( HANDLE )NULL ) ) == INVALID_HANDLE_VALUE )
{
printf( "CreateFile failed with error %d/n", GetLastError() );
system( "pause" );
return 0;
}
ZeroMemory( &buff, sizeof( buff ) );
gets( buff );
if( WriteFile( pipehandle, buff, sizeof( buff ), &writesbytes, NULL ) == 0 )
{
printf( "WriteFile failed with error %d/n", GetLastError() );
CloseHandle( pipehandle );
system( "pause" );
return 0;
}
printf( "write %d bytes", writesbytes );
CloseHandle( pipehandle );
system( "pause" );
return 0;
}
命名管道不仅可用于无关联进程间、位于不同机器上的两进程间的通信,而且可用于多对一通信,可以建立服务器进程,允许同时通过多个客户访问命名管道。命名管道常常用于多线程服务器。
前面提到了几种方式,还要下面几种方式实现进程间的通信:
四、 共享内存
共享内存也可以实现进程间的通信。进程需要可以被其他进程浏览的内存块。希望访问这个内存块的其他进程请求对它的访问,或由创建它的进程授予访问内存块的权限。可以访问特定内存块的所有进程对它具有即时可见性。共享内存被映射到使用它的每个进程的地址空间。所以,它看起来像是另一个在进程内声明的变量。当一个进程写共享内存,所有的进程都立即知道写入的内容,而且可以访问。
进程间共享内存的关系与函数间全局变量的关系相似。程序中的所有函数都可以使用全局变量的值。同样,共享内存块可以被正在执行的所有进程访问。内存块可能共享一个逻辑地址,进程也可以共享某些物理地址。
共享内存块的创建必须由一个系统API调用来完成。在WIN32环境中,使用CreateFileMapping()、MapViewOfFile()以及MapViewOfFileEx() API能很好地完成。
共享内存分配位于WIN32系统中2~3GB地址范围内。一旦调用MapViewOfFile()和MapViewOfFileEx(),共享文件映射对象的所有进程都可以立即访问此内存块,而且在需要时,可以读写此内存块。
五、动态数据交换
动态数据交换( dynamic data exchange ) 是当今可用的进程间通信最强大和完善的形式之一。动态数据交换使用消息传递、共享内存、事务协议、客户/服务器范畴、同步规则以及会话协议来让数据和控制信息在进程间流动。动态数据交换对话( dynamic data exchange session, DDE )的基本模型是客户、服务器。服务器对来自客户的数据或动作作出反应。客户和服务器可以以多种关系来通信。
一个服务器可以与任意数量的客户通信。一个客户也可以与任意数量的服务器通信。单个DDE代理既可以是客户,也可以是服务器。也就是说,进程可以从一个正为另一个进程执行服务的DDE代理请求服务。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/masefee/archive/2009/05/13/4177196.aspx