Microsoft .Net Remoting系列專題之一:.Net Remoting基礎篇

Microsoft .Net Remoting系列專題之一

一、Remoting基礎

什麼是Remoting,簡而言之,我們可以將其看作是一種分佈式處理方式。從微軟的產品角度來看,可以說Remoting就是DCOM的一種升級,它改善了很多功能,並極好的融合到.Net平臺下。Microsoft® .NET Remoting 提供了一種允許對象通過應用程序域與另一對象進行交互的框架。這也正是我們使用Remoting的原因。爲什麼呢?在Windows操作系統中,是將應用程序分離爲單獨的進程。這個進程形成了應用程序代碼和數據周圍的一道邊界。如果不採用進程間通信(RPC)機制,則在一個進程中執行的代碼就不能訪問另一進程。這是一種操作系統對應用程序的保護機制。然而在某些情況下,我們需要跨過應用程序域,與另外的應用程序域進行通信,即穿越邊界。

在Remoting中是通過通道(channel)來實現兩個應用程序域之間對象的通信的。如圖所示:

首先,客戶端通過Remoting,訪問通道以獲得服務端對象,再通過代理解析爲客戶端對象。這就提供一種可能性,即以服務的方式來發布服務器對象。遠程對象代碼可以運行在服務器上(如服務器激活的對象和客戶端激活的對象),然後客戶端再通過Remoting連接服務器,獲得該服務對象並通過序列化在客戶端運行。

在Remoting中,對於要傳遞的對象,設計者除了需要了解通道的類型和端口號之外,無需再瞭解數據包的格式。但必須注意的是,客戶端在獲取服務器端對象時,並不是獲得實際的服務端對象,而是獲得它的引用。這既保證了客戶端和服務器端有關對象的鬆散耦合,同時也優化了通信的性能。

1、Remoting的兩種通道

Remoting的通道主要有兩種:Tcp和Http。在.Net中,System.Runtime.Remoting.Channel中定義了IChannel接口。IChannel接口包括了TcpChannel通道類型和Http通道類型。它們分別對應Remoting通道的這兩種類型。

TcpChannel類型放在名字空間System.Runtime.Remoting.Channel.Tcp中。Tcp通道提供了基於Socket的傳輸工具,使用Tcp協議來跨越Remoting邊界傳輸序列化的消息流。TcpChannel類型默認使用二進制格式序列化消息對象,因此它具有更高的傳輸性能。HttpChannel類型放在名字空間System.Runtime.Remoting.Channel.Http中。它提供了一種使用Http協議,使其能在Internet上穿越防火牆傳輸序列化消息流。默認情況下,HttpChannel類型使用Soap格式序列化消息對象,因此它具有更好的互操作性。通常在局域網內,我們更多地使用TcpChannel;如果要穿越防火牆,則使用HttpChannel。

2、遠程對象的激活方式

在訪問遠程類型的一個對象實例之前,必須通過一個名爲Activation的進程創建它並進行初始化。這種客戶端通過通道來創建遠程對象,稱爲對象的激活。在Remoting中,遠程對象的激活分爲兩大類:服務器端激活和客戶端激活。

(1) 服務器端激活,又叫做WellKnow方式,很多又翻譯爲知名對象。爲什麼稱爲知名對象激活模式呢?是因爲服務器應用程序在激活對象實例之前會在一個衆所周知的統一資源標識符(URI)上來發布這個類型。然後該服務器進程會爲此類型配置一個WellKnown對象,並根據指定的端口或地址來發布對象。.Net Remoting把服務器端激活又分爲SingleTon模式和SingleCall模式兩種。

SingleTon模式:此爲有狀態模式。如果設置爲SingleTon激活方式,則Remoting將爲所有客戶端建立同一個對象實例。當對象處於活動狀態時,SingleTon實例會處理所有後來的客戶端訪問請求,而不管它們是同一個客戶端,還是其他客戶端。SingleTon實例將在方法調用中一直維持其狀態。舉例來說,如果一個遠程對象有一個累加方法(i=0;++i),被多個客戶端(例如兩個)調用。如果設置爲SingleTon方式,則第一個客戶獲得值爲1,第二個客戶獲得值爲2,因爲他們獲得的對象實例是相同的。如果熟悉Asp.Net的狀態管理,我們可以認爲它是一種Application狀態。

SingleCall模式:SingleCall是一種無狀態模式。一旦設置爲SingleCall模式,則當客戶端調用遠程對象的方法時,Remoting會爲每一個客戶端建立一個遠程對象實例,至於對象實例的銷燬則是由GC自動管理的。同上一個例子而言,則訪問遠程對象的兩個客戶獲得的都是1。我們仍然可以借鑑Asp.Net的狀態管理,認爲它是一種Session狀態。

(2) 客戶端激活。與WellKnown模式不同,Remoting在激活每個對象實例的時候,會給每個客戶端激活的類型指派一個URI。客戶端激活模式一旦獲得客戶端的請求,將爲每一個客戶端都建立一個實例引用。SingleCall模式和客戶端激活模式是有區別的:首先,對象實例創建的時間不一樣。客戶端激活方式是客戶一旦發出調用的請求,就實例化;而SingleCall則是要等到調用對象方法時再創建。其次,SingleCall模式激活的對象是無狀態的,對象生命期的管理是由GC管理的,而客戶端激活的對象則有狀態,其生命週期可自定義。其三,兩種激活模式在服務器端和客戶端實現的方法不一樣。尤其是在客戶端,SingleCall模式是由GetObject()來激活,它調用對象默認的構造函數。而客戶端激活模式,則通過CreateInstance()來激活,它可以傳遞參數,所以可以調用自定義的構造函數來創建實例。

二、遠程對象的定義

前面講到,客戶端在獲取服務器端對象時,並不是獲得實際的服務端對象,而是獲得它的引用。因此在Remoting中,對於遠程對象有一些必須的定義規範要遵循。

由於Remoting傳遞的對象是以引用的方式,因此所傳遞的遠程對象類必須繼承MarshalByRefObject。MSDN對MarshalByRefObject的說明是:MarshalByRefObject 是那些通過使用代理交換消息來跨越應用程序域邊界進行通信的對象的基類。不是從 MarshalByRefObject 繼承的對象會以隱式方式按值封送。當遠程應用程序引用一個按值封送的對象時,將跨越遠程處理邊界傳遞該對象的副本。因爲您希望使用代理方法而不是副本方法進行通信,因此需要繼承MarshallByRefObject。

以下是一個遠程對象類的定義:
public class ServerObject:MarshalByRefObject
{
        public Person GetPersonInfo(string name,string sex,int age)
        {
            Person person = new Person();
            person.Name = name;
            person.Sex = sex;
            person.Age = age;
            return person;
        }
}

這個類只實現了最簡單的方法,就是設置一個人的基本信息,並返回一個Person類對象。注意這裏返回的Person類。由於這裏所傳遞的Person則是以傳值的方式來完成的,而Remoting要求必須是引用的對象,所以必須將Person類序列化。

因此,在Remoting中的遠程對象中,如果還要調用或傳遞某個對象,例如類,或者結構,則該類或結構則必須實現串行化Attribute[SerializableAttribute]:
[Serializable]
 public class Person
 {
        public Person()
        {
           
        }

        private string name;
        private string sex;
        private int age;

        public string Name
        {
            get    {return name;}
            set    {name = value;}
        }

        public string Sex
        {
            get {return sex;}
            set {sex = value;}
        }

        public int Age
        {
            get {return age;}
            set {age = value;}
        }
  }
將該遠程對象以類庫的方式編譯成Dll。這個Dll將分別放在服務器端和客戶端,以添加引用。

在Remoting中能夠傳遞的遠程對象可以是各種類型,包括複雜的DataSet對象,只要它能夠被序列化。遠程對象也可以包含事件,但服務器端對於事件的處理比較特殊,我將在本系列之三中介紹。

三、服務器端

根據第一部分所述,根據激活模式的不同,通道類型的不同服務器端的實現方式也有所不同。大體上說,服務器端應分爲三步:

1、註冊通道

要跨越應用程序域進行通信,必須實現通道。如前所述,Remoting提供了IChannel接口,分別包含TcpChannel和HttpChannel兩種類型的通道。這兩種類型除了性能和序列化數據的格式不同外,實現的方式完全一致,因此下面我們就以TcpChannel爲例。

註冊TcpChannel,首先要在項目中添加引用“System.Runtime.Remoting”,然後using名字空間:System.Runtime.Remoting.Channel.Tcp。代碼如下:
            TcpChannel channel = new TcpChannel(8080);
            ChannelServices.RegisterChannel(channel);

在實例化通道對象時,將端口號作爲參數傳遞。然後再調用靜態方法RegisterChannel()來註冊該通道對象即可。

2、註冊遠程對象

註冊了通道後,要能激活遠程對象,必須在通道中註冊該對象。根據激活模式的不同,註冊對象的方法也不同。

(1) SingleTon模式

對於WellKnown對象,可以通過靜態方法RemotingConfiguration.RegisterWellKnownServiceType()來實現:RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServerRemoteObject.ServerObject),
                "ServiceMessage",WellKnownObjectMode.SingleTon);

(2)SingleCall模式

註冊對象的方法基本上和SingleTon模式相同,只需要將枚舉參數WellKnownObjectMode改爲SingleCall就可以了。RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServerRemoteObject.ServerObject),
                "ServiceMessage",WellKnownObjectMode.SingleCall);

(3)客戶端激活模式

對於客戶端激活模式,使用的方法又有不同,但區別不大,看了代碼就一目瞭然。
RemotingConfiguration.ApplicationName = "ServiceMessage";
RemotingConfiguration.RegisterActivatedServiceType(
                typeof(ServerRemoteObject.ServerObject));

爲什麼要在註冊對象方法前設置ApplicationName屬性呢?其實這個屬性就是該對象的URI。對於WellKnown模式,URI是放在RegisterWellKnownServiceType()方法的參數中,當然也可以拿出來專門對ApplicationName屬性賦值。而RegisterActivatedServiceType()方法的重載中,沒有ApplicationName的參數,所以必須分開。

