The Common Language Runtime Remoting Architecture

I l @ ve RuBoard

The Remoting architecture of the common language runtime allows you to make remote method calls and access remote objects. Before we look at how remoting works, we first need to consider exactly what .NET means by a remote object .

Remote Objects

In the traditional programming model, a remote object is an object that's running in a different process than the one invoking it. The two processes can be on the same machine or on different machines. The RPC infrastructure provided by many environments hides the fact that an object is remote through the use of proxies and stubs (as described in Chapter 1). But .NET uses application domains to isolate applications, and several application domains might be contained in the same process. In .NET, any method call that crosses an application domain boundary is considered an RPC.

Another complication is the context , which is used to group objects that share certain common properties ”synchronization attributes, transactions, and thread affinity, for example. Chapter 8 discussed how objects can be marked as being context-bound or context-agile. A context-agile object can be accessed freely from any other context in the same application domain. Context-bound objects can be accessed directly by other objects in the same context, but objects in other contexts must use proxies to access a context-bound object ”even if they reside in the same application domain.

The designers at Microsoft decided to build a generic remoting architecture that employs the same basic principles whether a remote method being invoked is in the same application domain but different context as the caller, in a different application domain in the same process, in a different process, or on a completely different machine. The architecture uses pluggable, customizable components that can be optimized for these scenarios.

As we discussed in 'sc, remote objects come in two varieties: those that can change their state and those that are immutable. You might argue that all objects can change their state, but sometimes what you want is a copy of an object for analysis or reporting purposes. For example, an automated teller machine (ATM) might be asked to display the details of recent transactions applied to a customer's bank account, and the ATM will not modify this data (you hope!). Once the copy has been created, the original data can change to its heart's content (a pending account transaction can be cancelled, for example), but these changes will not be reflected in the copy. A new, updated copy would have to be generated for you to see the most recent version of the data.

