|
|
Title |
Remoting callbacks and events in .NET 2.0
|
Summary |
This is the simplest application I could create to demonstrate remoting events. The client can call the server as in simple remoting or web services. However the magic of this article is in the servers ability to call the client. |
Contributor |
John McTainsh
|
Published |
20-Oct-2006 |
Last updated |
20-Oct-2006 |
|
|
Download
Sample project files - 18 Kb
Introduction
A simple example
Conclusion
Introduction
This article is part two on .NET remoting. Part 1 is somewhere on this site and is a gentle introduction
into .NET remoting. This article we will cover how to make the server callback into the client. We will
do this in such a way the the server can (at any time) call the server. I wrote this article because there is
too many big complex examples on the web and no little simple ones. This is the smallest example I could make. I
hope you find it useful.
Using remoting or even web services, you can make called from the clien to the server. However only in remoting
is it possible for the server to fire an event back into the server. I believe web services will be upgraded to
enable events or callbacks in the not too distant future but at this point in time, .NET 2.0 does not support events
or callbacks for web services. So we will be using remoting.
For this example I have created a simple client and server. objects in the project are.
- JrmServer.exe. Server displaying client calls and firing events back to the client when the user enters text.
- JrmClient.exe. Which connects to the server, continously send numbers to the server and displays messages fired from the server.
- BidirectionShare.dll. Is a shared object used to define the interface between client and server.
We could make this as 2 executables without the dll but it is more like a real life design if we keep the interface in
a shared d\DLL. The below is what the applicatrion look like running. You can have as many clients as you wish.
A simple example
I will show all the code here and explain how to add it to your own project. Please read
my comments in the code as these are quite detailed. we will start with the shared code.
Create the shared DLL
Create a new "Class library" project in C#. and paste the following code into the auto generated program.cs.
using System;
using System.Runtime.Remoting;
namespace BidirectionalShare
{
/// <summary>
/// Delegate defines the metho call fromthe server to the client
/// </summary>
/// <param name="s">Pass a string for testing</param>
public delegate void NotifyCallback( string s );
/// <summary>
/// Defines server interface which will be deployed on every client
/// </summary>
public interface ICallsToServer
{
/// <summary>
/// Function to call the server from the client
/// </summary>
/// <param name="n">Some number</param>
/// <returns>Some interesting text</returns>
string SomeSimpleFunction( int n);
/// <summary>
/// Add or remove callback destinations on the client
/// </summary>
event NotifyCallback Notify;
}
/// <summary>
/// This class is used by client to provide delegates to the server that will
/// fire events back through these delegates. Overriding OnServerEvent to capture
/// the callback from the server
/// </summary>
public abstract class NotifyCallbackSink : MarshalByRefObject
{
/// <summary>
/// Called by the server to fire the call back to the client
/// </summary>
/// <param name="s">Pass a string for testing</param>
public void FireNotifyCallback( string s )
{
Console.WriteLine( "Activating callback" );
OnNotifyCallback( s );
}
/// <summary>
/// Client overrides this method to receive the callback events from the server
/// </summary>
/// <param name="s">Pass a string for testing</param>
protected abstract void OnNotifyCallback( string s );
}
}
Create the Server
Create a new "Console Application" project in C#. and paste the following code into the auto generated program.cs file.
using BidirectionalShare;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Serialization.Formatters;
using System.Text;
namespace JrmServer
{
class JrmServer
{
/// <summary>
/// Main application starts here
/// </summary>
/// <param name="args">Maybe pass in the port number</param>
static void Main( string[] args )
{
// Creating a custom formatter for a TcpChannel sink chain.
BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
provider.TypeFilterLevel = TypeFilterLevel.Full;
// Creating the IDictionary to set the port on the channel instance.
IDictionary props = new Hashtable();
props["port"] = 8392; // This must match number on client
// Pass the properties for the port setting and the server provider
TcpChannel m_tcpChannel = new TcpChannel(props, null, provider);
ChannelServices.RegisterChannel( m_tcpChannel, false );
// Create the server object for clients to connect to
RemotingConfiguration.RegisterWellKnownServiceType(
typeof( ClientComms ),
"RemoteServer",
WellKnownObjectMode.Singleton );
// Loop here until the uers is done
Console.WriteLine( "Type some text to send to the client, or just press enter to exit" );
string s;
while( ( s = Console.ReadLine() ) != "" )
ClientComms.FireNewBroadcastedMessageEvent( s );
Console.WriteLine( "Sooo long and thanks for all the fish." );
}
}
/// <summary>
/// An instance of this class is exposed to the client allowing it to call into the server here
/// </summary>
public class ClientComms : MarshalByRefObject, ICallsToServer
{
/// <summary>
/// Function to call the server from the client. This is not in the GUI thread.
/// </summary>
/// <param name="n">Some number</param>
/// <returns>Some interesting text</returns>
public string SomeSimpleFunction( int n )
{
Console.Write( " Client sent : {0} \r", n );
return "Server says : " + n.ToString();
}
/// <summary>
/// Local copy of event holding a collection
/// </summary>
private static event NotifyCallback s_notify;
/// <summary>
/// Add or remove callback destinations on the client
/// </summary>
public event NotifyCallback Notify
{
add { s_notify = value; }
remove { Console.WriteLine( "TODO : Notify remove." ); }
}
/// <summary>
/// Call this method to send the string to the client. This call will throw an exception
/// if the client has gone or network is down
/// </summary>
/// <param name="s">Some test to send to client</param>
public static void FireNewBroadcastedMessageEvent( string s )
{
Console.WriteLine( "Broadcasting... Sending : {0}", s );
s_notify( s );
}
}
}
You will need to add System.Runtime.Remoting to the references of you project. Do this
by selecting Project->Add Reference from the menu. You will also need to add a reference to the class library dll
you creatred in the previous step.
Create the Client
Create a new "Console Application" project in C#. and paste the following code into the auto generated program.cs file.
using BidirectionalShare;
using System;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Text;
namespace JrmClient
{
class JrmClient
{
/// <summary>
/// Client application starts here
/// </summary>
/// <param name="args">Maybe pass in the port number and IP address</param>
static void Main( string[] args )
{
// Sleep for a moment to let the sewrver start
System.Threading.Thread.Sleep( 2000 );
// Use a defined port to receive callbacks or 0 to receive available port
TcpChannel m_TcpChan = new TcpChannel( 0 );
ChannelServices.RegisterChannel( m_TcpChan, false );
// Create the object for calling into the server
ICallsToServer m_RemoteObject = (ICallsToServer)
Activator.GetObject( typeof( ICallsToServer ),
"tcp://127.0.0.1:8392/RemoteServer" ); // Must match IP and port on server
// Define sink for events
RemotingConfiguration.RegisterWellKnownServiceType(
typeof( NotifySink ),
"ServerEvents",
WellKnownObjectMode.Singleton );
NotifySink sink = new NotifySink();
// Assign the callback from the server to here
m_RemoteObject.Notify += new NotifyCallback( sink.FireNotifyCallback );
// Keep calling till the server is gone (This is not necessary)
Console.WriteLine( "Client is now ready to send and receive data..." );
int n = 1;
while( true )
{
try
{
m_RemoteObject.SomeSimpleFunction( n++ );
System.Threading.Thread.Sleep( 1000 );
}
catch
{
Console.WriteLine( "Server is not responding. Go home." );
return;
}
}
}
/// <summary>
/// Derived from the wellknown object shared by the two applications. This is used to expose
/// methods for calling backing into client from the server
/// </summary>
class NotifySink : NotifyCallbackSink
{
/// <summary>
/// Events from the server call into here. This is not in the GUI thread.
/// </summary>
/// <param name="s">Pass a string for testing</param>
protected override void OnNotifyCallback( string s )
{
Console.WriteLine( "Message from the server : {0}", s );
}
}
}
}
You will need to add System.Runtime.Remoting to the references of you project. Do this
by selecting Project->Add Reference from the menu. You will also need to add a reference to the class library dll
you creatred in the previous step.
That is it. You can now build all three project. Run the server first and then a couple of clients. Type some text into the server to
see it sent to the client. Also try terminating one of the client then send some text from the server again. You will
still need to add some exception handlers to manage these situations.
Also try running the client on another machine and change 127.0.0.1 to the IP address of the server. See what happens when you
pull the network connection between the client and the server.
Conclusion
To make things simpler, no object clean up has been done. You should take the time to remove unwanted event handlers and connections.
May things can go wrong with remoting so be ready for them. Simple calls to methods that simply add two number can
throw exceptions because of network failure or missing servers. Make sure you test these situations and try to gracefully
handle them. Also watch out for threads. Most of the events triggered by remoting calls are not in the GUI thread so you
need to be sure you dont call the GUI before decoupling from the input thread with something like begin invoke.
|