3、註銷通道

如果要關閉Remoting的服務,則需要註銷通道,也可以關閉對通道的監聽。在Remoting中當我們註冊通道的時候,就自動開啓了通道的監聽。而如果關閉了對通道的監聽,則該通道就無法接受客戶端的請求,但通道仍然存在,如果你想再一次註冊該通道,會拋出異常。

           //獲得當前已註冊的通道;
            IChannel[] channels = ChannelServices.RegisteredChannels;

            //關閉指定名爲MyTcp的通道;
            foreach (IChannel eachChannel in channels)
            {
                if (eachChannel.ChannelName == "MyTcp")
                {
                    TcpChannel tcpChannel = (TcpChannel)eachChannel;

                    //關閉監聽;
                    tcpChannel.StopListening(null);

                    //註銷通道;
                    ChannelServices.UnregisterChannel(tcpChannel);
                }
            }
代碼中,RegisterdChannel屬性獲得的是當前已註冊的通道。在Remoting中,是允許同時註冊多個通道的,這一點會在後面說明。

四、客戶端

客戶端主要做兩件事,一是註冊通道。這一點從圖一就可以看出,Remoting中服務器端和客戶端都必須通過通道來傳遞消息,以獲得遠程對象。第二步則是獲得該遠程對象。

1、註冊通道:
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);

注意在客戶端實例化通道時,是調用的默認構造函數,即沒有傳遞端口號。事實上,這個端口號是缺一不可的,只不過它的指定被放在後面作爲了Uri的一部分。

2、獲得遠程對象。

與服務器端相同,不同的激活模式決定了客戶端的實現方式也將不同。不過這個區別僅僅是WellKnown激活模式和客戶端激活模式之間的區別,而對於SingleTon和SingleCall模式,客戶端的實現完全相同。

(1) WellKnown激活模式

要獲得服務器端的知名遠程對象,可通過Activator進程的GetObject()方法來獲得:
ServerRemoteObject.ServerObject serverObj = (ServerRemoteObject.ServerObject)Activator.GetObject(
              typeof(ServerRemoteObject.ServerObject), "tcp://localhost:8080/ServiceMessage");

首先以WellKnown模式激活,客戶端獲得對象的方法是使用GetObject()。其中參數第一個是遠程對象的類型。第二個參數就是服務器端的uri。如果是http通道,自然是用http://localhost:8080/ServiceMessage了。因爲我是用本地機,所以這裏是localhost,你可以用具體的服務器IP地址來代替它。端口必須和服務器端的端口一致。後面則是服務器定義的遠程對象服務名,即ApplicationName屬性的內容。

(2) 客戶端激活模式

如前所述,WellKnown模式在客戶端創建對象時,只能調用默認的構造函數,上面的代碼就說明了這一點,因爲GetObject()方法不能傳遞構造函數的參數。而客戶端激活模式則可以通過自定義的構造函數來創建遠程對象。

客戶端激活模式有兩種方法:
1) 調用RemotingConfiguration的靜態方法RegisterActivatedClientType()。這個方法返回值爲Void,它只是將遠程對象註冊在客戶端而已。具體的實例化還需要調用對象類的構造函數。
 RemotingConfiguration.RegisterActivatedClientType(               
                typeof(ServerRemoteObject.ServerObject),
                "tcp://localhost:8080/ServiceMessage");
 ServerRemoteObject.ServerObject serverObj = new ServerRemoteObject.ServerObject();

2) 調用進程Activator的CreateInstance()方法。這個方法將創建方法參數指定類型的類對象。它與前面的GetObject()不同的是,它要在客戶端調用構造函數,而GetObject()只是獲得對象,而創建實例是在服務器端完成的。CreateInstance()方法有很多個重載,我着重說一下其中常用的兩個。
a、 public static object CreateInstance(Type type, object[] args, object[] activationAttributes);

參數說明:
type:要創建的對象的類型。
args :與要調用構造函數的參數數量、順序和類型匹配的參數數組。如果 args 爲空數組或空引用(Visual Basic 中爲 Nothing),則調用不帶任何參數的構造函數(默認構造函數)。
activationAttributes :包含一個或多個可以參與激活的屬性的數組。

這裏的參數args是一個object[]數組類型。它可以傳遞要創建對象的構造函數中的參數。從這裏其實可以得到一個結論:WellKnown激活模式所傳遞的遠程對象類,只能使用默認的構造函數;而Activated模式則可以用戶自定義構造函數。activationAttributes參數在這個方法中通常用來傳遞服務器的url。
假設我們的遠程對象類ServerObject有個構造函數:
            ServerObject(string pName,string pSex,int pAge)
            {
                name = pName;
                sex = pSex;
                age = pAge;
            }

那麼實現的代碼是:
            object[] attrs = {new UrlAttribute("tcp://localhost:8080/ServiceMessage")};
            object[] objs = new object[3];
            objs[0] = "wayfarer";
            objs[1] = "male";
            objs[2] = 28;
            ServerRemoteObject.ServerObject = Activator.CreateInstance(
                typeof(ServerRemoteObject.ServerObject),objs,attrs);
可以看到,objs[]數組傳遞的就是構造函數的參數。

b、public static ObjectHandle CreateInstance(string assemblyName, string typeName, object[] activationAttribute);

參數說明:
assemblyName :將在其中查找名爲 typeName 的類型的程序集的名稱。如果 assemblyName 爲空引用(Visual Basic 中爲 Nothing),則搜索正在執行的程序集。
typeName:首選類型的名稱。
activationAttributes :包含一個或多個可以參與激活的屬性的數組。

參數說明一目瞭然。注意這個方法返回值爲ObjectHandle類型,因此代碼與前不同:
            object[] attrs = {new UrlAttribute("tcp://localhost:8080/EchoMessage")};           
            ObjectHandle handle = Activator.CreateInstance("ServerRemoteObject",
                                   "ServerRemoteObject.ServerObject",attrs);
            ServerRemoteObject.ServerObject obj = (ServerRemoteObject.ServerObject)handle.Unwrap();

這個方法實際上是調用的默認構造函數。ObjectHandle.Unwrap()方法是返回被包裝的對象。

說明:要使用UrlAttribute,還需要在命名空間中添加:using System.Runtime.Remoting.Activation;

五、Remoting基礎的補充

通過上面的描述,基本上已經完成了一個最簡單的Remoting程序。這是一個標準的創建Remoting程序的方法,但在實際開發過程中,我們遇到的情況也許千奇百怪,如果只掌握一種所謂的“標準”,就妄想可以“一招鮮、吃遍天”,是不可能的。

1、註冊多個通道

在Remoting中,允許同時創建多個通道,即根據不同的端口創建不同的通道。但是,Remoting要求通道的名字必須不同,因爲它要用來作爲通道的唯一標識符。雖然IChannel有ChannelName屬性,但這個屬性是隻讀的。因此前面所述的創建通道的方法無法實現同時註冊多個通道的要求。

這個時候,我們必須用到System.Collection中的IDictionary接口:

註冊Tcp通道:
IDictionary tcpProp = new Hashtable();
tcpProp["name"] = "tcp9090";
tcpProp["port"] = 9090;
IChannel channel = new TcpChannel(tcpProp,
 new BinaryClientFormatterSinkProvider(),
 new BinaryServerFormatterSinkProvider());
ChannelServices.RegisterChannel(channel);

註冊Http通道:
IDictionary httpProp = new Hashtable();
httpProp["name"] = "http8080";
httpProp["port"] = 8080;
IChannel channel = new HttpChannel(httpProp,
 new SoapClientFormatterSinkProvider(),
 new SoapServerFormatterSinkProvider());
ChannelServices.RegisterChannel(channel);

在name屬性中,定義不同的通道名稱就可以了。

2、遠程對象元數據相關性

由於服務器端和客戶端都要用到遠程對象,通常的方式是生成兩份完全相同的對象Dll,分別添加引用。不過爲了代碼的安全性,且降低客戶端對遠程對象元數據的相關性,我們有必要對這種方式進行改動。即在服務器端實現遠程對象,而在客戶端則刪除這些實現的元數據。

由於激活模式的不同,在客戶端創建對象的方法也不同,所以要分離元數據的相關性,也應分爲兩種情況。

(1) WellKnown激活模式:

通過接口來實現。在服務器端,提供接口和具體類的實現,而在客戶端僅提供接口:
    public interface IServerObject
    {
        Person GetPersonInfo(string name,string sex,int age);
    }

public class ServerObject:MarshalByRefObject,IServerObject
{ ......}
注意:兩邊生成該對象程序集的名字必須相同,嚴格地說,是命名空間的名字必須相同。
           
(2) 客戶端激活模式:

如前所述,對於客戶端激活模式,不管是使用靜態方法,還是使用CreateInstance()方法,都必須在客戶端調用構造函數實例化對象。所以,在客戶端我們提供的遠程對象,就不能只提供接口,而沒有類的實現。實際上,要做到與遠程對象元數據的分離,可以由兩種方法供選擇:

a、利用WellKnown激活模式模擬客戶端激活模式:

方法是利用設計模式中的“抽象工廠”,下面的類圖表描述了總體解決方案:

我們在服務器端的遠程對象中加上抽象工廠的接口和實現類:
    public interface IServerObject
    {
        Person GetPersonInfo(string name,string sex,int age);
    }

    public interface IServerObjFactory
    {
        IServerObject CreateInstance();       
    }

    public class ServerObject:MarshalByRefObject,IServerObject
    {
        public Person GetPersonInfo(string name,string sex,int age)
        {
            Person person = new Person();
            person.Name = name;
            person.Sex = sex;
            person.Age = age;
            return person;
        }       
    }

    public class ServerObjFactory:MarshalByRefObject,IServerObjFactory
    {
        public IServerObject CreateInstance()
        {
            return new ServerObject();
        }
    }

然後再客戶端的遠程對象中只提供工廠接口和原來的對象接口:
    public interface IServerObject
    {
        Person GetPersonInfo(string name,string sex,int age);
    }

    public interface IServerObjFactory
    {
        IServerObject CreateInstance();       
    }
