平時都是在網上找水喝,今天我也當回打井人。上週在搞IPC通信的項目,一個星期下來,以爲仍是有些收穫的,在此總結一下開發中我遇到的問題,但願能爲其餘人提供一些經驗。socket
首先說說IPC通信有什麼特色。第一,就是IPC只能用於同一臺PC的多個進程間的通信,這個是一個前提,若是想在多臺電腦上進行通信,那仍是老老實實的用socket或管道來作。第二,簡單。相比於socket,IPC通信的準備工做不多。server端創建一個有名字的IPC通道,註冊通道實例,再給這個通道指定一個共享數據類型和對應的實例,就算OK啦,夠簡單吧。client端也是同樣的道理,首先創建一個管道,註冊,引用通道的共享數據實例,如今就能夠通信了。第三,因爲僅限於PC內部進程間的通信,因此不須要綁定端口,而且通信速度和穩定性都很好。速度和穩定性都很好理解,一臺機器,都是人民內部矛盾,因此處理起來基本沒問題。不須要綁定到硬件資源就能夠避免硬件資源的衝突,想象一下,若是用socket的話就要綁定一個監聽端口,若是是多個實例在一臺機器上運行,這個端口該怎麼分配,就是一個比較麻煩問題啦。ide
再說說IPC的通信載體,共享數據實例。咱們能夠想象成是sever和client共用的一個盒子,這個盒子的格局由數據類型肯定,須要傳遞什麼數據,均可以經過聲明共享數據類型來肯定。這個裏面有個事情要注意,就是共享數據的實例是在server或client一端創建的,因此用來傳數據是能夠的,可是不能用來傳遞數據地址。由於這個共享數據只能在一個進程中被建立,在另個進程中引用,若是進程A把本身的地址傳給進程B,進程B就會很是鬱悶,由於進程B沒有這個地址。這就好像一個超市裏有1到100號儲物箱,可是一我的打開儲物箱發現讓他到200號箱裏去取東西是同樣的。函數
基本的東西說完了,如今看看具體的實現,限於篇幅關係,只貼基本的東西,其餘的內容能夠看附件中的代碼:測試
共享數據模塊:this
public class DataOperator : MarshalByRefObjectorm
{server
// Data進程
private string InformationStr = string.Empty;事件
// eventip
public event ClientHasConnectedHandler OnConnectedEvent = null;
public event WriteOptionFinishedHandler OnServerWriteEvent = null;
public event WriteOptionFinishedHandler OnClientWriteEvent = null;
public event ClientHasConnectedHandler OnDisconnectedEvent = null;
// client call this function to implement connected event
public void SendConnectEventByClient(string ipcName){
try{
if (OnConnectedEvent != null)
OnConnectedEvent(ipcName);
}
catch(Exception ex)
{
IPCLog.WriteLog(string.Format("error msg is {0}, DTid = {1}", ex.Message, id));
}
}
#region Buffer Operator about
public bool Writeflag{
get{
if (InformationStr == string.Empty) return true;
else return false;
}
}
public string Write{
set {
InformationStr = string.Empty;
InformationStr = value;}
}
public string Read{
get{
string tmpRes = InformationStr;
InformationStr = string.Empty;
return tmpRes;}
}
#endregion
}
共享數據類中主要包括兩個部分,一是用來傳遞數據的InformationStr,一是用來傳遞控制狀態的事件。前面說過要想用IPC傳遞數據是很是容易的,可是咱們不能老是循環去判斷數據段裏是否是有數據,因此須要用事件進行控制。以DataOperator 類中的OnConnectedEvent爲例,在server端綁定該事件,在client端調用SendConnectEventByClient方法,server端就知道,有一個client創建了鏈接。
再看server端的處理:
BinaryServerFormatterSinkProvider serverProvider =
new BinaryServerFormatterSinkProvider();
serverProvider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["name"] = m_ipcName;
props["portName"] = m_ipcName;
props["typeFilterLevel"] = TypeFilterLevel.Full;
// create a server channel
svrChl = new IpcServerChannel(props, serverProvider, null);
ChannelServices.RegisterChannel(svrChl);
m_optDt = new DataOperator(m_ipcName);
objRef = RemotingServices.Marshal(m_optDt, m_ipcName + "_DataOperator");
m_optDt.OnConnectedEvent += new ClientHasConnectedHandler(ClientHasConnected);
這裏解釋一下,serverProvider實例用來設定序列化格式,因爲咱們須要在共享數據中增長事件類型,因此須要重指定序列化等級。props用來設定通道信息,若是沒有特殊的要求能夠直接使用IpcServerChannel的構造函數來設定通道信息。RemotingServices.Marshal方法用來把m_optDt創建一個引用名,這樣,其餘的進程就能夠經過這個名字引用到server創建的這個實例。
最後client端:
// client channel
IDictionary props = new Hashtable();
props["name"] = m_ipcName;
props["typeFilterLevel"] = TypeFilterLevel.Full;
props["portName"] = m_ipcName;
// create a new client channel
cltChl = new IpcClientChannel(props, null);
ChannelServices.RegisterChannel(cltChl);
// Get DataOperator instance of server
m_optDt = (DataOperator)Activator.GetObject(typeof(DataOperator), chlName);
m_optDt.SendConnectEventByClient(this.IPCName);
client端相對比較簡單,不作過多解釋了。如今就能夠在經過調用DataOperator的Write和Read屬性發信和收信了。固然如今功能只能算是完成了一半,由於如今只能由server監控client的動做,若是須要client也響應server的動做,就須要在作一條通道,只要server端和client端方向調換一下就OK了。
最後再說一下通道的釋放。當通道的歷史使命完成後,須要把通道釋放,固然直接退出程序是能夠的。代碼以下:
RemotingServices.Disconnect(m_optDt);
// unregister all channels
ChannelServices.UnregisterChannel(svrChl);
代碼很簡單,就兩句話,須要注意的是第一句。RemotingServices.Disconnect用來釋放咱們綁定的共享數據引用。當咱們註銷通道以後,共享數據的引用是不會自動消失的,須要手動釋放,不然下次再創建鏈接的時候,RemotingServices.Marshal會絕不客氣地告訴你,你指定的名字已經有人用啦。
附件裏提供了完整的代碼和測試測序,可以實現server和client的雙向通訊及狀態控制,IPC通訊模塊作了封裝,server端能夠創建多個通道與不通的client端通訊並區分數據來源。