Similarly, if the local copy somehow changes, the original data will not change (just as writing "deposit $1,000,000" on your bank statement will not suddenly add $1,000,000 to your account, no matter how much you'd like it to). In the .NET Remoting architecture, local object copies are called marshal- by-value objects . They represent a snapshot of the state of an object at a given point in time.

Note

Copies of objects are useful, but their usefulness depends on how they are used. Some design patterns (such as those used when you program with ADO.NET DataSet objects) use what are essentially marshal-by-value objects to perform batch updates. Chapter 7 explained how an ADO.NET DataSet can contain several rows of data retrieved from a database and how this data can be modified by an application. When the application has finished making the changes, it can send the updates back to the database en masse . This approach can be efficient in terms of network bandwidth, but the logic involved when you propagate the updates to the database can be tortuous because you have to take into account that another application might have already modified the data. You should not blindly overwrite such updates.


As an alternative to the marshal-by-value object, you can use the marshal-by-reference object . A marshal-by-reference object is an object whose value can be dynamic, and all applications that use it will see (or be able to make) changes to its state. Applications do not hold a copy of the object; instead, they retain a reference to the original object living in its own application domain. Every time an application needs to modify the object, it must access it through the reference. A proxy object that hides the underlying mechanics usually provides the reference.

You can compare Figures 11-1 and 11-2 to see the differences between marshal-by-value and marshal-by-reference objects. In Figure 11-1, an object created in the server application domain is accessed using a marshal-by-value object in the client application domain. This causes a complete copy of the object to be instantiated in the client, including all of its fields. If the value of field1 changes in the object in the server application domain, the value of field1 will remain unchanged (at 99) in the object in the client application domain.

Figure 11-1. A marshal-by-value object

In Figure 11-2, a similar object in the server application domain is accessed by using a marshal-by-reference object in the client application domain. This time, the object is not copied to the client application domain; instead, a proxy object is created that can be used to access the original object in the server application domain. If the value of field1 changes in the object in the server application domain, this new value will be visible through the proxy in the client application domain.

Figure 11-2. A marshal-by-reference object

Performing updates by using reference objects is less involved (at the application programming level, at least) than updating by using value objects, but the drawback is that every access to a reference object involves using the proxy, which can in turn require transmitting data over a network (depending on the relative location of the application domains involved). Even accessing other objects in the same application domain can require a proxy if the objects in question occupy different contexts, although the .NET infrastructure performs a number of optimizations to minimize the overhead in this situation.

Marshal-by-value objects must be serializable ”that is, they must be marked with the SerializableAttribute , and they can implement the ISerializable interface. (See Chapter 10 for details.) Marshal-by-reference objects extend the System.MarshalByRefObject class, which is an abstract class that provides default implementations of some of the mechanisms required for managing remote references. (The nearest functional equivalent in the JDK is the java.rmi.server.RemoteObject class.)

Marshal-by-reference objects are a common feature in .NET Remoting. The sample file CakeUtils.jsl (in the CakeUtils project) shows a variation on the CakeInfo class used in earlier chapters. The class exposes the single method, FeedsHowMany , which establishes how many people a cake of given dimensions, shape, and filling will serve. The class extends the MarshalByRefObject class, allowing it to be remoted .

If you download and examine this sample, you'll notice that the FeedsHowMany method is no longer static. This is because static methods behave differently from instance methods in a remoting environment. We'll explore the reasons for this later in the chapter. The method also prints a message ("Calculating ") on screen while it's running so you can observe where objects of this type are actually instantiated.

The .NET Remoting Model

Remote objects require a context and an application domain to execute in. You can create your own server application to act as a host, or you can use IIS. For the time being, we'll look at building your own server application and consider IIS hosting later.

A host server application registers a channel for listening to incoming client requests . A channel transports messages between the server application and clients . The .NET Framework Class Library has two basic types of channel available, but you can also define your own custom channels. The two built-in channels are System.Runtime.Remoting.Channels.Tcp.TcpChannel, which uses TCP for transmitting and receiving messages, and System.Runtime.Remoting.Channels.Http.HttpChannel , which is based on HTTP. You must add a reference to the assembly System.Runtime.Remoting.dll to use the objects of the System.Runtime.Remoting namespace (and its child namespaces).

By default, TCP channels package messages and data using binary encoding and HTTP channels use SOAP (but you can override this behavior). These channels are two-way ”you can use them to both send and receive messages. Specialized versions are also available: TcpClientChannel , TcpServerChannel , HttpClientChannel , and HttpServerChannel . These are intended for use only at the client or the server end, as their names imply.

The server application registers a channel with the static RegisterChannel method of the System.Runtime.Remoting.Channels.ChannelServices class. The following code fragment creates and registers a TCP channel that listens to port 6000. Note that you cannot create more than one TCP channel using the same port on the same computer at the same time.

 importSystem.Runtime.Remoting.Channels.*; importSystem.Runtime.Remoting.Channels.Tcp*; ChannelServices.RegisterChannel(newTcpChannel(6000)); 

The next step depends on whether the objects being remoted are instantiated by the server or by the client. (The reasons for choosing one option over the other will be discussed shortly.) In the case of server-activated objects, the server application must register the type or types of objects that it can create and manage, together with an activation mode. We'll also look at activation modes in more detail later in the chapter. For now, suffice it to say that when you use server-activated objects, the server governs the lifetimes of those objects and multiple clients can share such objects; client-activated objects, in contrast, are managed by a single client and are private to that client ”they cannot be shared.

The server registers object types using the static RegisterWellKnownServiceType method of the System.Runtime.Remoting.RemotingConfiguration class. This method also expects a unique URI to identify the object type. Any client that knows the URI can instantiate objects of the specified class on the server. (This is what we mean when we say that a service type is well-known .) Internally, the remoting infrastructure maintains a table of types, URIs, and activation modes. Each type that is registered is added to this table. This table is consulted by the remoting infrastructure when a client attempts to gain access to a well-known object.

The following code registers the CakeInfo class of the CakeUtils namespace. This namespace is implemented in the CakeUtils.dll assembly. The URI specified is CakeInfo.rem ; it is customary to attach the suffix rem to remote object URIs. The final parameter is the activation mode. You should specify one of the values from the System.Runtime.Remoting.WellKnownObjectMode enumeration. (More on this later.)

 importSystem.Runtime.Remoting.*; RemotingConfiguration.RegisterWellKnownServiceType(Type.GetType("CakeUtils.CakeInfo,CakeUtils"),  "CakeInfo.rem",WellKnownObjectMode.Singleton); 

If the objects are client-activated, the client takes responsibility for creating and managing them and the server simply provides an environment in which they can execute. The types still have to be registered with the server, but the server does not publish a URI (the service types available are not well-known ”a client must know which classes can be activated on the server) or specify an activation mode for them. The server can invoke the RemotingConfiguration.RegisterActivatedServiceType method to perform this task:

 RemotingConfiguration.RegisterActivatedServiceType(Type.GetType("CakeUtils.CakeInfo,CakeUtils")); 

Note

You can register the same class as a well-known server-activated type and a client-activated type on the same server.


That is all the server host application needs to do. However, if the server terminates, the registered channel and service type information will be lost and the client will not be able to use it to activate remote objects. The main thread of a typical server application will therefore suspend itself quietly in some way until an administrator shuts it down. A completed host server application that implements server-activation is shown in the CakeServer.jsl sample file in the ServerActivatedTCPCakeSizeServer project.

A client application that wants to access a remote object must also create a channel and register it. The type of the channel should be the same as that used by the server. The client does not need to specify a port number for the TCP channel. Behind the scenes, the .NET Remoting infrastructure will create a client socket and dynamically allocate it a port number. If you do specify a port, you should select one that is not already in use; otherwise , an exception will be thrown by the Remoting infrastructure:

 ChannelServices.RegisterChannel(newTcpChannel()); 

The client activates the remote object by using one of the static methods of the System.Activator class. The GetObject method is used to create a proxy that references a server-activated remote object. You specify the URI that was created when the type was registered on the server. The proxy itself is actually constructed on the client using the type information specified by the first parameter of the GetObject method, and it does not need to contact the server at this time, so no network traffic will be generated. The validity of the object URI is not checked until the first method call to the remote object occurs. Also, note that the proxy type returned by GetObject is Object , so you should cast it as appropriate.

 CakeInfoci= (CakeInfo)Activator.GetObject(Type.GetType("CakeUtils.cakeInfo, CakeUtils"), "tcp://localhost:6000/CakeInfo.rem"); 

Note

If the server is running on a different computer than the client, you should replace localhost with the name of the server computer.


You use the GetObject method to access a server-activated object. On the other hand, the CreateInstance method of the Activator class constructs a new instance of a client-activated object on a remote server. CreateInstance is heavily overloaded. The version shown below specifies the type of the remote object, an array of constructor values, and an array of activation attributes. The array of constructor values is null in this case, which will cause the default constructor (with no parameters) to be invoked when the object is created on the server. If you supply real values in this array, the best-matching constructor that takes parameters of the same types as your list will be used instead. (For example, if you populate the array with an int and a S tring , the server will look for a constructor that takes an int and a String , in that order.) If no usable constructor can be found, a MissingMethodException will be thrown. The array of activation attributes should include a UrlAttribute specifying the address of the remote server. The UrlAttribute class is located in the System.Runtime.Remoting.Activation namespace.

 CakeInfoci=(CakeInfo)Activator.CreateInstance(Type.GetType("CakeUtils.CakeInfo,CakeUtils"),null, newSystem.Object[]{newUrlAttribute("tcp://localhost:6000")}); 

The CreateInstance method contacts the specified server, instantiates an object of the appropriate type, and returns a proxy that can be used to access that object. Once again, the value returned must be cast.

Whether GetObject or CreateInstance is used, the result is a proxy that exposes the same methods as the remote object. You can invoke these methods on the proxy, and they will be executed on the remote object. The listing in the CakeClient.jsl sample file in the ServerActivatedTCPCakeClient project is a client application that invokes a server-activated CakeInfo object. It calls the Feeds ­HowMany method using the proxy and prints the result.

You can verify that the CakeInfo object is executing remotely from the client by observing where the "Calculating " message printed by the CakeInfo object is output ”it should be displayed on the host server, as shown in Figure 11-3.

Figure 11-3. The console screen showing the host server output

Remote Exceptions

If the host application throws an exception while activating an object or executing one of its methods on behalf of a client, the client application will be notified of the exception and will also be supplied with a stack trace indicating where the error occurred on the server. This stack trace will often be lengthy, but it can make for interesting reading if you're keen to follow how messages are propagated from a server back to the client and see the various parts of the remoting infrastructure involved.

The stack dump shown below is an example of the error output of the client. The error was caused by the server not being able to locate the assembly containing the remote object class. To achieve this, we deliberately deleted the copy of the CakeUtils assembly in the ConfigTCPCakeSizeServer\bin\Debug folder (you'll see more of this example later) after compiling the remote server class. If you want to try this yourself, you must then run the server from the command line, because if you execute it in Visual Studio .NET, the IDE will notice that the assembly has been deleted and copy it again.

 Exceptioncommunicatingwithserver:System.IO.FileNotFoundException:Fileor assemblynameCakeUtils,oroneofitsdependencies,wasnotfound. Filename: "CakeUtils" Serverstacktrace: atSystem.Reflection .Assembly.nLoad(AssemblyNamefileName,StringcodeBase, BooleanisStringized,EvidenceassemblySecurity, BooleanthrowOnFileNotFound,AssemblylocationHint, StackCrawlMark&stackMark) atSystem.Reflection .Assembly.InternalLoad(AssemblyNameassemblyRef, Booleanstringized,EvidenceassemblySecurity, StackCrawlMark&stackMark) atSystem.Reflection.Assembly.InternalLoad(StringassemblyString,Evidence assemblySecurity,StackCrawlMark&stackMark) atSystem.Reflection.Assembly.Load(StringassemblyString) atSystem.Runtime.Remoting.RemotingConfigInfo.LoadType(StringtypeName, StringassemblyName) atSystem.Runtime.Remoting .RemotingConfigInfo.StartupWellKnownObject(StringasmName, StringsvrTypeName,StringURI,WellKnownObjectModemode, BooleanfReplace) atSystem.Runtime.Remoting.RemotingConfigInfo.StartupWellKnownObject(StringURI) atSystem.Runtime.Remoting.RemotingConfigHandler.CreateWellKnownObject(Stringuri) atSystem.Runtime.Remoting.IdentityHolder.CasualResolveIdentity(Stringuri) atSystem.Runtime.Remoting.Messaging.MethodCall.ResolveType() atSystem.Runtime.Remoting.Messaging.MethodCall .ResolveMethod(BooleanbThrowIfNotResolved) atSystem.Runtime.Remoting.Messaging .MethodCall..ctor(ObjecthandlerObject,BinaryMethodCallMessagesmuggledMsg) atSystem.Runtime.Serialization.Formatters .Binary.BinaryMethodCall.ReadArray(Object[]callA,ObjecthandlerObject) atSystem.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandlerhandler,__BinaryParserserParser,BooleanfCheck, IMethodCallMessagemethodCallMessage) atSystem.Runtime.Serialization.Formatters.Binary.BinaryFormatter. Deserialize(StreamserializationStream,HeaderHandlerhandler, BooleanfCheck,IMethodCallMessagemethodCallMessage) atSystem.Runtime.Remoting.Channels.CoreChannel.DeserializeBinaryRequest Message(StringobjectUri,StreaminputStream,BooleanbStrictBinding) atSystem.Runtime.Remoting.Channels.BinaryServerFormatterSink .ProcessMessage(IServerChannelSinkStacksinkStack, IMessagerequestMsg,ITransportHeadersrequestHeaders, StreamrequestStream,IMessage&responseMsg, ITransportHeaders&responseHeaders,Stream&responseStream) Exceptionrethrownat[0]: atSystem.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg,IMessageretMsg) atSystem.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData,Int32type) atCakeUtils.CakeInfo.FeedsHowMany(Int16diameter,Int16shape,Int16 filling) atCakeSizeClient.CakeClient.main(String[]args)inC:\Temp\Chapter11\ ConfigTCPCakeClient\CakeClient.jsl:line25 Fusionlogfollows: ===Pre-bindstateinformation=== LOG:DisplayName=CakeUtils (Partial) LOG:Appbase=C:\Temp\Chapter11\ConfigTCPCakeSizeServer\bin\Debug\ LOG:InitialPrivatePath=NULL Callingassembly:(Unknown). === LOG:Policynotbeingappliedtoreferenceatthistime(private,custom, partial,orlocation-basedassemblybind). LOG:Post-policyreference:CakeUtils LOG:AttemptingdownloadofnewURL file:///C:/Temp/Chapter11/ConfigTCPCakeSizeServer/bin/Debug/CakeUtils.DLL. LOG:AttemptingdownloadofnewURL file:///C:/Temp/Chapter11/ConfigTCPCakeSizeServer/bin/Debug/CakeUtils/CakeUtils.DLL. LOG:AttemptingdownloadofnewURL file:///C:/Temp/Chapter11/ConfigTCPCakeSizeServer/bin/Debug/CakeUtils.EXE. LOG:AttemptingdownloadofnewURL file:///C:/Temp/Chapter11/ConfigTCPCakeSizeServer/bin/Debug/CakeUtils/CakeUtils.EXE. 