我們用WellKnown激活模式註冊遠程對象,在服務器端:
           //傳遞對象;
            RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServerRemoteObject.ServerObjFactory),
                "ServiceMessage",WellKnownObjectMode.SingleCall);

注意這裏註冊的不是ServerObject類對象,而是ServerObjFactory類對象。

客戶端:
ServerRemoteObject.IServerObjFactory serverFactory =               
                (ServerRemoteObject.IServerObjFactory) Activator.GetObject(
                typeof(ServerRemoteObject.IServerObjFactory),
                "tcp://localhost:8080/ServiceMessage");

ServerRemoteObject.IServerObject serverObj = serverFactory.CreateInstance();

爲什麼說這是一種客戶端激活模式的模擬呢?從激活的方法來看,我們是使用了SingleCall模式來激活對象,但此時激活的並非我們要傳遞的遠程對象,而是工廠對象。如果客戶端要創建遠程對象,還應該通過工廠對象的CreateInstance()方法來獲得。而這個方法正是在客戶端調用的。因此它的實現方式就等同於客戶端激活模式。

b、利用替代類來取代遠程對象的元數據

實際上,我們可以用一個trick,來欺騙Remoting。這裏所說的替代類就是這個trick了。既然是提供服務,Remoting傳遞的遠程對象其實現的細節當然是放在服務器端。而要在客戶端放對象的副本,不過是因爲客戶端必須調用構造函數,而採取的無奈之舉。既然具體的實現是在服務器端,又爲了能在客戶端實例化,那麼在客戶端就實現這些好了。至於實現的細節,就不用管了。

如果遠程對象有方法,服務器端則提供方法實現,而客戶端就提供這個方法就OK了,至於裏面的實現,你可以是拋出一個異常,或者return 一個null值;如果方法返回void,那麼裏面可以是空。關鍵是這個客戶端類對象要有這個方法。這個方法的實現,其實和方法的聲明差不多,所以我說是一個trick。方法如是,構造函數也如此。

還是用代碼來說明這種“陰謀”,更直觀:

服務器端:
    public class ServerObject:MarshalByRefObject
    {
        public ServerObject()
        {
           
        }

        public Person GetPersonInfo(string name,string sex,int age)
        {
            Person person = new Person();
            person.Name = name;
            person.Sex = sex;
            person.Age = age;
            return person;
        }       
    }

客戶端:
    public class ServerObject:MarshalByRefObject
    {
        public ServerObj()
        {
            throw new System.NotImplementedException();
        }

        public Person GetPersonInfo(string name,string sex,int age)
        {
            throw new System.NotImplementedException();
        }       
    }

比較客戶端和服務器端,客戶端的方法GetPersonInfo(),沒有具體的實現細節,只是拋出了一個異常。或者直接寫上語句return null,照樣OK。我們稱客戶端的這個類爲遠程對象的替代類。

3、利用配置文件實現

前面所述的方法,於服務器uri、端口、以及激活模式的設置是用代碼來完成的。其實我們也可以用配置文件來設置。這樣做有個好處,因爲這個配置文件是Xml文檔。如果需要改變端口或其他,我們就不需要修改程序,並重新編譯,而是只需要改變這個配置文件即可。

(1) 服務器端的配置文件:
<configuration>
  <system.runtime.remoting>
    <application name="ServerRemoting">
      <service>
        <wellknown mode="Singleton" type="ServerRemoteObject.ServerObject" objectUri="ServiceMessage"/>
      </service>
      <channels>
         <channel ref="tcp" port="8080"/>
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

如果是客戶端激活模式,則把wellknown改爲activated,同時刪除mode屬性。

把該配置文件放到服務器程序的應用程序文件夾中,命名爲ServerRemoting.config。那麼前面的服務器端程序直接用這條語句即可:
RemotingConfiguration.Configure("ServerRemoting.config");

(2) 客戶端配置文件

如果是客戶端激活模式,修改和上面一樣。調用也是使用RemotingConfiguration.Configure()方法來調用存儲在客戶端的配置文件。

配置文件還可以放在machine.config中。如果客戶端程序是web應用程序,則可以放在web.config中。

4、啓動/關閉指定遠程對象

Remoting中沒有提供類似UnregisterWellKnownServiceType()的方法,也即是說,一旦通過註冊了遠程對象,如果沒有關閉通道的話,該對象就一直存在於通道中。只要客戶端激活該對象,就會創建對象實例。如果Remoting傳送的只有一個遠程對象,這不存在問題,關閉通道就可以了。如果傳送多個遠程對象呢?要關閉指定的遠程對象應該怎麼做?關閉之後又需要啓動又該如何?

我們注意到在Remoting中提供了Marshal()和Disconnect()方法,答案就在這裏。Marshal()方法是將MarshalByRefObject類對象轉化爲ObjRef類對象,這個對象是存儲生成代理以與遠程對象通訊所需的所有相關信息。這樣就可以將該實例序列化以便在應用程序域之間以及通過網絡進行傳輸,客戶端就可以調用了。而Disconnect()方法則將具體的實例對象從通道中斷開。

方法如下:
首先註冊通道:
TcpChannel channel = new TcpChannel(8080);
ChannelServices.RegisterChannel(channel);

接着啓動服務:
先在服務器端實例化遠程對象。
ServerObject obj = new ServerObject();

然後,註冊該對象。注意這裏不用RemotingConfiguration.RegisterWellKnownServiceType(),而是使用RemotingServices.Marshal():

ObjRef objrefWellKnown = RemotingServices.Marshal(obj, "ServiceMessage");

如果要註銷對象,則:
RemotingServices.Disconnect(obj);

要注意,這裏Disconnect的類對象必須是前面實例化的對象。正因爲此,我們可以根據需要創建指定的遠程對象,而關閉時,則Disconnect之前實例化的對象。

至於客戶端的調用,和前面WellKnown模式的方法相同,仍然是通過Activator.GetObject()來獲得。但從實現代碼來看,我們會注意到一個問題,由於服務器端是顯式的實例化了遠程對象,因此不管客戶端有多少,是否相同,它們調用的都是同一個遠程對象。因此我們將這個方法稱爲模擬的SingleTon模式。

客戶端激活模式

我們也可以通過Marshal()和Disconnect()來模擬客戶端激活模式。首先我們來回顧“遠程對象元數據相關性”一節,在這一節中,我說到採用設計模式的“抽象工廠”來創建對象實例,以此用SingleCall模式來模擬客戶端激活模式。在仔細想想前面的模擬的SingleTon模式。是不是答案就將呼之欲出呢?

在“模擬的SingleTon”模式中,我們是將具體的遠程對象實例進行Marshal,以此讓客戶端獲得該對象的引用信息。那麼我們換一種思路,當我們用抽象工廠提供接口,工廠類實現創建遠程對象的方法。然後我們在服務器端創建工廠類實例。再將這個工廠類實例進行Marshal。而客戶端獲取對象時,不是獲取具體的遠程對象,而是獲取具體的工廠類對象。然後再調用CreateInstance()方法來創建具體的遠程對象實例。此時,對於多個客戶端而言,調用的是同一個工廠類對象;然而遠程對象是在各個客戶端自己創建的,因此對於遠程對象而言,則是由客戶端激活,創建的是不同對象了。

當我們要啓動/關閉指定對象時,只需要用Disconnet()方法來註銷工廠類對象就可以了。

六、小結

Microsoft.Net Remoting真可以說是博大精深。整個Remoting的內容不是我這一篇小文所能盡述的,更不是我這個Remoting的初學者所能掌握的。王國維在《人間詞話》一書中寫到:古今之成大事業大學問者,必經過三種境界。“昨夜西風凋碧樹,獨上高樓,望盡天涯路。”此第一境界也。“衣帶漸寬終不悔,爲伊消得人憔悴。”此第二境界也。“衆裏尋他千百度,驀然回首,那人卻在燈火闌珊處。”此第三境界也。如以此來形容我對Remoting的學習,還處於“獨上高樓,望盡天涯路”的時候,真可以說還未曾登堂入室。

或許需得“衣帶漸寬”,學得Remoting“終不悔”,方纔可以“驀然回首”吧。

10
0
(請您對文章做出評價)
« 上一篇:品味.NET經典[轉載]
» 下一篇:在Visual C# .Net 中怎樣向Microsoft Excel 2002 傳輸XML數據[譯]

posted on 2004-07-30 20:44 張逸 閱讀(85465) 評論(159) 編輯 收藏 所屬分類: .NET Remoting

評論

#45樓 2004-11-26 17:52 linaren

public class GoodsPort : System.MarshalByRefObject,GoodsPerform 

public GoodsPort() 
{} 
public DataTable GetAllGoods() 

try 

//CommonLibary.SerializePort.SerializeObject( 
string sqlStr = "select * from " 
+ DBMapping.GoodsTable.BaseInfoTable.TableName ; 

DataTable dt = DataPort.GetDataPort().DBE.ExecQuery( sqlStr ).Tables[0] ;
return dt; 

catch(Exception ex) 

string str = ex.Message; 
return null; 


public string AddGoods( Goods goods ){ return "";} 
public string DelGoods( Goods goods ){ return "";} 
public string ModGoods( Goods goods ){ return "";} 
}//class Goods 


 回覆 引用   

#46樓 2004-11-26 18:04 linaren

GoodsPort這個調用就會出現上術的問題的 

現在我又遇到一個問題 :) 
是這樣的的在一個方法的參數裏有個一個自定義class類型 
這個類裏面有幾個string字段,還有個Hashtable字段 
如下 
interface a{ 
string Done( MYClass myclass ); 


public class ima 

public string Done(MYClass myclass ); 


..... 
return ""; 


======================== 
客戶端調用如下: 
a A = (a)System.Activator.GetObject( typeof(a), 
RemoteObjectUrl ); 
A.Done( myclass); 

問題是:在執行A.Done( myclass )時竟會出現異常---索引超出數組界限! 
這個是不是跟傳參編組有關?我真的很迷惑 

(附加說明:我把幾個接口都在一個類中實現的,不知與這有關係沒) 
:)又要麻煩你解答了
  回覆 引用   

#47樓 2004-11-26 18:05 linaren

