1. 程式人生 > >幾種常用的程序間通訊的方式,通訊特點和通訊方式的優缺點

幾種常用的程序間通訊的方式,通訊特點和通訊方式的優缺點

http://blog.csdn.net/liuzhanchen1987/article/details/7452910

程式設計師必須讓擁有依賴關係的程序集協調,這樣才能達到程序的共同目標。可以使用兩種技術來達到協調。第一種技術在具有通訊依賴關係的兩個程序間傳遞資訊。這種技術稱做程序間通訊(interprocess communication)。第二種技術是同步,當程序間相互具有合作依賴時使用。這兩種型別的依賴關係可以同時存在。

一般而言,程序有單獨的地址空間。我們可以瞭解下可執行程式被裝載到記憶體後建立的一系列對映等理解這一點。如此以來意味著如果我們有兩個程序(程序A和程序B),那麼,在程序A中宣告的資料對於程序B是不可用的。而且,程序B看不到程序A中發生的事件,反之亦然。如果程序A和B一起工作來完成某個任務,必須有一個在兩個程序間通訊資訊和時間的方法。我們這裡可以去看看基本的程序元件。注意程序有一個文字、資料以及堆疊片斷。程序可能也有從自由儲存空間中分配的其它記憶體。程序所佔有的資料一般位於資料片斷、堆疊片斷或程序的動態分配記憶體中。資料對於其它程序來說是受保護的。為了讓一個程序訪問另外一個程序的資料,必須最終使用作業系統呼叫。

與之類似,為了讓一個程序知道另一個程序中文字片斷中發生的事件,必須在程序間建立一種通訊方式。這也需要來自作業系統API的幫助。當程序將資料傳送到另一程序時,稱做IPC(interprocess communication,程序間通訊)。下面先列舉幾種不同型別的程序間通訊方式:

程序間通訊                                 描述

環境變數/檔案描述符            子程序接受父程序環境資料的拷貝以及所有檔案描述符。父程序可以在它的資料片斷或環境中設定一定的變數,同時子程序接收這些值。父程序可以開啟檔案,同時推進讀/寫指標的位置,而且子程序使用相同的偏移訪問該檔案。

命令列引數                      在呼叫exec或派生函式期間,命令列引數可以傳遞給子程序。

管道                            用於相關和無關程序間的通訊,而且形成兩個程序間的一個通訊通道,通常使用檔案讀寫程式訪問。

共享記憶體                        兩個程序之外的記憶體塊,兩個程序均可以訪問它。

DDE(動態資料交換,Dynamic data exchange)     使用客戶機/伺服器模型(C/S),伺服器對客戶的資料     或動作請求作出反應。

一、環境變數、檔案描述符:

當建立一個子程序時,它接受了父程序許多資源的拷貝。子程序接受了父程序的文字、堆疊

以及資料片斷的拷貝。子程序也接受了父程序的環境資料以及所有檔案描述符的拷貝。子進

程從父程序繼承資源的過程創造了程序間通訊的一個機會。父程序可以在它的資料片斷或環

境中設定一定的變數,子程序於是接受這些值。同樣,父程序也可以開啟一個檔案,推進到

檔案內的期望位置,子程序接著就可以在父程序離開讀/寫指標的準確位置訪問該檔案。

這類通訊的缺陷在於它是單向的、一次性的通訊。也就是說,除了檔案描述外,如果子程序

繼承了任何其它資料,也僅僅是父程序拷貝的所有資料。 一旦建立了子程序,由子程序對

這些變數的任何改變都不會反映到父程序的資料中。同樣,建立子程序後,對父程序資料的

任何改變也不會反映到子程序中。所以,這種型別的程序間通訊更像指揮棒傳遞。一旦父進

程傳遞了某些資源的拷貝,子程序對它的使用就是獨立的,必須使用原始傳遞資源。

二、命令列引數:

通過命令列引數(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代理請求服務。