The ObjRef Object and Proxies

The .NET Remoting architecture uses a pair of proxies on the client called the TransparentProxy and the RealProxy , each of which has a well-defined purpose. The reference returned to the client application when an object is activated is actually a TransparentProxy object. The TransparentProxy packages up a method call and its parameters into an IMessage object (an object that implements the System.Runtime.Remoting.Messaging.IMessage interface and is essentially a serializable representation of the method call) and passes it to the RealProxy object, which handles the actual marshaling and transmission of data. The proxy objects are constructed by the GetObject or CreateInstance method, using the type information supplied as parameters to these methods.

In the case of a well-known server-activated object, the client call to the GetObject method creates the proxy objects without needing to contact the server. However, if the remote object is client-activated, the CreateInstance method executed by the client will result in a message being transmitted to the server, which will then create and activate an object of the required type ( assuming the server has registered the requested type). Depending on any context attributes specified in the metadata of the object type, the server might also need to create a new context to house the object. The remoting infrastructure on the server will then create an ObjRef object; this is a serializable representation of a remote object reference that can be passed from one context and application domain to another. An ObjRef contains the information needed to locate a remote object and create the appropriate proxy objects. The ObjRef is returned from the server to the CreateInstance method in the client, which uses it to create the RealProxy and TransparentProxy objects for communicating with the server before handing the TransparentProxy back to the client as the return value from CreateInstance .