這個類也加了序列化屬性  回覆 引用   

#48樓[樓主2004-11-26 19:00 wayfarer      

我仔細看了你的代碼,我想問清楚幾點: 
1、GoodsPort類,你在服務器端激活的時候,typeof()取的是GoodsPort,還是接口GoodsPerform?正確的做法是用類類型。 
2、如果是調用的類類型,那麼typeof()裏面對於類類型是否描述正確?我不知道你這些類,接口和服務端程序是否都在一個命名空間下。你必須保證typeof()裏的類類型是正確的,或者是完整的。 
3、如果還是不對,那麼我就需要寫段代碼來測試一下了。因爲目前我正在出差,使用的機器是公司測試部門的,他們的機器上沒有安裝Visual Studio 2003,所以我需要在下週回去後,寫段測試代碼給你看。 

你所說的後一個問題,即出現“索引超出數組界限”的異常,我還沒有遇見過。首先你保證MYClass類是否添加了[Serializabled]? 如果已經添加了,那麼是否是因爲Hashtable的原因?我沒看到你這個MyClass類的代碼,不好下肯定的判斷。 

另外,我在實際運用中,並沒有在傳值類中用過Hashtable,我不知道Hashtable是否支持序列化。所以,我需要實際測試一下。 

希望下週我回去之後,能給你一個滿意的答案。這期間,你也請仔細檢查一下代碼,或者請教一下其他高手。 

btw:我不明白你這句話:把幾個接口都在一個類中實現的。 

如果你是把幾個接口都放在一個cs文件中,那時沒有問題的。VS的元數據並不以文件爲界限。你可以在一個cs文件中放n個接口和類。 

如果你說的是一個類同時實現了幾個接口,那也沒有問題。Remoting支持這種做法。只要你保證在服務端和客戶端都部署了這些接口,就OK。 
 回覆 引用 查看   

#49樓 2004-11-29 09:36 linaren

thank you very much! 
前兩天休息了,今天上班纔看到的 

===1、GoodsPort類,你在服務器端激活的時候,typeof()取的是GoodsPort,還是接口GoodsPerform?正確的做法是用類類型。 

:: 我在typeof()取的是GoodsPerform,因爲要是取GoodsPort的話,就達不到我要的組件分佈效果,因爲在GoodsPort裏用到的其它有關的業務類還有數據操作等(這些我理解是可以完全只放在服務端的),要是用GoodsPerform的話,這些組件也都要放到Client端了,那樣的話,整個架構可能是沒多大意義了, 
但不知你是怎樣解決這個問題的。 


===“索引超出數組界限”===== 
這個問題我自己也正查看是什麼原因的 

=====btw:我不明白你這句話:把幾個接口都在一個類中實現的。== 
:) 可能是我的表達能力太差了,我的意思就是 
是一個類同時實現了幾個接口 
因爲我還沒解決註冊多個類型的問題,就只好這樣能進展下去的 
讓你見笑了:) 

~@~很感謝你能百忙之中來解答我的疑問的 
(我自我介紹一下我的情況吧,由於公司前幾個項目都是在最原始的C/S架構下做的,當然這是有原因的比如用戶要求的時間等,現在考慮要進行改進的,因爲這在實際應用中發現原來的模式存在有很多的不便之處,而我現在就是負責探索採用.Remoting體系結構的可行性與實際的技術點探索,我以前對這的確是知之甚少-_-,現在發現這其中的東西多着呢!) 

 回覆 引用   

#50樓 2004-11-29 10:09 linaren

:) 
===“索引超出數組界限”===== 
這個問題我自己也正查看是什麼原因的 
==================== 
這個是我自己搞錯了,引用了在Hashtable中不存在的key
  回覆 引用   

#51樓 2004-11-29 13:06 wayfarer

@linaren: 
如果我沒有理解錯的話,我想我找到你的錯誤原因了。 
GoodsPerform是接口,GoodsPort是類類型,同時也是你的遠程對象。GoodsPort處理了很多業務,這些業務當然是放在服務器端的。如果GoodsPerform只是GoodsPort類對象中一部分業務的接口的話,那麼該類中的其它業務還需要定義接口。 

關鍵的一點是:在服務器端,你只能激活類對象,即typeof()只能取GoodsPort。道理很簡單,首先接口並不能派生MarshalByRefObject,所以纔會出現你前面說的異常。其次,這裏所謂的激活,就是要創建該對象的實例,接口當然是不能創建實例的了。 

你想把業務分類開,以實現分佈式處理。這很簡單啊,由於你的遠程對象類GoodsPort實現了GoodsPerform接口。所以,你只需要在客戶端放上接口定義就可以了。此時,在客戶端獲得遠程對象的時候,這裏的typeof()纔是取接口類型: 
GoodsPerform perform = (GoodsPerform)Activator.GetObject(typeof(GoodsPerform)); 

btw:我提醒一下,在爲接口類型命名時,最好在該名字前加上“I”,即將GoodsPerform 接口命名爲IGoodsPerform 。這樣便於區別接口類型和類類型。因爲,我發現你在寫的時候,你常常把這兩種對象弄混淆。
  回覆 引用   

#52樓 2004-11-30 08:51 linaren

to wayfarer 
first i am obliged to you for your help! 
但是我對你的這段解答 
############################## 
關鍵的一點是:在服務器端,你只能激活類對象,即typeof()只能取GoodsPort。道理很簡單,首先接口並不能派生MarshalByRefObject,所以纔會出現你前面說的異常。其次,這裏所謂的激活,就是要創建該對象的實例,接口當然是不能創建實例的了。 
############################## 
還是有些迷惑的地方 
1. 你說的typeof()是指註冊時的還是在Client調用時的? 
我在註冊時typeof()裏取的就是具體的實現類,不是接口,client端調用與你寫的 
========================== 
GoodsPerform perform = (GoodsPerform)Activator.GetObject(typeof(GoodsPerform)); 
=============================== 
是一致的。 

2.我註冊了兩個類型(UserPort與GoodsPort,當然都是繼承於MarshalByRefObject的) 
UserPort實現了IUser接口,GoodsPort實現了IGoodsPerform接口 
,爲什麼第一個註冊的可以正常調用,第二個就出現我開始說的問題(試圖創建object2類型的已知對象,已知對象必須從shalByRefObject )? 
**我的註冊多個對象的代碼已經在上面貼出來了 

3. :) 希望你能給寫個註冊多個對象的示例代碼,我學習一下,看是那裏的問題。
  回覆 引用   

#53樓 2004-11-30 10:52 wayfarer

哦,原來是這樣。那麼按道理應該是沒有問題的。 
public interface IUserPerform 

void Foo1(); 

public interface IGoodsPerform 

void Foo2(); 

public class UserPort:MarshalByRefObject,IUserPerform 

public void Foo1(){//代碼略;} 

public class GoodsPort:MarshalByRefObject,IGoodsPerform 

public void Foo2(){//代碼略;} 

以上是遠程對象; 
TcpChannel channel = new TcpChannel(8000); 
ChannerServices.RegisterChannel(channel); 
RemotingConfiguration.RegisterWellKnownServiceType( 
typeof(UserPort), 
"UserMessage",WellKnownObjectMode.SingleCall); 
RemotingConfiguration.RegisterWellKnownServiceType( 
typeof(GoodsPort), 
"GoodsMessage",WellKnownObjectMode.SingleCall); 
以上是服務端; 
TcpChannel channel = new TcpChannel(); 
ChannelServices.RegisterChannel(channel); 
IUserPerform iUser = (IUserPerform)Activator.GetObject(typeof(IUserPerform)); 
iUser.Foo1(); 
IGoodsPerform iGoods = (IGoodsPerform)Activator.GetObject(typeof(IGoodsPerform)); 
iGoods.Foo2(); 
以上是客戶端; 
我只是憑記憶寫的以上代碼。因爲在出差途中,機器上沒有VS。如果有誤的話,我想等我回去後,給你一個測試程序,你再看看。我還是肯定一點,註冊多個對象是絕對沒有問題的:) 
 回覆 引用   

#54樓 2004-11-30 15:04 linaren

thanks agin ! 
After studying your code, i find out the matter! 

問題出在了我在Client調用時引用的遠程對象的uri錯誤了 

IGoodsPerform iGoods = (IGoodsPerform)Activator.GetObject(typeof(IGoodsPerform), objectUri ); 

其中的objectUri錯誤了。 

很是感謝你的!祝你旅途愉快,工作順利! 

忍不住又冒出個問題了 :) 
我在這個問題解決之前,瞭解到可以註冊多個通道,需要創建新的程序域。 
但我不知這在什麼情況下合適的,或者說這樣有什麼意義或用途, 
:) 希望有空再解答一下吧! 


 回覆 引用   

#55樓 2004-11-30 15:39 wayfarer

呵呵,解決了就好。遺憾的是我在給你回覆的時候,客戶端調用Activator.GetObject()方法也忘了寫URI,呵呵,真是慚愧。 

關於多通道的問題,我一直也還未完全解決。在本文中,已經描述了註冊通道的方法,但是註冊多通道後,怎樣將對象註冊到指定的通道中,一直還是個問題。所以我也沒有一個好的解決方案。希望隨着我的學習深入後,能解決你的問題。 
 回覆 引用   

#56樓 2004-12-03 10:04 linaren

謝謝你 
我以後有問題可能還要麻煩你的 :) 
這兩天我正搭建試驗系統原型 
知識可能是具有很強的繁衍性吧,由一個頭開始我發現引發了很多東西的呵呵
  回覆 引用   

#57樓 2004-12-06 20:04 roky      

不錯,總結的很好  回覆 引用 查看   

#58樓 2004-12-22 18:43 cenci

寫的不錯,多謝 
請問SingleTon 和SingleCall是一次實例化一個對象還是可以實例化對象。(象EJB中SessionBean?)
  回覆 引用   

#59樓[樓主2004-12-22 21:31 wayfarer      

SingleCall是在客戶端每調用一次方法就實例化對象,之後馬上銷燬。而SingleTon則自始自終只實例化一個對象。 
 回覆 引用 查看   

#60樓 2005-01-17 14:35 aoyu

