C# IPC管道通訊模塊

  平時都是在網上找水喝,今天我也當回打井人。上週在搞IPC通訊的項目,一個星期下來,覺得還是有些收穫的,在此總結一下開發中我遇到的問題,希望能爲其他人提供一些經驗。

  首先說說IPC通訊有什麼特點。第一,就是IPC只能用於同一臺PC的多個進程間的通訊,這個是一個前提,如果想在多臺電腦上進行通訊,那還是老老實實的用socket或管道來做。第二,簡單。相比於socket,IPC通訊的準備工作很少。server端建立一個有名字的IPC通道,註冊通道實例,再給這個通道指定一個共享數據類型和對應的實例,就算OK啦,夠簡單吧。client端也是一樣的道理,首先建立一個管道,註冊,引用通道的共享數據實例,現在就可以通訊了。第三,由於僅限於PC內部進程間的通訊,所以不需要綁定端口,並且通訊速度和穩定性都很好。速度和穩定性都很好理解,一臺機器,都是人民內部矛盾,所以處理起來基本沒問題。不需要綁定到硬件資源就可以避免硬件資源的衝突,想象一下,如果用socket的話就要綁定一個監聽端口,如果是多個實例在一臺機器上運行,這個端口該怎麼分配,就是一個比較麻煩問題啦。

  再說說IPC的通訊載體,共享數據實例。我們可以想象成是sever和client共用的一個盒子,這個盒子的格局由數據類型確定,需要傳遞什麼數據,都可以通過聲明共享數據類型來確定。這個裏面有個事情要注意,就是共享數據的實例是在server或client一端建立的,所以用來傳數據是可以的,但是不能用來傳遞數據地址。因爲這個共享數據只能在一個進程中被創建,在另個進程中引用,如果進程A把自己的地址傳給進程B,進程B就會非常鬱悶,因爲進程B沒有這個地址。這就好像一個超市裏有1到100號儲物箱,但是一個人打開儲物箱發現讓他到200號箱裏去取東西是一樣的。

  基本的東西說完了,現在看看具體的實現,限於篇幅關係,只貼基本的東西,其他的內容可以看附件中的代碼:

  共享數據模塊:

  public class DataOperator : MarshalByRefObject

 {

       // Data

        private string InformationStr = string.Empty;

        // event

        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端通信並區分數據來源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章