程序員必須讓擁有依賴關系的進程集協調,這樣才能達到進程的共同目標。可以使用兩種技術來達到協調。第一種技術在具有通信依賴關系的兩個進程間傳遞信息。這種技術稱做進程間通信( 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( "http:////.//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( "http:////.//Pipe//cao", NMPWAIT_WAIT_FOREVER ) == 0 )
{
printf( "WaitNamedPipe failed with error %d/n", GetLastError() );
system( "pause" );
return 0;
}
if( ( pipehandle = CreateFile( "http:////.//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;
}
命名管道不僅可用于無關聯進程間、位于不同機器上的兩進程間的通信,而且可用于多對一通信,可以建立服務器進程,允許同時通過多個客戶訪問命名管道。命名管道常常用于多線程服務器。累死了。。(待續)
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