寫了個remoting的project,採用客戶端激活, 
當我用soapsuds.exe爲遠程類生成替代類時,卻發現生成的替代類中不包括遠程類中帶訪問器的public屬性,卻將遠程類中的private屬性公開了。比如遠程類Person裏分別定義了 
private string _Name; 
public string Name 

get 

return _Name; 


set 

_Name = value; 

但是在生成的代理類中就剩下了: 
public string _Name. 

很是鬱悶,不知到原因出在哪裏。
  回覆 引用   

#61樓 2005-02-18 21:52 heping

寫了個remoting的項目,採用客戶端激活,採用了:利用替代類來取代遠程對象的元數據。 
有個問題: 
原來的三層結構,非常清楚簡單。界面層 訪問 應用層,應用層 訪問 數據訪問層。數據訪問層與數據庫通信。 
現在,利用替代類,我新增了兩個項目,分別是:客戶端的RemoteObject項目,就是:空的遠程對象的集合;服務端的RemoteObject項目,就是:實現了的遠程對象的集合。 

由於我的項目內容比較多,這每個小模塊,都增加了這麼兩層的工作,彙總起來,工作量就直線上升。 

請教,我現在的結構如下,正確麼? 
項目1:客戶端:界面層,訪問客戶端的RemoteObject項目; 
項目2:客戶端的RemoteObject項目:把應用層的每個類的方法用空的實現,寫一次;考慮到應用層的類的數目龐大,若干個小模塊,放在一個對象中。但這樣,方法名,要作修改; 
項目3:服務端的RemoteObject項目:客戶端的RemoteObject項目,每個方法,調用應用層的類的方法;應用層、數據訪問層; 
項目4:公共對象:存放客戶端和服務端各層都共享的數據對象; 
項目5:服務端:調用服務端的RemoteObject項目。
  回覆 引用   

#62樓 2005-02-18 21:53 heping

寫了個remoting的項目,採用客戶端激活,採用了:利用替代類來取代遠程對象的元數據。 
有個問題: 
原來的三層結構,非常清楚簡單。界面層 訪問 應用層,應用層 訪問 數據訪問層。數據訪問層與數據庫通信。 
現在,利用替代類,我新增了兩個項目,分別是:客戶端的RemoteObject項目,就是:空的遠程對象的集合;服務端的RemoteObject項目,就是:實現了的遠程對象的集合。 

由於我的項目內容比較多,這每個小模塊,都增加了這麼兩層的工作,彙總起來,工作量就直線上升。 

請教,我現在的結構如下,正確麼? 
項目1:客戶端:界面層,訪問客戶端的RemoteObject項目; 
項目2:客戶端的RemoteObject項目:把應用層的每個類的方法用空的實現,寫一次;考慮到應用層的類的數目龐大,若干個小模塊,放在一個對象中。但這樣,方法名,要作修改; 
項目3:服務端的RemoteObject項目:客戶端的RemoteObject項目,每個方法,調用應用層的類的方法;應用層、數據訪問層; 
項目4:公共對象:存放客戶端和服務端各層都共享的數據對象; 
項目5:服務端:調用服務端的RemoteObject項目。
  回覆 引用   

#63樓 2005-02-18 21:53 heping

寫了個remoting的項目,採用客戶端激活,採用了:利用替代類來取代遠程對象的元數據。 
有個問題: 
原來的三層結構,非常清楚簡單。界面層 訪問 應用層,應用層 訪問 數據訪問層。數據訪問層與數據庫通信。 
現在,利用替代類,我新增了兩個項目,分別是:客戶端的RemoteObject項目,就是:空的遠程對象的集合;服務端的RemoteObject項目,就是:實現了的遠程對象的集合。 

由於我的項目內容比較多,這每個小模塊,都增加了這麼兩層的工作,彙總起來,工作量就直線上升。 

請教,我現在的結構如下,正確麼? 
項目1:客戶端:界面層,訪問客戶端的RemoteObject項目; 
項目2:客戶端的RemoteObject項目:把應用層的每個類的方法用空的實現,寫一次;考慮到應用層的類的數目龐大,若干個小模塊,放在一個對象中。但這樣,方法名,要作修改; 
項目3:服務端的RemoteObject項目:客戶端的RemoteObject項目,每個方法,調用應用層的類的方法;應用層、數據訪問層; 
項目4:公共對象:存放客戶端和服務端各層都共享的數據對象; 
項目5:服務端:調用服務端的RemoteObject項目。
  回覆 引用   

#64樓[樓主2005-02-19 21:24 wayfarer      

@heping: 
如果模塊較多,最好不要採用替代類的方法。這樣會讓添加很多工作量。建議使用工廠方法,以WellKnown方式模擬客戶端激活。 

另外對於分佈式開發,一定要注意方法。將接口單獨放在一個程序集中,便於部署。
  回覆 引用 查看   

#65樓 2005-02-21 10:24 frank

爲什麼客戶端異常退出後,再重新連接服務器就會報“基礎連接已關閉,無法連接到遠程服務器。”  回覆 引用   

#66樓 2005-02-21 10:35 frank

爲什麼客戶端異常退出後,再重新連接服務器就會報“基礎連接已關閉,無法連接到遠程服務器。”  回覆 引用   

#67樓 2005-02-21 10:35 frank

爲什麼客戶端異常退出後,再重新連接服務器就會報“基礎連接已關閉,無法連接到遠程服務器。”  回覆 引用   

#68樓 2005-02-21 10:35 frank

爲什麼客戶端異常退出後,再重新連接服務器就會報“基礎連接已關閉,無法連接到遠程服務器。”?  回覆 引用   

#69樓 2005-03-14 13:39 city

我遇到一個問題就是: 
客戶端和服務器都是用配置文件配置的,server端直接調用RemotingConfiugre.config(配置文件)。然後一切正常,可是當客戶端調用傳入SqlParameter數組參數的時候異常:“此遠程處理代理沒有信道接收,這意味着服務器沒有正在偵聽的已註冊服務器信道,或者此應用程序沒有用來與服務器對話的適當客戶端信道”。 
而我改爲string數組是沒有問題的。可否指點一下?
  回覆 引用   

#70樓 2005-03-29 13:58 Phoenix

我也在學習Remoting.看到作者這麼熱心的解答問題,真是讓人感動.  回覆 引用   

#71樓 2005-03-31 11:12 skywood

其實採用替代類的方法也並不會增加多少工作量的,我沒有怎麼用過SoapSuds.exe這個工具,而且看他的命令行參數,感覺一不直觀二也確實挺麻煩的。我在現在做的一個項目中就是自己來寫這個生成器,雖然代碼寫的很爛,但也就百來行代碼,用起來還可以,反正沒出什麼錯,選中程序集就可以自動生成其中所有的替代類了。當然適用的情形可能都很簡單,我的意思是實現這個生成器或者擴充功能並不難和麻煩,所以使用替代類這個方法我不覺得會有什麼問題,我十分支持。對於樓主所說的"算不上是真正的分佈式應用",不知道是什麼意思,我並不這麼認爲。  回覆 引用   

#72樓 2005-04-20 22:46 form

在remoting中如何傳遞一個form呢?  回覆 引用   

#73樓 2005-05-04 17:24 阿扁

爲什麼要在客戶端也要一個dll啊,感覺繞了一個圈. 
 回覆 引用   

#74樓 2005-05-21 09:56 pdy

蟈蟈俊的文章我也看了,解決了不少問題。可是還有問題! 
你有沒有嘗試過在通道中傳輸SqlParameter?? 
我找了整個網絡也沒找到解決方法 
現在報的錯是: 
權限被拒絕: 無法遠程調用非公共或靜態方法
  回覆 引用   

#75樓 2005-05-21 11:40 p

爲什麼不能調用remoting裏的這個函數? 

public SqlCommand BuildQueryCommand(string storedProcName, SqlParameter[] parameters) 

try 

if(myCn.State==ConnectionState.Closed) 
myCn.Open(); 
SqlCommand command = new SqlCommand( storedProcName, myCn ); 
command.CommandType = CommandType.StoredProcedure; 

foreach (SqlParameter parameter in parameters) 

command.Parameters.Add( parameter ); 

return command; 

catch(Exception ex) 

throw new Exception(ex.Message+"數據庫操作失敗~(由BuildQueryCommand引發!)/n"); 


finally 

myCn.Close(); 




其他的都沒問題
  回覆 引用   

#76樓 2005-05-25 16:58 留心

//關閉監聽; 
tcpChannel.StopListening(null); 

//註銷通道; 
ChannelServices.UnregisterChannel(tcpChannel) 

我在服務器端"stop"按扭中寫上以上代碼後,但發現並不能關閉服務,客戶端依然可與服務器連接,不知爲什麼:( 

你的文章寫的真不錯 
 回覆 引用   

#77樓 2005-07-22 15:36 花雨[未註冊用戶]

報錯:the underlying connection was closed:unable to connect to the remote server 
各位好心人幫我看看!謝了 

類代碼://客戶端和服務器端用來通訊的“共享命令集” 
using System; 
using System.Runtime; 
using System.Data.SqlClient; 

namespace DotNetRemoteTest 

/// <summary> 
/// Class1 的摘要說明。 
/// </summary> 
public class ResumeLoader:System.MarshalByRefObject 

private SqlConnection dbConnection; 
public ResumeLoader() 

// 
// TODO: 在此處添加構造函數邏輯 
// 
this.dbConnection = new System.Data.SqlClient.SqlConnection(); 
this.dbConnection.ConnectionString ="data source=YUANTT;initial catalog=gwcp_db;integrated security=SSPI;persist security info=False;workstation id=YUANTT;packet size=4096"; 
     System.Console.WriteLine("New Referance Added!"); 

public Resume GetResumeByUserID(decimal userid1) 

Resume resume = new Resume(0); 
try 

dbConnection.Open(); 
SqlCommand cmd = new SqlCommand("SELECT resumeid,userid,title,body FROM Resume WHERE Resume.userid="+userid1+"",dbConnection); 
SqlDataReader aReader = cmd.ExecuteReader(); 
if(aReader.Read()) 

resume.resumeid=aReader.GetDecimal(0); 
resume.userid=aReader.GetDecimal(1); 
resume.title=aReader.GetString(2); 
resume.body=aReader.GetString(3); 

aReader.Close(); 
dbConnection.Close(); 

catch(Exception x) { resume.title="Error:"+x; } 
return resume; 



[Serializable] 
  public class Resume 
  { 
private decimal resumeid1, userid1; 
private string body1,title1; 
public Resume(decimal resumeid1) 

this.resumeid=resumeid1; 
this.userid=1; 
this.body="This is the default body of the resume"; 
this.title="This is the default Title"; 




public decimal resumeid 

get { return resumeid1; } 
set { this.resumeid1=value; } 

public decimal userid 

get { return userid1; } 
set { this.userid1=value; } 

public string body 

get { return body1; } 
set { this.body1=value;} 

public string title 

get { return title1; } 
set { this.title1=value; } 



}//RESUME對象結束 
}//DotNetRemoteTest名字空間結束 