The TransparentProxy object contains a list of all the methods and interfaces implemented by the remote object. When a method call is made through the TransparentProxy , the runtime intercepts and examines the call to determine whether it is valid and how remote the referenced object really is. If the remote object is in the same application domain and context as the client, it is not really remote, and the TransparentProxy will route the request directly to a local instance of the object. If the remote object is in the same application domain as the client but in a different context, the TransparentProxy will determine whether the remote object is context-agile. A context-agile remote object residing in the same application domain as the client can also be accessed directly. (By default, classes that directly inherit from MarshalByRefObject are context-agile.)

A remote object class can extend the System.ContextBoundObject class (which itself inherits from MarshalByRefObject ), but such classes are not context-agile. Neither are classes whose metadata restricts them to a particular context, such as those that are tagged with SynchronizationAttribute (which was described in Chapter 8). Method calls to these objects will be forwarded on to the RealProxy object, as will calls to objects that are located in a different application domain. The TransparentProxy packages the method invocation, with its parameters, into an IMessage object and calls the Invoke method on the RealProxy object, passing this message as a parameter. The RealProxy does the real work of arranging for the data to be transported to its ultimate destination, which might be another context in the same application domain or in a different application domain, possibly over the network.

Messages, Channels, and Channel Sinks

Besides the proxies and the ObjRef , other items are involved in remoting that are mainly concerned with the physical transportation of data between the client and server application domains. Once the RealProxy object has been created, its task is to arrange for the requested method to be called on the remote object, using the information supplied by the IMessage object passed as a parameter to its Invoke method. (The TransparentProxy actually creates a System.Runtime.Remoting.Messaging.MethodCall object and passes it to the RealProxy ; the MethodCall class implements the IMessage interface.) The RealProxy will route the message over the channel to the server, but before the message is transmitted it must be transformed into a format that the channel can handle.

