C#程式間通訊的各種途徑及解析

小弟季義欽發表於2013-08-07
原文地址:C# 程式間通訊的各種途徑及解析作者:留心驛站
一、開篇
程式間通訊的主要目的是實現多臺計算機(也可以是同一臺)中應用程式之間的資料共享與資訊交換。在不同的計算機系統中,它們之間要通過網路之間的協議才能實現資料共享與資訊交換;在同一臺計算機系統中,它們之間只需一定的通道就能實現資料共享與資訊交換。在不同計算機系統和同一計算機系統的程式通訊中,既有很多相同之處,也有各自的特點。程式間通訊都要靠一定的通道(pipe)來實現,其中的通道多種多樣,各俱特色。
為了充分認識和掌握程式間通訊及其相應的實現技術,本文對各種通訊方法進行討論。包括每種方法的原理和實現等。

二、各種通訊途徑及實現
首先,程式間相互獨立,它的執行環境不為別的程式所改變。
I.[socket]
SOCKET程式設計是一種典型的會話程式設計方式,類似於老師家訪,敲門----有人開門----進去----交流----出門。它適用於client/server通訊方式,也適用於點對點通訊方式。
下面分別介紹服務端和客戶端的具體任務。
這裡介紹TCP的過程。
(1)服務端
服務端首先建立一個套接字,使用socket()呼叫;然後使用bind()將該套接字與本地IP和某一埠相關聯(該埠可以是空閒的也可以是非空閒的,具體的可以參閱筆者的《對埠截聽的實現》這篇文章)。使用listen()讓套接字等候進入連線,然後用accept()使套接字作好接受客戶連線的準備。當連線請求到來後,被阻塞服務程式的accept()函式生成一個新的套接字與客戶套接字建立連線,並向客戶返回接收訊號。用read()來讀入資料,用write()來向傳送程式寫回一些資料,如確認資訊或回顯資訊。
(2)客戶端
客戶程式也是先建立一個套接字,用socket()呼叫,然後客戶向服務程式發出連線請求,通過呼叫connect()可以建立一個端的連線,連線成功後用write()向服務端傳送資料,用read()讀取服務端返回的資料。
這種方式隱含了在建立client/server間通訊的非對稱性。client/server模型工作時要求有一套為客戶機和伺服器所共識的慣例來保證伺服器能夠被提供(或被接受),這一套慣例包括了一套協議,它必須在通訊的兩頭都被實現。根據不同的實際情況,協議可能是對稱的或是非對稱的。在對稱的協議中,每一方都有可能扮演主從角色;在非對稱協議中,每一放則是從機。例如終端模擬TELNET是對稱協議,HTTP是非對稱協議。無論具體的協議是對稱的還是非對稱的,當服務被提供時必然存在客戶程式和服務程式。
這種通訊方式適用於單個計算機系統,也適用於多個計算機系統。
[cpp:nogutter] view plaincopyprint?
WSADATA wsaData;
WORD version = MAKEWORD(2, 0);
int ret = WSAStartup(version, &wsaData);
if(ret != 0)
TRACE("Initilize Error!/n");
m_hSocket = socket(AF_INET, SOCK_STREAM,0);
m_addr.sin_family = AF_INET;
m_addr.sin_addr.S_un.S_addr = INADDR_ANY;
m_addr.sin_port = htons(m_nPort);
int ret = 0;
int error = 0;
ret = bind(m_hSocket, (LPSOCKADDR)&m_addr, sizeof(m_addr));
if(ret == SOCKET_ERROR){
TRACE("Bind Error: %d /n", (error = WSAGetLastError()));
return ;
}
ret = listen(m_hSocket, 2);
if(ret == SOCKET_ERROR){
TRACE("Listen Error: %d /n", (error = WSAGetLastError()));
return ;
}
SOCKET s = accept(m_hSocket, NULL, NULL);
if(s == SOCKET_ERROR){
TRACE("Accept Error: %d /n", (error = WSAGetLastError()));
return ;
}
char buff[256];
ret = recv(s, buff, 256, 0);
if(ret == 0 || ret == SOCKET_ERROR ){
TRACE("Recv data error: %d/n", WSAGetLastError());
return ;
}
char buf[]="hello";
send(s, buf, str.GetLength(), 0);