服務器端代碼: 
using System; 
using System.Runtime; 
using System.Runtime.Remoting; 
using System.Runtime.Remoting.Channels; 
using System.Runtime.Remoting.Channels.Http; 
using System.Data.SqlClient; 
using DotNetRemoteTest; 


namespace ResumeSuperServer 

/// <summary> 
/// Class1 的摘要說明。 
/// </summary> 
class ResumeSuperServer 

/// <summary> 
/// 應用程序的主入口點。 
/// </summary> 
[STAThread] 
static void Main(string[] args) 

// 
// TODO: 在此處添加代碼以啓動應用程序 
// 
HttpServerChannel channel = new HttpServerChannel(9932); 
ChannelServices.RegisterChannel(channel); 
RemotingConfiguration.RegisterWellKnownServiceType(typeof(ResumeLoader),"ResumeLoader", WellKnownObjectMode.SingleCall); 
System.Console.WriteLine("Press Any Key"); 
System.Console.ReadLine(); 




客戶端代碼: 
using System; 
using System.Runtime.Remoting; 
using System.Runtime.Remoting.Channels; 
using System.Runtime.Remoting.Channels.Http; 
using DotNetRemoteTest; 


namespace ResumeClient 

/// <summary> 
/// Class1 的摘要說明。 
/// </summary> 
class ResumeClient 

/// <summary> 
/// 應用程序的主入口點。 
/// </summary> 
[STAThread] 
static void Main(string[] args) 

// 
// TODO: 在此處添加代碼以啓動應用程序 
// 
ChannelServices.RegisterChannel(new HttpClientChannel()); 
ResumeLoader loader = (ResumeLoader)Activator.GetObject(typeof(ResumeLoader), "http://202.113.96.37:9932/ResumeLoader"); 
if(loader==null) 
{ Console.WriteLine("Unable to get remote referance"); } 
else 

Resume resume = loader.GetResumeByUserID(2); 
Console.WriteLine("ResumeID:"+ resume.resumeid); 
Console.WriteLine("UserID:"+ resume.userid); 
Console.WriteLine("Title:"+ resume.title); 
Console.WriteLine("Body:"+ resume.body); 


Console.ReadLine();//在能夠看到結果前不讓窗口關閉 




數據庫結構: 
Resume 
  ResumeID, numeric (autonumber) 
  UserID, numeric 
  Title, Char(30) 
  Body, Text 

 回覆 引用   

#78樓 2005-07-28 12:14 casual[未註冊用戶]

關於“註冊多個通道”, 
“這個時候,我們必須用到System.Collection中的IDictionary接口:” 

有更簡單的方法,如下: 
TcpServerChannel channel = new TcpServerChannel(string applicationname,int port); 
Http通道也有相應的構造函數直接指定Name屬性。 


最後感謝作者,你的這幾篇文章就是我的Remoting入門教程! 
謝謝 
 回覆 引用   

#79樓 2005-09-05 16:49 遊客[未註冊用戶]

wayfarer解答問題的誠意令人敬佩  回覆 引用   

#80樓 2005-09-15 00:44 yicone      

to city: 
to p: 
sqlParameter沒有被串行化 

to py: 
好像只能自己改造了,你還是問蟈蟈吧 

to form: 
讓你的form類繼承自MarshalByRefObject, 使用沒有參數的構造函數,(一定要使用的話,使用客戶端激活方式, 這時客戶端的代碼使用Activator.CreateInstance()能夠接收參數數組的那個重載, 好像是上面的a);所有客戶端向服務器傳遞的參數和方法調用的返回值必須可序列化! 

to 阿扁: 
文章中已經介紹過了 
“由於服務器端和客戶端都要用到遠程對象,通常的方式是生成兩份完全相同的對象Dll,分別添加引用。不過爲了代碼的安全性,且降低客戶端對遠程對象元數據的相關性,我們有必要對這種方式進行改動。即在服務器端實現遠程對象,而在客戶端則刪除這些實現的元數據。” 

to frank: 
雖然不能解決你的問題,但還是想知道你使用的是客戶端激活方式嗎?
  回覆 引用 查看   

#81樓 2005-09-15 00:51 yicone      

建議WayFarer大俠整理一下這裏,文章加上評論太長了,我來回拖動鼠標幾十次,好累啊! 
幻想:要是有VS.NET中“ctrl" + "-"的功能就好了,或者把文章用#region #endregion按章節、小分類摺疊起來就好了,目前好像不支持這倆個
  回覆 引用 查看   

#82樓 2005-09-15 09:47 遊客[未註冊用戶]

lovecherry文中說: 
SingleCall 類型對於每個客戶端請求始終只有一個實例。下一個方法調用將由另一個服務器實例提供服務 
wayfarer文中說: 
當客戶端調用遠程對象的方法時,Remoting會爲每一個客戶端建立一個遠程對象實例 

我感覺兩句話意思差很遠,除了自己動手試驗外,哪個更接近真相?
  回覆 引用   

#83樓 2005-09-18 13:24 feng[未註冊用戶]

請教一下wayFarer大俠,一個.net Remoting支撐負載的問題。我們有個項目,有500個遠程客戶端通過1M 帶寬ADSL和服務器端的中央數據庫進行數據交換,每客戶每分鐘大概有一次調用。看了您的文章後,想採用.net Remoting 充當客戶端和數據庫的中間層,請問一下.net Reomoting能否支撐如此負載,假設中間層服務器的配置是PIV 2.8, 1024M RAM;以及採用哪種方式比較好,single call,工廠模式?最好服務器端能控制住同時能有多少個用戶連接上來,對用戶請求進行排隊。 
謝謝!
  回覆 引用   

#84樓 2005-10-15 22:33 奇思軟件[未註冊用戶]

問一個搞笑的問題,如果我在服務器端註冊了singleton模式的遠程對象,這意味着,所有客戶端的調用都會調用同一個對象,並且它會維持狀態,但有很多情況下,我在服務器端也要對這個對象進行一些操作,那麼我該如何得到這個唯一對象的引址呢? 

就拿典型的事件通知模型來說,我在服務器端有一個EventServer對象,我在每一個客戶端放一個EventClient對象,這兩個對象都是遠程對象,並且EventServer爲singleton模式的對象,當EventClient對象聲明時會把自身的一段代碼註冊到EventServer的一個多點代理(事件)上,這樣當有事件觸發時,會調用這段代碼,但有時,我想在服務器端也有這樣的事件處理代碼,這時最好的方式當然是我有EventServer的引址,直接進行事件註冊,而由於不知道如何獲取這個引址,所以目前我在服務器端也生明一個EventClient,並遠程註冊到本地機器上的服務器上,雖然能完成同樣的功能,但總感覺不是十全十美,必競在同一臺電腦上我也通過遠程的方式相互通信,不知你有什麼建議? 
郵件:[email protected]
  回覆 引用   

#85樓 2005-12-01 10:28 kawashima[未註冊用戶]

你好,剛學.Net Remoting,我們現在需要在應用程序域中傳遞DataSet對象,請問如何序列化DataSet對象啊,期待你的回覆,我現在非常急,謝謝!  回覆 引用   

#86樓 2006-03-21 16:10 毀於隨[未註冊用戶]

@奇思軟件 
不知道使用Marshal的方法能不能實現你的要求呢?
  回覆 引用   

#87樓 2006-04-17 17:18 passing traveller      

 

文章真的寫的不錯,也和上面的有同感,最近開始學Remoting,你的這些文章也是我的入門文章!
謝謝了!
回覆 引用 查看   

 

#88樓 2006-04-29 14:51 Aadon[未註冊用戶]

汗... 
偶是菜鳥,看的濛濛的.. 
學Remoting 前要先學什麼?
  回覆 引用   

#89樓 2006-05-30 15:46 xietangzcn[未註冊用戶]

"可以將Remoting放到IIS中以Web Service的方式進行。"-->我正寫此程序,請教作者如何處理! 即通過WEB SERVICE的方式如何調用REMOTING,能否給個例子!


謝謝! 

順向作者問好!
  回覆 引用   

#90樓 2006-05-31 14:39 levinknight      

用Marshal()方法就意味着是Singleton,那用SingleCall激活的對象呢,要怎麼關閉呢?  回覆 引用 查看   

#91樓 2006-06-07 13:23 forair[未註冊用戶]

我也在學習REMOTING中,不過剛學的 
我想問一下,我在客戶端想傳一個自己定義的對象到服務端的時候它會彈出一個異常說: 
由於安全限制,無法訪問類型 System.EnterpriseServices.ServicedComponentMarshaler。 

我上網查了一下也查不到關於ServicedComponentMarshaler的資料, 
我的那個對象已經標明的[Serializable],並且那個對象我是做成一個組件的,我在客戶端和服務端也引用的它的DLL了,請大家指教一下,
  回覆 引用   

#92樓 2006-06-07 21:46 forair[未註冊用戶]

現在那個ServicedComponentMarshaler解決了,不過它現在又彈出一個異常說: 
由於安全限制,無法訪問類型Command.Customer(這個類是我自己定義的,是用來保存Customer信息的) 
真的很鬱悶啊,搞了很久啦,都還是沒有解決啊。。。。。。不懂啊。。 