Message transformation is achieved using channel sinks . A channel sink is a class that implements the IClientChannelSink or IServerChannelSink interface, depending on whether the sink is used to send or receive messages. The purpose of a channel sink is to examine the message object passed in, process it, and then forward the result to another sink for further processing or transmission. Channel sinks are linked into sink chains. The first sink in the sink chain on the client side is often the formatter sink. It is installed by the channel, and the type of the sink is determined by the type of the channel.

The TcpChannel class uses a BinaryClientFormatterSink object, which serializes data into a binary format using a BinaryFormatter object, as described in Chapter 10. The HttpChannel class employs a SoapClientFormatterSink object, which serializes data into XML using a SoapFormatter object. You can create your own custom sinks and arrange for them to be inserted into the sink chain, usually before the formatter sink (it is easier to manipulate the message before it is formatted), although you can insert them after the formatter sink if needed. Later in the chapter, we'll look at some scenarios showing why you might want to do this and how to customize the remoting infrastructure.

After the message has been formatted and processed by various channel sinks, it can be passed to a transport sink . The transport sink is the last sink in the chain on the client side, and it is responsible for physically sending the transformed data to the appropriate endpoint on the server using the protocol indicated by the channel (TCP or HTTP). The transport sink is an integral part of the channel object and cannot be extended or replaced (unless you create your own custom channel class). On the server side, the transport sink is the first in the chain, and it is responsible for receiving the message sent by the client.

