programming4us
programming4us
ENTERPRISE

Programming .NET Components : Building a Distributed Application (part 6) - Remote Callbacks

9/29/2012 2:06:36 AM

9. Remote Callbacks

Callbacks are just as useful in distributed applications as they are in local applications. A client can pass in as a method parameter a reference to a client-side marshal-by-reference object to be used by a remote object. A client can also provide a remote server with a delegate targeting a client-side method, so that the remote server can raise an event or simply call the client-side method. The difference in the case of remote callbacks is that the roles are reversed: the remote server object becomes the client, and the client (or client-side object) becomes the server. In fact, as far as the server is concerned, the client (or the target object) is essentially a client-activated object, because the server has no URI associated with the target object and cannot treat it as a well-known object.

To receive a remote callback, the client needs to register a port and a channel and have .NET listen on that port for remote callbacks. The problem is, how does the remote server know about that port? And for that matter, how does the remote server object know what URL to use to connect to the client? The answer is built into the remoting architecture. As mentioned already, whenever a reference to an object is marshaled across an app domain boundary, the object reference contains information about the location of the remote object and the channels the host has registered. The object reference is part of the proxy. When the server calls the proxy, the proxy knows where to marshal the call and which channel and port to use. In essence, this is also how delegates work across remoting. The client can create a delegate that targets a method on an object on the client's side and then add that delegate to a public delegate or an event maintained by the server object.

9.1. Registering callback channels

The client must register the channels on which it would like to receive remote callbacks. The client provides a port number to the channel constructor, just like the host application does:

    //Registering a channel with a specific port number on the client side,
    //to enable callbacks:
    IChannel channel = new TcpChannel(9005);
    ChannelServices.RegisterChannel(channel);

When the client invokes a call on the server, the client needs to know in advance which ports the host is listening on. This isn't the case when the server makes a callback to the client, because the proxy on the server side already knows the client-side port number. Consequently, the client doesn't really have to use a pre-designated port for the callback; any available port will do. To instruct .NET to select an available port automatically and listen on that port for callbacks, the client simply needs to register the channels with port 0:

    //Instructing .NET to select any available port on the client side
    IChannel channel = new TcpChannel(0);
    ChannelServices.RegisterChannel(channel);

Or, if you're using a configuration file:

    <channels>
       <channel ref="tcp"  port="0"/>
    </channels>

The client can also register an IPC channel for the callbacks:

    //Registering an IPC callback channel
    IChannel ipcChannel = new IpcChannel("MyCallback");
    ChannelServices.RegisterChannel(ipcChannel);

An interesting scenario is when the client registers multiple channels for callbacks. The client can assign a priority to each channel, using a named property called priority as part of a collection of named properties provided to the channel constructor (similar to explicitly specifying a formatter). The client can also assign priorities to channels in the configuration file:

    <application>
       <channels>
          <channel ref="tcp"   port="0" priority="1"/>
          <channel ref="http"  port="0" priority="2"/>
       </channels>
    </application>

The channels' priority information is captured by the object reference. The remote server tries to use the channels according to their priority levels. If the client registers multiple channels but doesn't assign priorities, the host selects a channel for the call.

9.2. Remote callbacks and type filtering

Every remoting channel has a filter associated with it. The filter controls the kinds of types the channel is willing to serialize across. In a distributed application the server is inherently more susceptible to attacks than the client, and it is particularly susceptible to being handed a harmful callback object by a malicious client. To protect against such attacks, the default level of the filter is set to Low. Low-level type filtering allows only a limited set of types to be passed in as parameters to remote methods as callback objects. Types allowed under low type filtering include remoting infrastructure types and simple compositions of reference types out of primitive types. Full type filtering allows all marshalable types to be passed in as callback objects. Setting the filter level to Full does not eliminate the security threat; it just makes it your explicit decision to take the risk, rather than Microsoft's decision.

To enable callbacks, the host must set its type-filtering level to Full. In most practical scenarios the client must elevate its type filtering to Full as well, unless only very simple callback objects are involved. Changing the type filtering is done on a per-formatter-per-channel basis. You can set the type filtering to Full both programmatically and administratively. To set it programmatically, supply a set of properties to the channel, and set the TypeFilterLevel property of the channel formatter to Full. TypeFilterLevel is of the enum type TypeFilterLevel, defined as:

    public enum TypeFilterLevel
    {
        Full,
        Low
    }

For example, here is how you programmatically set the type filtering of the binary formatter using a TCP channel on the host side:

    BinaryServerFormatterSinkProvider formatter;
    formatter = new BinaryServerFormatterSinkProvider( );
    formatter.TypeFilterLevel = TypeFilterLevel.Full;

    IDictionary channelProperties = new Hashtable( );
    channelProperties["name"] = "FullHostTCPChannel";
    channelProperties["port"] = 8005;

    IChannel channel = new TcpChannel(channelProperties,null,formatter);
    ChannelServices.RegisterChannel(channel);