我的項目: 
服務端: 
一個是Command(類庫),我把它做成一個組件。裏面有一個Customer.cs 
一個是OfficeMartLib(類庫),我也把它做成一個組件。裏面有一個OfficeMartLib.cs這個裏面定義了一些方法是在服務端運行的,其中一個是用來接收客戶端發來的對象,就是那個Customer. 
最後一個就是OfficeMartService(一個Window服務) 

客戶端: 
就一個就是寫了個一OfficeMartClient (一個Window應用程序) 

我這個東東主要想做的就是在客戶端通過遠程服務OfficeMartService調用組件發送一個對象回服務端 

因爲正在學習服務組件很多東西都不瞭解,希望各位大哥指一下啊, 
 回覆 引用   

#93樓 2006-08-09 00:46 dddang[未註冊用戶]

請問一個問題,如果我在服務端有多個remote object需要export,那麼RegisterChannel應該怎麼調用呢,換句話說是不是服務端只需要調用一次RegisterChannel,然後所有要export的remoteobject都調用RegisterWellKnownServiceType就可以了麼,是不是說多個remote Object共用一個前面註冊的channel端口呢?? 

上述情況是指所有Remote Object都存在於同一個Server程序中. 
class A:MarshalByRefObject; 
class B:MarshalByRefObject; 

class server{ 
server(){ 
ChannelService.RegisterChanell(new TcpChannel(9999)); 
RegisterWellKnownServiceType(typeof(A),"A"); 
RegisterWellKnownServiceType(typeof(B),"B"); 



我注意到RegisterChannel是個static函數,那麼RegsiterWellKnownService怎麼知道註冊的object應該用哪個Channel呢 
問了那麼多,也不知道描述清楚沒有
  回覆 引用   

#94樓 2006-10-10 10:13 feng[匿名][未註冊用戶]

幫我看看這是哪出錯了,我用的是framework2.0 
Activated模式 
運行客戶端出現異常 :類型沒有爲激活而註冊 
服務端: 
TcpChannel channel = new TcpChannel(8080); 
ChannelServices.RegisterChannel(channel,true); 
RemotingConfiguration.RegisterActivatedServiceType(typeof(Customer)); 
客戶端: 
TcpChannel chnl = new TcpChannel(); 
ChannelServices.RegisterChannel(chnl,true); 
RemotingConfiguration.RegisterActivatedClientType(typeof(Customer), "tcp://localhost:8080"); 
Customer cust = new Customer("Homer");
  回覆 引用   

#95樓 2006-10-26 15:43 laugha[未註冊用戶]

有關“Remoting要求必須是引用的對象,所以必須將Person類序列化”這句,不大理解,所有類都必須序列化嗎?如果不序列化PERSON類的話,客戶端調用時也可以取到PERSON類的屬性值阿? 
服務端: 
public DataEntity.UserInfo test() 

DataEntity.UserInfo uu=new DataEntity.UserInfo(); 
uu.userName="張三"; 
uu.userPwd="dfsdf"; 
return uu; 


客戶端:(採用TRICK類) 
obj = new RemotingServerObject.AutoServiceGetDataSet(); 
Console.WriteLine( "Client tcp {0}",obj.test().userName); 
也可以得到"張三"的阿?? 
請問各位我是哪裏理解錯了??謝謝大家。
  回覆 引用   

#96樓 2006-10-28 15:51 海邊拾貝的流浪者[未註冊用戶]

學習NET REMOTING 遇到這麼多問題 真的鬱悶哦 
現在的問題是 服務端 
- System.SystemException {"指定的轉換無效。"} System.SystemException
  回覆 引用   

#97樓 2006-11-18 10:58 Fox[匿名][未註冊用戶]

近來用Remoting做中間層服務器,採用服務端激活方式(客戶端激活時問題同樣存在),從服務端遠程對象取數據正常,但是在客戶端給遠程對象屬性賦值後,在服務端跟蹤調試發現服務端取不到客戶端賦給遠程對象的值(但在客戶端賦值後再取其屬性值可以取到),因為採用的是TCP協議通信,因為Remotin Server 底層通信採用的是Socket,所以感覺是數據隻能單向傳輸,即隻能從服務端接收數據,而不能傳送更新的數據給服務端,請問高手,是因為通道創建不對還是其它原因造成的!非常感謝!
注:我的服務端對象有從MarshalByRefObject繼承,不過沒有用 [Serializable]屬性 
服務端通道註冊代碼為: 
Hashtable TcpPort = new Hashtable(); 
TcpPort.Add("port", 3119); 

Hashtable HttpPort = new Hashtable(); 
HttpPort.Add("port", 4119); 

BinaryServerFormatterSinkProvider SrvProvider = new BinaryServerFormatterSinkProvider(); 
SrvProvider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full; 

BinaryClientFormatterSinkProvider ClientProvider = new BinaryClientFormatterSinkProvider(); 

TcpChannel chan = new TcpChannel(TcpPort, ClientProvider, SrvProvider); 
HttpChannel chan2 = new HttpChannel(HttpPort, null, SrvProvider); 

ChannelServices.RegisterChannel(chan); 
ChannelServices.RegisterChannel(chan2); 

RemotingConfiguration.RegisterWellKnownServiceType(typeof(ClassFactoryObj), "St", WellKnownObjectMode.SingleCall); 

客戶端通道註冊代碼為(不註冊一樣可以通信): 

//註冊通道 
TcpChannel TcpChan = new TcpChannel(); 
ChannelServices.RegisterChannel(TcpChan); 

//仿客戶端端激活方式 
PUBFac = (TPUBClassFactory)Activator.GetObject(typeof(TPUBClassFactory), "tcp://10.168.0.230:3119/St"); 
Stor = PUBFac.CreateStorageObj(); 
 回覆 引用   

#98樓 2006-11-18 16:51 Fox[匿名][未註冊用戶]

謝謝各位,這個問題已經解決了,主要是對Remoting的工作原理了解不是很深,不過今天還是突然想到了,呵呵  回覆 引用   

#99樓 2006-12-19 18:11 kylin[匿名][未註冊用戶]

好文章!學習過後,受益非淺!謝謝啊,呵呵,繼續努力,多出好文章!!  回覆 引用   

#100樓 2007-01-16 16:35 小魚[未註冊用戶]

好文章,非常感謝!  回覆 引用   

#101樓 2007-01-24 17:46 來賓[未註冊用戶]

DataSet已經序列化了,就不用序列化了; 
另外想問個問題: 
既然把Person類做成dll放在客戶端和服務器端,那還用remoting幹什麼,客戶斷直接調用自己的就可以了吧。
  回覆 引用   

#102樓 2007-01-26 14:24 天外飛仙[未註冊用戶]

看了幾遍,每次的收穫都不同啊!!  回覆 引用   

#103樓 2007-02-06 11:57 PIAO[未註冊用戶]

請樓主幫忙看下~~ 
服務啓動時做: 
private TcpChannel chan = new TcpChannel(3333); 
ChannelServices.RegisterChannel(chan, false); 
RemotingConfiguration.RegisterWellKnownServiceType( 
typeof(CacheClass.CacheClass), 
"Cache", WellKnownObjectMode.SingleCall); 
運行正常; 
可是改成以下配置並啓動時做: 
RemotingConfiguration.Configure("HaulageService.config",false); 服務就啓動不了了!!! 
<configuration> 
<system.runtime.remoting> 
<application name = "HaulageService"> 
<service> 
<wellknown mode="SingleCall" type="CacheClass.CacheClass" objectUri="Cache"/> 
</service> 
<channels> 
<channel ref="tcp" port="3333"/> 
</channels> 
</application> 
</system.runtime.remoting> 
</configuration> 
以上步驟跟樓主說的沒差別呀,配置文件也放在服務安裝的文件夾 
服務器端讀取配置文件還需要注意什麼呢?? 
爲什麼一改成配置,服務(Win服務)就啓動不了? 配置的問題? 
 回覆 引用   

#104樓 2007-02-10 13:36 一凡[未註冊用戶]

樓主你好:第一次訪問你的博客,看了你的文章之後,感覺受益非淺, 
但是有一點我不明白,我們不是可以直接用ADO.net可以訪問嗎!!幹嘛這麼麻煩呀
  回覆 引用   

#105樓 2007-03-05 10:12 zengchumin[未註冊用戶]

在網上其他地方也看過這篇文件 
樓主只是轉過來而已吧???
  回覆 引用   

#106樓[樓主2007-03-05 11:01 Bruce Zhang      

@zengchumin
氣煞我也,我自己的原創,居然有人誣衊我是轉載。拜託你在發表評論之前,先仔細看看。看清楚我發表此文的時間,看看大家的評論。

坦白說,我這篇文章在發表之後,可以說被轉載的次數太多了,我也懶得理睬。可如今卻因爲看了別處的轉載文字,反過來說我的原創是轉載,真是讓我哭笑不得啊。
 回覆 引用 查看   

#107樓 2007-03-22 15:10 eros      

Bruce,你好,最近項目中想使用remoting技術,正好拜讀了你的文章,讓我收穫頗豐,在此表示感謝。只是實踐中遇到一點問題,在文章第五部分 remoting基礎的補充的2遠程對象的元數據相關性中,關於客戶端激活中用WellKnown激活模式模擬客戶端激活的方法,我按照文中所述實驗了一下,寫了如下代碼: 

服務端遠程對象: 
//抽象工廠接口 
public interface IObjectFactory 

IRemoteObject CreateInstance(); 

//抽象工廠實現 
public class ObjectFactory : MarshalByRefObject, IObjectFactory 

public ObjectFactory() 



public IRemoteObject CreateInstance() 

return new RemoteObject(); 


//遠程對象接口 
public interface IRemoteObject 

string TempTransfer(float temp, string t); 


//遠程對象實現 
public class RemoteObject : MarshalByRefObject, IRemoteObject 

public string TempTransfer(float temp, string t) 

//...... 



註冊遠程對象 
TcpChannel tcpChannel = new TcpChannel(8080); 
ChannelServices.RegisterChannel(tcpChannel); 
RemotingConfiguration.RegisterWellKnownServiceType(typeof (ServerRemoteObject.ObjectFactory), "ServiceMessage", WellKnownObjectMode.SingleCall); 

客戶端 
tcpChannel = new TcpChannel(); 
ChannelServices.RegisterChannel(tcpChannel); 

objectFactory = (ServerRemoteObject.IObjectFactory)Activator.GetObject(typeof(ServerRemoteObject.IObjectFactory), "tcp://localhost:8080/ServiceMessage"); 

remoteObject = objectFactory.CreateInstance();* 

問題就出在標星號的這一句上,啓動服務端後在啓動客戶端,運行到這一句就產生異常“未處理的“System.InvalidCastException”類型的異常出現在 mscorlib.dll 中。其他信息: 返回參數具有無效的類型。”,百思不得其解,不知是哪出錯了呢?還望解答,先謝過。 
 回覆 引用 查看   

#108樓[樓主2007-03-22 16:02 Bruce Zhang      

@eros 
我沒有看到客戶端代碼中remoteObject實例的定義。此外,你的IRemoteObject接口的定義程序集被部署到了客戶端了嗎?
  回覆 引用 查看   

#109樓 2007-03-22 19:58 eros[未註冊用戶]

是否在服務端的遠程對象中提供工廠和遠程對象的接口以及它們的完整定義,而在客戶端的遠程對象中僅僅提供工廠和遠程對象的接口? 程序集在服務端和客戶端都分別部署了.如果方便的話我把完整的代碼發給你幫我看看,卡在這卻不知何解心裏真不好受.  回覆 引用   

#110樓 2007-04-23 15:57 Nina      

有沒remoting應用在web的啊。我現在看到的都是win方面的  回覆 引用 查看   

#111樓 2007-04-26 11:18 kevin[未註冊用戶]

好文章,受益非淺!  回覆 引用   

#112樓 2007-04-26 17:56 小楊[未註冊用戶]

文章很好啊,謝謝,還有關於這樣的資料嗎??我的E_mail是[email protected] 
 回覆 引用   

#113樓 2007-04-27 10:33 [email protected] [未註冊用戶]

今天才開始看remoting 感覺好抽象。 
要求能調用另一臺電腦上的程序或進程。 
請問有沒好的思路啊? 
或者有好的代碼等就感激不盡... 
代碼:[email protected] 並請留言說明下 謝謝
  回覆 引用   

#114樓 2007-05-10 15:43 在北京的湖南人      

一直不明白爲什麼需要把遠程對象程序集服務器端和客戶端都需要佈置?還是客戶端只需要類似ws那樣的代理類的具體遠程對象信息就可以了呀?那樣的話,那我們是不是可以根據接口來編程呢? 還請指教!  回覆 引用 查看   

#115樓 2007-05-14 23:44 fee[未註冊用戶]

good  回覆 引用   

#116樓 2007-06-11 10:34 andyli[未註冊用戶]

不錯,寫得非常的易懂而不切深奧,學習Ing。。。  回覆 引用   

#117樓 2007-07-10 16:42 一凡[未註冊用戶]

請問一下水晶報表怎麼調用 呀  回覆 引用   

#118樓 2007-07-31 13:48 Kylin[未註冊用戶]

您好,這是我第二次拜讀您的文章,第一次有些囫圇吞棗,第二次讀反而有了些問題,其他人也許也曾提到過,不知道現在是否已經解決了. 
RemotingConfiguration.RegisterWellKnownServiceType( 
typeof(ServerRemoteObject.ServerObject), 
"ServiceMessage",WellKnownObjectMode.SingleTon); 
其實就是多通道的問題,假如我採用服務端激活但是不管哪種方式,如果我創建了多個通道,那麼我的遠程對象在註冊的時候是如何確定註冊到哪個通道上呢,比如例子中的httpchanel和tcpchanel,那麼現在我有 
serverobj1,serverobj2,在將這兩個對象用RemotingConfiguration.RegisterWellKnownServiceType進行註冊的時候如何指定註冊到哪個通道(chanel)??,如果只有一個,我發先就是我所建立的第一個通道,如果沒有正如其他人所說的是machine.config當中的默認通道,但是現在有多個通道,不知道是如何解決的呢??看了上面的文章似乎沒有提及到服務器對象在多通道註冊的問題啊,謝謝!!!十分關注remoting的應用,因爲在delphi裏發現對於負載均衡的實現想比要簡單,但是在.net下實在沒有發現類似dcom這種可以做爲appserver的方式,故對次問題十分關注,期待您的答覆!!
  回覆 引用   

#119樓 2007-08-04 12:53 毛毛蟲[未註冊用戶]

你好,我正在學習remoting. 
用的《c#高級編程》,有個問題亟待解決。 
我在客戶端有多個對象的透明代理,這些對象實際上都在服務器端。我想完全像使用本地對象一樣在客戶端使用這些透明代理來使用服務器上的真正對象。 

當我在客戶端調用其中一個透明代理的方法,這個方法以另一個透明代理作爲參數。這樣編譯沒有問題,調試起來就報異常:Because of security restrictions, the type System.Runtime.Remoting.ObjRef cannot be accessed. 
我嘗試過把作爲參數的那個類去掉對System.MarshalByRefObject的繼承,而在其前聲明[Serializable],這樣沒有異常了,但是這樣該後,對象就被傳送到了客戶端,而不是對服務器端對象的引用了。這樣做會產生其他問題。 

這中問題您也應該遇到過吧,請問怎麼解決的? 

另外,我學習這個每次都要開3個vs,一個客戶端,一個服務器端,還有一個做dll程序集。每次dll文件一改動,客戶端和服務器端的引用都得刪了重新添加,很麻煩,而且最大的問題是做成帶來了dll文件的類不能調試。 
vs這麼強大,應該不會有這種問題吧,我該怎麼用vs做這個? 
 回覆 引用   

#120樓 2007-08-04 13:00 毛毛蟲[未註冊用戶]

而且客戶端和服務器端都同時引用相同的dll文件,這樣是不是太冗餘啊? 

在我的理解中,c#的remoting及其類似java的rmi。 
而java中的rmi在客戶端好像只放個類的接口就想了,真正實現只要放在服務器端。
  回覆 引用   

#121樓 2007-08-04 15:29 毛毛蟲[未註冊用戶]

知道怎麼在一個vs中用多個工程了。 
 回覆 引用   

#122樓 2007-08-14 15:47 lsl[未註冊用戶]

tcpChannel = new TcpChannel(); 
ChannelServices.RegisterChannel(tcpChannel); 

objectFactory = (ServerRemoteObject.IObjectFactory)Activator.GetObject(typeof(ServerRemoteObject.IObjectFactory), "tcp://localhost:8080/ServiceMessage"); 

remoteObject = objectFactory.CreateInstance(); 

這段代碼中註冊的通道沒有體現出被使用阿?請大哥解釋一下,這個註冊通道到底有用嗎?如果有用,是怎麼被使用的呢?謝謝。
  回覆 引用   

#123樓 2007-08-21 17:44 在線代理[未註冊用戶]

@ Nina

在web方面 ,估計都是用的web service了。何必這麼麻煩呢
  回覆 引用   

#124樓 2007-08-23 16:44 superstar      

請問我這怎麼錯誤的 
無法啓動服務。System.Security.SecurityException: 不允許所請求的註冊表訪問權。 
at Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable) 
at System.Diagnostics.EventLog.FindSourceRegistration(String source, String machineName, Boolean readOnly) 
at System.Diagnostics.EventLog.SourceExists(String source, String machineName) 
at System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData) 
at System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category) 
at System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID) 
at System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type) 
at ghyWindowsService.ghyService.OnStart(String[] args) in e:/test/ghyremotingtest/ghyremoting/ghywindowsservice/ghyservice.cs:line 140 
at System.ServiceProcess.ServiceBase.ServiceQueuedMainCallback(Object state) 