II.[file]
這種方法很原始,使用率也很少,而且佔用磁碟空間。利用檔案作為程式間通訊的通道,程式A向一檔案寫入資料,程式B通過讀取這個檔案中的資料從而與程式A進行通訊,同時這一過程也可以是反向的。這種方法使用的前提是執行程式的使用者需要具有對磁碟讀/寫的許可權。
|A.WriteFile------>fileA<------B.ReadFile(check every <time>)
|A.ReadFile(check every <time>)----->fileB<-----B.WriteFile
[c-sharp:nogutter] view plaincopyprint?
//A
int buff=0;
ofstream Table;
Table.open("c://temp.txt");
Table<<buff<<endl;
Table.close();
//B
int buff=1;
for(;;)
{
ifstream Table;
Table.open("c://temp.txt");
Table>>buff;
Table.close();
if(buff==0){
printf("Operation code equal 0");
//...
return;
}
sleep(10);
}

這種方法適用於單個計算機系統也適用於多個計算機系統。用這種方法在多個計算機系統間進行控制和資料交換,可以將這個檔案放與一計算機上,對方建立空連線,COPY此檔案到本地,進行讀取。當然也還有別的很多方法。
III.[signal]
這種方法僅用於程式控制,不能進行資料交換。例如在發生非法記憶體訪問、執行無效指令、某些按鍵(如CTRL+C,DEL等)等都會產生一個訊號,其他程式呼叫有關的系統呼叫或使用者自定義的處理過程進行處理。
資訊處理的系統呼叫是signal,它的原型是void ( *signal( int sig, void (__cdecl *func) ( int sig [, int subcode ] )) ) ( int sig );這個函式可以讓一個程式選擇很多方法中的一種去處理作業系統返回的中斷訊號。
根據類似的思路,我們也可以在多個程式間利用“訊號”進行程式間的通訊。
這種方法的關鍵是產生訊號。可以通過keybd_evnet()產生訊號,這個函式能夠起到模擬鍵盤輸出的作用,它的函式原型是VOID keybd_event(BYTE bVk,BYTE bScan,DWORD dwFlags,DWORD dwExtraInfo);vVk是一個虛擬鍵碼,bScan是掃描碼,對應相應的鍵。
這種方法適用於單個計算機系統。
IV.[pipe]
管道(PIPE)是一種簡單的程式間通訊(IPC)機制,分為無名管道和有名管道兩種。命名管道可以在同一臺機器的不同程式間以及不同機器上的不同程式間進行雙向通訊(使用UNC命名規範)。管道的最大好處在於:它可以象對普通檔案一樣進行操作,它的操作表示符HANDLE,也就是說,它可以使用ReadFile,WriteFile函式進行與底層實現無關的讀寫操作,使用者根本就不必瞭解網路間/程式間通訊的具體細節。
無名管道實際上是記憶體中的一個臨時儲存區,由系統安全控制,並且獨立於建立它的程式的記憶體區。管道對資料採用先進先出的方式管理,並嚴格按順序操作,管道不能被搜尋,管道中的資訊只能讀一次。無名管道只是在父子程式之間或者一個程式的兩個子程式之間進行通訊的。它是單向的。無名管道其實是通過用給了一個指定名字的有名管道來實現的。
有名管道的操作和無名管道類似。
管道的建立用createpipe()函式,用readfile()、writefile()對其進行操作。
[cpp:nogutter] view plaincopyprint?
SECURITY_ATTRIBUTES a;
HANDLE hReadPipe,hWritePipe;
CreatePipe(&hReadPipe,&hWritePipe,&a,0);
unsigned long lBytesRead;
char Buff[1024];
int ret;
while(1)
{
ret=PeekNamedPipe(hReadPipe,Buff,1024,&lBytesRead,0,0);
if(lBytesRead)
{
ret=ReadFile(hReadPipe,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
}
else
{
//lBytesRead=...;
if(lBytesRead<=0) break;
ret=WriteFile(hWritePipe,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
}
}

V.[MQ]
訊息通訊方式以訊息緩衝區為中間介質,通訊雙方的傳送和接收操作均以訊息為單位。在儲存器中,訊息緩衝區被組織成佇列,通常稱為訊息佇列(MQ)。訊息佇列是獨立於生成它的程式的一段儲存區,任何具有正確訪問許可權的程式都可以訪問訊息佇列,它非常適用於在程式間交換短訊息。
其中,每條訊息有型別編號分類。訊息佇列在建立後將一直存在,直到使用相關的呼叫刪除它為止。
VI.[共享儲存段(SM)]
共享儲存段通訊(shared memory)允許多個程式在外部通訊協議或同步/互斥機制的支援下使用同一個記憶體段作為中間介質進行通訊,它是一種很有效的資料通訊方式。
在進行通訊之前,先建立一個共享記憶體段,然後進行對映和分離操作。同時可以根據需要改變共享記憶體段的存取許可權以及其他的一些特徵。

☆對於下面的集中通訊方式,就要提到“中介軟體(Middleware)”。
中介軟體是一類軟體,它對應用程式隱含了實際網路和通訊協議的細節。高階程式設計介面幫助開發者在不同的環境中建立應用程式,而不需要對將使用的網路和通訊協議有更多的瞭解。

正常情況下,中介軟體在使用不同網路通訊協議的客戶機/伺服器環境。它可以對客戶機/伺服器應用隱藏協議,從而使開發人員集中精力於改進應用程式,而不是開發通訊介面。
中介軟體產品隱藏了前端應用程式和後端應用程式的區別。中介軟體層包括通用應用程式和流行應用程式的應用程式程式設計介面(API)之間的翻譯功能。例如,Microsoft的開放式資料庫連線(ODBC)標準提供後端資料庫系統操作的通用功能。前端應用程式寫入ODBC,並利用它的功能。ODBC 隱藏了不同廠商的SQL實現的區別。Microsoft以一組Microsoft Windows驅動程式的形式提供ODBC,以提供對Microsoft Access、Microsoft Excel、Microsoft SQL Server、FoxPro、Btrieve,dBASE,Borland Paradox、IBM DB2、DECRdb和Oracle等格式產生的資料的訪問。ODBC是為了使Windows成為客戶訪問後端資料庫的標準而設計的。
□使用者需要訪問許多不同的後端伺服器上的服務。
□後端伺服器可以使用不同的作業系統,並需要不同的通訊協議。
□後端資料庫伺服器有不相容的SQL命令集,它使使用者和程式設計師很難從一個系統轉到另一個系統。
□限制使用者只能使用某種特定的訪問後端服務的應用程式現在已經不現實。使用者需要從不同的應用程式來訪問服務。
□新的模型是為了使使用者在多廠商環境使用多種協議在任何前端來訪問任何後端服務。
在多協議、多廠商環境,通常一個程式設計師需要編寫應用程式,來與每個協議和支援系統進行工作。使用中介軟體,程式設計師只需簡單地編寫到中介軟體的介面,而由中介軟體處理所有多協議、多廠商問題。有三種型別的中介軟體:遠端過程呼叫(RPC)、會晤(conversations)和訊息傳遞系統。它們都能很好地隱藏通訊過程,以及與它們進行操作的系統差異。
VII.[RPC]
Remote Procedure Call(PRC)
一個遠端過程呼叫是在網路上一個計算機系統對另一計算機系統的請求。RPC保持了中介軟體在不同網路平臺和通訊協議上工作的性質。基本上,一個PRC就是一臺計算機向另一臺計算機發出的直接請求。它是一種請求/回答的過程,這時請求放等待一個回答,這意味著PRC通常是在面向連線介面上發生的一種實時呼叫。
RPC機制強制通訊的兩個應用程式必須同時處於執行狀態。做遠端呼叫時,兩者必須先建立連線,而且通訊鏈路質量對它的效果影響很大。
它的工作方式是:當一個應用程式A需要與遠端的另一個應用程式B交換資訊或要求B提供協助時,A將在本地產生一個請求,通過通訊鏈路,同志B接收資訊或提供相應的服務,B完成相關處理後將確認資訊或結果返回給A。
PRC的優點是應用程式採用呼叫/返回方式通訊,擁有很高的潛在效率,但需要應用程式間的緊密耦合,通訊線路必須在通訊期間一直保持良好的狀態,而且必須進行大量的底層通訊的程式設計工作。
VIII.[conversations]
會晤(conversations)是邏輯連線的兩個或多個系統之間的連續會話,同RPC不同,在分散式環境,會晤可能會重疊執行。出於這個原因,對需要在多個地方必須完全同步地完成,修改分散式資料庫的工作,會晤是非常必要和有益的。IBM的高階程式對程式通訊(APPC)實現了會晤。用於實現會晤的OSI標準也已出現。Covia Technologies(Rosemont,Illinois)的通訊整合器(CI,Communication Integrator)是具有會晤的中介軟體的另一個例子、它可以在大型計算機、中型機和桌上型電腦上執行。
IX.[MQSeries訊息佇列]
為了簡化應用程式間的通訊,使得通訊具有較高的可靠性,又保證實現的簡單性,中介軟體技術是我們的首選,IBM公司的MQSeries就是基於這種技術的產品。MQSeries是一種獨立的通訊軟體,應用程式只需將任務提交給該軟體,就可以由該軟體自動去完成資訊的傳遞工作。
例如:
應用程式A
|
|
放入|訊息
|
|

MQSeries介面----------------
∧ |
| |
| |喚醒
讀取|訊息(於Q1中) |
| |
| |
應用程式B(無論本地還是遠端)<-----
應用程式間的訊息傳遞是通過佇列來實現的,是間接的,所以即使B沒能正常執行時,A仍然能正常執行,而且,訊息還可以喚醒B。
可見,MQSeries的優點是可以確保資訊是永久的、可恢復的,確保資訊傳遞的安全性,確保資訊的成功傳送且只傳送一次,同時,還可以使各應用程式可以獨立的正常執行。
無論是共有的還是私有的佇列,都是用MQOpenQueue()開啟,MQOpenQueue函式用來開啟一個用來傳送訊息或是讀取訊息的佇列。它的原型是HRESULT APIENTRY MQOpenQueue( LPCWSTR lpwcsFormatName, DWORD dwAccess, DWORD dwShareMode, LPQUEUEHANDLE phQueue );其中,lpwcsFormatName是使用者指定的指向佇列的format name字串,這個format name可以是public,private,或者是direct format。dwAccess是由使用者指定的應用程式使用佇列的方法,可以是peek,send,receive。當佇列開啟以後這個設定就不能被更改。dwAccess有三種模式:MQ_PEEK_ACCESS/MQ_SEND_ACCESS/MQ_RECEIVE_ACCESS。dwShareMode是由使用者指定的佇列的共享模式,分為:MQ_DENY_NONE和MQ_DENY_RECEIVE_SHARE,MQ_DENY_NONE是預設值,允許任何人使用,如果dwAccess設定成了MQ_SEND_ACCESS,那麼在此就必須設定成MQ_DENY_NONE;MQ_DENY_RECEIVE_SHARE限制了從此佇列讀取訊息的程式。如果這個佇列已經被別的程式開啟用來接收訊息,那麼將返回MQ_ERROR_SHARING_VIOLATION,MQ_DENY_RECEIVE_SHARE僅適用於當dwAccess設定成MQ_RECEIVE_ACCESS或MQ_PEEK_ACCESS的時候。phQueue是輸出的已開啟的佇列的控制程式碼。
然後可以用MQSendMessage()/MQReceiveMessage()等函式進行具體操作,在此不多熬述。
三、結束語
技術是一點一滴積累起來的,對任何知識點的系統性掌握很有必要也很有用。
各種通訊的實現技術都具有自己的特點和使用範圍,管道、訊息佇列、共享記憶體等技術最適用於同一計算機系統內部的程式間通訊,以保證高效率。而遠端過程呼叫、Socket會話程式設計、MQSeries則最適用於遠端的應用程式之間通訊,可以簡化通訊的程式設計,當然也保證通訊的可靠性。尤其是MQSeries,它是一個比較完善的中介軟體產品,為許多的資訊系統所選用。
對各種通訊技術的合理搭配使用,常常能起到事半功倍的作用。

相關文章