Demonstrates using .NET Remoting to broadcast to client subscribers. There are three parts:
1) Server.cs - for console app responsible for broadcasting messages (date/time stamp currently)
2) Client.cs - for console app that registers with server to subscribe to broadcasts
3) Agent.cs - class library that facilitates/eposes the interfaces for our client/server communication
To use, create the three project types defined above and include the appropriate code found in this snippet. In both the Server and Client projects, add references to the Agent.DLL project. Then, modify the IP address in the ClientClass's creation of the AbstractServer remoteObject to a string that represents your server's IP.
I began this experiment 2 - 3 weeks ago and have been waiting to perform static IP testing outside my LAN/LOCALMACHINE, but have not yet been able to. I decided to go ahead and comment the code, readying it for posting, while I was reading a very interesting and informative post titled "Peer to Peer Chat", which has nothing to do really with this snippet (LOL). Anyway, I have commented my code and am posting it as-is, but I think it should work over the internet.
Server broadcast events occur on a Timer (timer code aquired via a thread replied to by sknake--I love this guy's code!). The clients receive the broadcast by registering an event handler with the server (ClientEventHandler).
Communication/interfacing accomplished ia client adding a broadcast event handler (+= ClientEventHandler) to the server object, and, by functions GetClientID and MsgToServer (see code comments). I have provided many embedded comments in the code that should explain what is happening.
As mentioned at the top, I have only tested this using local IP because I didn't have a static IP address from which to run the server console. Meaning, it should work fine in a LAN environment, but I am not sure about WAN because I have not tested that. If someone could verify, and correct for WAN if necessary, I would appreciate. Of course, any comments or questions are welcome as well.
Cheers!
Server.cs file:
using System;
using Agent;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Collections;
using Timer = System.Timers.Timer;
using System.Collections.Generic;
using System.Security.Permissions;
namespace Server
{
class Program
{
static void Main(string[] args)
{
Console.Title = "Server Window";
// Create server object instance...
ServerClass.StartServer();
// FLUFF: Display statistics about our server channel...
RemoteUtility.ShowChannelInfo(ServerClass.ServerChannel);
// Wait... allow server instance to run...
System.Console.WriteLine("Press ENTER to quit");
System.Console.ReadLine();
}
}
// This is our broadcast/server object.
// Clients can subscribe to this service by creating an EventSink (see Agent.cs)
// and then adding client's event handler to ClientServiceEvent handler...
public class ServerClass : AbstractServer
{
// ServerClass instance objects...
static ServerClass Server { get; set; }
internal static TcpChannel ServerChannel { get; set; }
// List of clients subscribing to service...
Dictionary<int, ClientEventHandler> clients = new Dictionary<int, ClientEventHandler>();
int clientID = 0; // used to generate unique keys for our client list...
// Timer class used to broadcast messages on intervals for simulation...
TimerClass broadcastTimer;
public ServerClass()
{
// create timer that will broadcast messages at specified intervals...
broadcastTimer = new TimerClass(this, 3000); // broadcast at 3 second intervals...
broadcastTimer.StartTimer();
}
// Create broadcast server object...
[SecurityPermission(SecurityAction.Demand)]
internal static void StartServer()
{
try
{
// Create server and client sink providers needed for TCP channel...
BinaryServerFormatterSinkProvider serverProv = new BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full; //full deserialization level
BinaryClientFormatterSinkProvider clientProv = new BinaryClientFormatterSinkProvider();
// Properties of the server channel...
IDictionary props = new Hashtable();
props["port"] = 9999;
props["rejectRemoteRequests"] = "false";
// Create the server channel (message transport mechanism)...
ServerChannel = new TcpChannel(props,
clientProv, // receiver
serverProv); // sender
// Register the server channel...
ChannelServices.RegisterChannel(ServerChannel, false);
// Create the server instance...
Server = new ServerClass();
// Get type of the server instance to expose...
Type serverClassType = Server.GetType();
// Expose the server object for remote calls...
// Clients will create proxy using URI (RemotingBroadcastServer.rem),
// and our AbstractServer class object type...
RemotingConfiguration.RegisterWellKnownServiceType(
serverClassType, // Service type...
"RemotingBroadcastServer.rem", // object URI
WellKnownObjectMode.Singleton); // mode: every incoming message serviced by the same object instance...
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}. Stack trace: {1}.", e.Message, e.StackTrace);
}
}
// A method to allow client to obtain unique client id assigned by server...
// These keys (ID's) are created by incrementing, beginning with ID = 1,
// every time a client subscribes...
public override int GetClientID(ClientEventHandler client)
{
Console.WriteLine("Client requested it's ID.");
// Iterate through each client that has subscribed to our broadcast...
Dictionary<int, ClientEventHandler>.KeyCollection keys = clients.Keys;
foreach (int key in keys)
{
// dispatch the message string...
ClientEventHandler subscriber = clients[key];
if (client.Equals(subscriber))
return key;
}
// Our server's response if key not found...
return -1; // client not found!--how can this be?--should never happen!
}
// A method to demonstrate and allow client to send a message to server...
public override string MsgToServer(string s)
{
Console.WriteLine("Client sent message: " + s);
// Our server's response...
return "Server received message from client.";
}
// Event handler that allows clients to subscribe to the broadcast server events...
public override event ClientEventHandler ClientServiceEvent
{
add
{
Console.WriteLine("New Client subscribed to service...");
// Add client's event handler to our subscribed list...
clientID++;
clients[clientID] = value;
}
remove
{
// Just allow broadcast/dispatcher to remove so we don't have to
// depend on client to perform a graceful shutdown/cleanup;
// If connection is lost, client's event handler will be removed...
}
}
// Our broadcast/dispatcher to each client that has subscribed to the
// service event (ClientServiceEvent)...
// Called by timer and transmits date/time stamp to clients at specified intervals...
protected void BroadcastedMessageEventToClients(string s)
{
// list of any keys will be built to remove if any connection fails...
List<int> removeKeys = new List<int>();
// for each client subscribing to our service...
Dictionary<int, ClientEventHandler>.KeyCollection keys = clients.Keys;
foreach(int key in keys)
{
try
{
// retrieve client/subscriber's event handler...
ClientEventHandler subscriber = clients[key];
// dispatch the message string...
subscriber(s);
}
catch (Exception e)
{
// if exception, assume client has shutdown or been disconnected;
// remove client from subscribed list...
Console.WriteLine("Exception: {0}.", e.Message);//, e.StackTrace);
Console.WriteLine("\tRemoving client (ID: {0}) event handler...", key);
removeKeys.Add(key);
}
}
// remove any failed client connections...
foreach (int key in removeKeys)
clients.Remove(key);
}
// This timer class borrowed from sknake's code found in thread "blinking label"...
class TimerClass
{
ServerClass server;
public TimerClass(ServerClass server, int interval)
{
this.server = server;
__timerLock = new object();
timer = new Timer();
timer.Interval = interval;
timer.AutoReset = true;
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
syncPoint = 2; //NOTE the value change here
}
private object __timerLock;
private bool TimerRunning
{
get
{
lock (__timerLock)
{
return timer.Enabled;
}
}
}
private Timer timer;
/// <summary>
/// 0: The timer is available for the next elapsed event
/// 1: A thread is busy with the timer's elapsed event
/// 2: The timer is in a stopped state
/// </summary>
private long syncPoint;
public void StartTimer()
{
if (this.TimerRunning)
throw new InvalidOperationException("The timer is already running.");
if (System.Threading.Interlocked.Read(ref syncPoint) != 2)
throw new InvalidOperationException("The syncPoint value is not in the correct state for the timer to start");
lock (__timerLock)
{
System.Threading.Interlocked.Exchange(ref syncPoint, 0);
timer.Start();
}
}
public void StopTimer()
{
if (!this.TimerRunning)
throw new InvalidOperationException("The timer is not running.");
lock (__timerLock)
{
timer.Stop();
}
while (System.Threading.Interlocked.CompareExchange(ref syncPoint, 2, 0) != 2)
System.Threading.Thread.Sleep(10);
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
long sync = System.Threading.Interlocked.CompareExchange(ref syncPoint, 1, 0);
if (sync == 0)
{
try
{
// Broadcast message to all subscribed clients...
server.BroadcastedMessageEventToClients(DateTime.Now.ToString());
}
finally
{
System.Threading.Interlocked.Exchange(ref syncPoint, 0);
}
}
}
}
}
}
Client.cs file:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Collections;
using System.Security.Permissions;
using Agent;
namespace Client
{
// Client's agent class interface to broadcast server.
class ClientEventSink : AbstractBroadcastedMessageEventSink
{
// Overridden method that gets called by server's dispatch (broadcast) method.
// Client subscribes to the broadcast service by wiring to Server.ClientServiceEvent...
// eg. m_RemoteObject.ClientServiceEvent += new ClientEventHandler(sink.ServiceDispatchMethod);
// Method will be invoked by server's invocation of ClientServiceEvent of
// type: ClientEventHandler...
protected override void ClientCallback (string str)
{
Console.WriteLine("Server Broadcasted: " + str);
}
}
class Program
{
static TcpChannel clientChannel;
[SecurityPermission(SecurityAction.Demand)]
static void StartClient()
{
// Create server and client sink providers needed for TCP channel...
BinaryServerFormatterSinkProvider serverProv = new BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full; //full deserialization level
BinaryClientFormatterSinkProvider clientProv = new BinaryClientFormatterSinkProvider();
try
{
// Properties used when creating the client channel...
IDictionary props = new Hashtable();
props["port"] = 0;
props["rejectRemoteRequests"] = "false";
// Create the client channel (message transport mechanism)...
clientChannel = new TcpChannel(props,
clientProv, // receiver
serverProv); // sender
// Register the client's channel...
ChannelServices.RegisterChannel(clientChannel, false);
// Get type of the server instance to expose...
Type serverClassType = typeof(AbstractServer);
// Create a proxy to the server from givent URI (RemoteObject.rem),
// and the given object type (AbstractServer class)...
AbstractServer remoteObject = (AbstractServer)
Activator.GetObject(serverClassType,
"tcp://192.168.0.100:9999/RemotingBroadcastServer.rem"); // using my default gateway/IP
// Register an instance of client...
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ClientEventSink),
"RemotingBroadcastClient.rem", // URI...
WellKnownObjectMode.Singleton); // service by same object instance...
// Create a client's agent instance to speak with the server...
ClientEventSink sink = new ClientEventSink();
Console.WriteLine("Subscribing to service...");
// Subscribe to the broadcast service...
ClientEventHandler clientEventHandler = new ClientEventHandler(sink.ServiceDispatchMethod);
remoteObject.ClientServiceEvent += clientEventHandler;
int clientId = remoteObject.GetClientID(clientEventHandler);
Console.Title += "(" + clientId + ")";
// Send a message to the server--just say hello and hopefully it will respond back...
Console.WriteLine("\tSending Hello message to the server...");
string s = remoteObject.MsgToServer("Hello Server from new client, Thanks for listening!");
Console.WriteLine("\tServer replied: " + s);
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}. Stack trace: {1}.", e.Message, e.StackTrace);
}
}
static void Main(string[] args)
{
Console.Title = "Client Window";
StartClient();
// FLUFF: Display statistics about our client channel...
RemoteUtility.ShowChannelInfo(clientChannel);
System.Console.WriteLine("Press ENTER to quit");
System.Console.ReadLine();
}
}
}
Agent.cs file:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
namespace Agent
{
// delegate defining our broadcast event handler...
public delegate void ClientEventHandler(string str);
// Defines server interface; each client will use to communicate and subscribe to event(s)...
public abstract class AbstractServer : MarshalByRefObject
{
// Interface method client can use to talk to server...
public abstract string MsgToServer(string s);
// Interface method client can use to obtain the server assigned client ID...
public abstract int GetClientID(ClientEventHandler client);
// Broadcast event subscribed to by client...
public abstract event ClientEventHandler ClientServiceEvent;
}
//Defines the class that should have the "sink" in the server
public abstract class AbstractBroadcastedMessageEventSink : MarshalByRefObject
{
// Method/event client registers with server for dispatching;
// Calls the overridden method (ClientCallback) defined by client...
public void ServiceDispatchMethod(string str)
{
// Call client's handler...
ClientCallback(str);
}
// Callback event; overridden by client and used to receive messages
// broadcast from the server...
protected abstract void ClientCallback (string str) ;
}
// FLUFF: Not essential for any of this...
public class RemoteUtility
{
// Code within taken from MSDN example...
public static void ShowChannelInfo(TcpChannel chan)
{
// Show the name of the channel.
Console.WriteLine("The name of the channel is {0}.",
chan.ChannelName);
// Show the priority of the channel.
Console.WriteLine("The priority of the channel is {0}.",
chan.ChannelPriority);
// Show the URIs associated with the channel.
ChannelDataStore data = (ChannelDataStore)chan.ChannelData;
foreach (string uri in data.ChannelUris)
{
Console.WriteLine("The channel URI is {0}.", uri);
}
}
}
}