Sinks are chained together on the server side much as they are on the client, and again you can inject your own custom sinks into the chain. The sink chain will also include a formatter sink, which is responsible for deserializing the message back into a recognizable IMessage object. The formatter sink will usually be a BinaryServerFormatterSink object or a SoapServerFormatterSink object, depending on the type of the channel.

The final sink on the server side is called the stack builder sink . (Strictly speaking, the stack builder sink is not a channel sink, and there might be other objects between the channel sink chain and the stack builder, as described later in this chapter.) The stack builder sink takes the deserialized IMessage object and uses it to construct a stack frame for the target method of the remote object. It then invokes the target method, and the remote object reads its parameters from the stack, just as it would if it were the subject of a local method call. (If you're familiar with CORBA or RMI, you can think of the stack builder sink as the server stub or skeleton.)

You cannot override or extend the stack builder sink. Any return values and output parameters from the method are intercepted by the stack builder sink, which sends the results back through the server sink chain in reverse order, serializing them, and eventually submitting the resulting data to the transport sink at the end of the chain. The message is transmitted to the transport sink on the client, which passes the data through the client sink chain, deserializing the message, and returning it to the RealProxy object. The RealProxy object passes the results to the TransparentProxy , which returns them to the client object.

Figure 11-4 shows the components of the basic remoting infrastructure.

Figure 11-4. The basic remoting infrastructure

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net