When using an IPC callback channel, instead of port number you will need to provide the pipe's name in the portName property:

    channelProperties["portName"] = "MyClientCallback";

To set the filter level administratively, use the serverProviders tag under each channel, and set the filter level for each formatter. For example, to set type filtering to Full for a TCP channel on the host side, provide this configuration file:

    <channels>
       <channel ref="tcp" port="8005">
          <serverProviders>
             <formatter ref="soap"   typeFilterLevel="Full"/>
             <formatter ref="binary" typeFilterLevel="Full"/>
          </serverProviders>
       </channel>
    </channels> 


Here is the matching client-side configuration file:

    <channels>
       <channel ref="tcp" port="0">
          <serverProviders>
             <formatter ref="soap"   typeFilterLevel="Full"/>
             <formatter ref="binary" typeFilterLevel="Full"/>
          </serverProviders>
       </channel>
    </channels>

Note the use of port 0 on the client side, which tells .NET to automatically select any available port for the incoming callback.

9.3. Remote callbacks and metadata

Another side effect of reversing the roles of the client and server when dealing with remote callbacks has to do with the client's metadata. At runtime, the host must be able to build a proxy to the client-side object, and therefore the host needs to have access to the object's metadata. As a result, you typically need to package the client-side callback objects (or event subscribers) in class libraries and have the host reference those assemblies. 

9.4. Remote callbacks and error handling

On top of the usual things that can go wrong when invoking a callback, with remote callbacks there is also the potential for network problems and other wire-related issues. This is a particular concern in the case of remote event publishers, since they have to try to reach every subscriber and may have to wait a considerable amount of time for each because of network latency. However, because a publisher/subscriber relationship is by its very nature a looser relationship than that of a client and server, often the publisher doesn't need to concern itself with whether the subscriber managed to process the event successfully, or even if the event was delivered at all. If that is the case with your application, it's better if you don't publish events simply by calling the delegate.

There is something you can do on the subscriber's side to make the life of the remote publisher easier. The OneWay attribute, defined in the System.Runtime.Remoting.Messaging, makes any remote method call a fire-and-forget asynchronous call. If you designate a subscriber's event-handling method as a one-way method, the remoting infrastructure only dispatches the callback and doesn't wait for a reply or for completion. As a result, even if you publish an event by calling a delegate directly, the event publishing will be asynchronous and concurrent: it's asynchronous because the publisher doesn't wait for the subscribers to process the event, and it's concurrent because every remote subscriber is served on an impendent worker thread (remote calls use threads from the thread pool). In addition, any errors on the subscriber's side don't propagate to the publisher's side, so the publisher doesn't need to program to catch and handle exceptions raised by the event subscribers.

Because static members and methods aren't remotable (no object reference is possible), you can't subscribe to a remote static event, and you can't provide a static method as a target for a remote publisher. You can only pass a remote object a delegate targeting an instance method on the client side. If you pass a remote publisher a delegate targeting a static method, the event is delivered to a static method on the remote host side.


9.5. Remote callback example

Example 4 shows a publisher firing events on a remote subscriber. These projects are a variation of the projects presented in Example 3. The host is identical to the one in Example 3; the only changes are in the host configuration file. The host exposes the type RemoteServer.MyPublisher as a client-activated object and as a server-activated object. The host also elevates type filtering to Full on its channels. The ServerAssembly class library contains both the subscriber and the publisher classes. Recall that this is required so that both the client and the host can gain access to these types' metadata. Note that both the publisher and the subscriber are derived from MarshalByRefObject. The publisher uses GenericEventHandler by aliasing it to NumberChangedEventHandler:

    using NumberChangedEventHandler = GenericEventHandler<int>;

The publisher publishes to the subscribers in the FireEvent( ) method using EventsHelper. The subscriber is the MySubscriber class. The subscriber's event-handling method is OnNumberChanged( ), which pops up a message box with the value of the event's argument:

						
    [OneWay]
    public void OnNumberChanged(int number)
    {
       MessageBox.Show("New Value: " + number);
    }

The interesting part of OnNumberChanged( ) is that it's decorated with the OneWay attribute. As a result, the publisher's FireEvent( ) method actually fires the event asynchronously and concurrently to the various subscribers. The client configuration file is the same as in Example 3, except this time the client registers the port 0 with the channel, which allows the client to receive remote callbacks. Interestingly enough, the subscriber is simple enough to pass on the client channel even with client-side type filtering set to Low. The client configuration file registers the publisher as a remote object. The client creates a local instance of the subscriber and a remote instance of the publisher, and saves them as class members. The client is a Windows Forms dialog. The dialog allows the user to subscribe to or unsubscribe from the publisher, and to fire the event. The dialog reflects the event's argument in a text box. Subscribing to and unsubscribing from the event are done using the conventional += and -= operators, as in the local case.