有關更多信息,請參閱在 http://go.microsoft.com/fwlink/events.asp 的幫助和支持中心。
  回覆 引用 查看   

#125樓 2007-12-13 01:00 kao720[未註冊用戶]

請問, 能不能走tcpChannel, Server使用Windows Form, Client使用Web Form.  回覆 引用   

#126樓 2007-12-14 15:16 強強      

從一開始就已經把你當作我的偶像,有太多的不明白想求教,但我的理解能力很明顯是有限的,只能通過時間來打通任督二脈. 
昨天終於鼓起勇氣在博客園註冊了個號,希望以後能得到您的指點.
  回覆 引用 查看   

#127樓 2008-06-03 20:29 xyzremoting[未註冊用戶]

請問一下樓主: 
在"Remoting基礎的補充"-->(2) 客戶端激活模式:中提到的 
a、利用WellKnown激活模式模擬客戶端激活模式 
如果在配置文件中同時配置服務器端和客戶端,由於接口不能被實例化,如何在客戶端引用呢: 
這是服務器端的配置: 
<system.runtime.remoting> 
<application name="ServerRemoting"> 
<service> 
<wellknown mode="SingleCall" type="ServerRemoteObject.ServerObjectFactory,RemoteObjectApplication" objectUri="ServiceMessage"/> 
</service> 
<channels> 
<channel ref="tcp" port="8080"/> 
<channel ref="http" port="8180"/> 
<channel ref="ipc" portName="RemotingIPCPort"/> 
</channels> 
</application> 
</system.runtime.remoting> 


這是客戶端端的配置: 
<system.runtime.remoting> 
<application name="ClientRemoting"> 
<client> 
<wellknown type="ServerRemoteObject.IServerObjectFactory,ServerWindowsApplication" uri="http://localhost:8080/IServerObjectFactory/ServiceMessage"/> 
</client> 
<channels> 
<channel ref="http" port="8180"/> 
</channels> 
</application> 
</system.runtime.remoting> 

客戶端引用: 
private void FrmMain_Load(object sender, EventArgs e) 

RemotingConfiguration.Configure("ClientWindowsApplication.exe.Config", false); 
IServerObjectFactory pServerObjectFactory = new *****();//有問題,由於客戶端沒有ServerObjectFactory類,而只有IServerObjectFactory類 

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