Example 4. Remote events
/////////////  RemoteServerHost.exe.config : the host configuration file  ////////
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.runtime.remoting>
    <application>
      <service>
         <activated  type="RemoteServer.MyPublisher,ServerAssembly"/>
         <wellknown  type="RemoteServer.MyPublisher,ServerAssembly"
                     mode="SingleCall" objectUri="MyRemotePublisher"/>
      </service>
      <channels>
         <channel ref="tcp" port="8005">
            <serverProviders>
               <formatter ref="soap"   typeFilterLevel="Full"/>
               <formatter ref="binary" typeFilterLevel="Full"/>
            </serverProviders>
         </channel>
         <channel ref="http" port="8006">
            <serverProviders>
               <formatter ref="soap"   typeFilterLevel="Full"/>
               <formatter ref="binary" typeFilterLevel="Full"/>
               </serverProviders>
         </channel>
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>
/////////////////////   ServerAssembly class library  ////////////////////////////
using NumberChangedEventHandler = GenericEventHandler<int>;

namespace RemoteServer
{
   public class MyPublisher : MarshalByRefObject
   {
      public event NumberChangedEventHandler NumberChanged;
      public void FireEvent(int number)
      {
         EventsHelper(NumberChanged,number);
      }
   }
   public class MySubscriber : MarshalByRefObject
   {
      [OneWay]
      public void OnNumberChanged(int number)
      {
         MessageBox.Show("New Value: " + number);
      }
   }
}
////////////////  Client.exe.config: the client configuration file  //////////////
<?xml version="1.0" encoding="utf-8"?>
<configuration>
   <system.runtime.remoting>
      <application>
         <client url="tcp://localhost:8005">
            <activated  type="RemoteServer.MyPublisher,ServerAssembly"/>
         </client>
         <channels>
            <channel ref="tcp"  port="0"/>
         </channels>
      </application>
   </system.runtime.remoting>
</configuration>
///////////////////////////  Client EXE assembly  ////////////////////////////////
using RemoteServer;
 

partial class SubscriberForm : Form
{
   Button m_FireButton;
   Button m_SubscribeButton;
   Button m_UnsubscribeButton;
   TextBox m_NumberValue;
  
   MyPublisher  m_Publisher;
   MySubscriber m_Subscriber;
  
   public SubscriberForm( )
   {
      InitializeComponent( );
  
      m_Publisher  = new MyPublisher( );
      m_Subscriber = new MySubscriber( );
   }
   void InitializeComponent( )
   {...}
 
   static void Main( )
   {
      RemotingConfigurationEx.Configure( );
  
      Application.Run(new SubscriberForm( ));
   }
   void OnFire(object sender,EventArgs e)
   {
      int number = Convert.ToInt32(m_NumberValue.Text);
      m_Publisher.FireEvent(number);
   }
   void OnUnsubscribe(object sender,EventArgs e)
   {
      m_Publisher.NumberChanged -= m_Subscriber.OnNumberChanged;    
   }
   void OnSubscribe(object sender,EventArgs e)
   {
      m_Publisher.NumberChanged += m_Subscriber.OnNumberChanged;
   }
}

Other  
 
Video
PS4 game trailer XBox One game trailer
WiiU game trailer 3ds game trailer
Top 10 Video Game
-   Renoir [PS4/XOne/PC] Kickstarter Trailer
-   Poly Bridge [PC] Early Access Trailer
-   Renoir [PS4/XOne/PC] Gameplay Explanation Trailer
-   Renoir [PS4/XOne/PC] More About Trailer
-   King's Quest: A Knight to Remember [PS3/PS4/X360/XOne/PC] Complete Collection Trailer
-   Samurai Warriors Chronicles 3 | Announcement Trailer
-   FIFA 16 | No Touch Dribbling with Lionel Messi
-   Why We're Cautiously Optimistic For The Final Fantasy VII Remake
-   Civilization: Beyond Earth – Rising Tide [PC] E3 Gameplay Walkthrough
-   Why We're Excited For the FFVII Remake
-   Mortal Kombat X | Predator Brutality
-   Mortal Kombat X | Predator Fatality
-   Poly Bridge [PC] Early Access Trailer
-   Silence: The Whispered World 2 [PS4/XOne/PC] Cinematic Trailer
-   Devilian [PC] Debut Trailer
Game of War | Kate Upton Commercial
programming4us
 
 
programming4us