Google
WWW Yariv Hammer's Code Site

Saturday, January 28, 2006

Maintaining a List of All Connected Clients - A Remoting Tutorial

Introduction
This tutorial continue the the list of Remoting tutorials in this blog. In my last tutorial I showed how to recover from disconnecions both in the server and the client.
Now I am going to demonstrate how to maintain a list of connected clients both in the server and the clients. At the end, we will have a window that looks like a chat room, with all the connected users listed on the form.

The starting point of this tutorial is the end point of the previous one, so be sure to read my previous one first.

Full source code (C#) can be downloaded from here (~350 KB)

Step 1 - Create an IClientsManager Interface
In the Common assembly we will add a new interface called IClientsManager.
Here is the code:
-------------------------------------------

using System.Collections;
namespace Common
{
public interface IClientsManager
{
long RegisterNewClient();
bool NotifyClientAlive(long clientId);
bool IsClientConnected(long clientId);
ICollection GetListOfActiveClients();
}
}
--------------------------------------------------------
The client will be identified by a unique id, given by the server. Later you can extend this to be a full ClientInfo class. For now a number will do.
The interface IClientsManager has 4 methods. RegisterNewClient will be called by the client when it connects to the server. The server will return the new Id.
NotifyClientAlive will be called by the client once a period. When the method is no longer called, the server can know that the client is disconnected and thus notify the other clients.
IsClientConnected will help clients ask the server whether another client is connected.
GetListOfActiveClients will be used by the clients to refresh their list of active clients.

Step 2 - Extending the Messages
Before we jump for the implementation of the new interface we need to do some modifications.
In order to distinguish between custom messages raised by the clients, and system messages such as client disconnections, we will create a new type of message: SystemMessage.

First we will change a bit the Message class (in Common assembly)
--------------------------------------------
[Serializable()]
public class Message
{
public enum MessageType
{
Custom,
System
}
public string _msg;
public Message(string msg)
{
this._msg = msg;
}
public string Text
{
get { return _msg; }
}
public virtual MessageType Type
{
get { return MessageType.Custom;}
}
}
--------------------------------------------------

What I've done here is add an enumeration MessageType. If the message is a string message sent by a client (as before) then the message is a Custom message. If the message is sent by the server to notify about the connections or disconnection of a client then type is System.

Now we will derive from Message to create a new class SystemMessage. I put the class in the Message.cs file right under the Message class.
-------------------------------------------------------
[Serializable()]
public class SystemMessage:Message
{
private SystemMessageType _type;
private long _clientId;
public enum SystemMessageType
{
ClientConnected,
ClientDisconnected
}
public SystemMessage(SystemMessageType type, long clientId, string message):
base(message)
{
_type = type;
_clientId = clientId;
}
public override Common.Message.MessageType Type
{
get { return MessageType.System; }
}
public SystemMessageType SystemType
{
get { return _type; }
}
public long ClientId
{
get { return _clientId; }
}
}
---------------------------------------------------------
The SystemMessageType enumeration contains client connected or client disconnected types of messages. Later we might want to add more system messages. The SystemType property holds this information.
The ClientId property will be used in order to know which client connected or disconnected.

Note: I must say that rethinking about this design, I don't like it. It is very hard to extend the Message using inheritance. I would much prefer to use a pattern such as the Tag property of a Control (extend the message by composition instead of inheritance). But lets leave it like this at this point.

We will return later to modify further the Message class.

Now we add a method, BroadcastSystemMessage to the Broadcaster class in the Server:
--------------------------------------------
[Serializable()]
public class Broadcaster:MarshalByRefObject,IBroadcaster
{
public event MessageArrivedHandler MessageArrived;
public void BroadcastMessage(Message msg)
{
SafeInvokeEvent(msg);
}
public void BroadcastSystemMessage(SystemMessage msg)
{
SafeInvokeEvent(msg);
}
private void SafeInvokeEvent(Message msg)
{
if (MessageArrived == null)
return;
MessageArrivedHandler mah=null;
foreach(Delegate del in MessageArrived.GetInvocationList())
{
try
{
mah = (MessageArrivedHandler)del;
mah(msg);
}
catch (Exception ex)
{
Console.Write(ex.Message);
MessageArrived -= mah;
}
}
}
public override object InitializeLifetimeService()
{
return null;
}
}
------------------------------------------------
The BroadcastMessage and BroadcastSystemMessages are identical. We only make a distinction in order for our code to be more readable. Note that the client cannot invoke this new method because we did not add the method to the IBroadcaster interface. This is good, because only the server should raise system events.
By now you should have guessed that we are going to use our already implemented Broadcaster to distribute the connections and disconnections of clients. Thats the power of a good and solid well-designed system.

Last at this step we need to extend the functionality of MessageBroadcaster in the client. The class will raise the SystemMessageArrived event, so the client's objects can easily distinguish between the two types of events:
---------------------------------------------------------

using System;
using System.Net.Sockets;
using System.Threading;
using Common;
namespace Client
{
public class MessageBroadcaster:IBroadcaster
{
private IBroadcaster _bcaster = null;
private BroadcastEventHelper _eventHelper = null;
public MessageBroadcaster()
{
if (ClientStartup.Connector.ServerAlive)
{
InitializeObjects();
}
ClientStartup.Connector.ServerDisconnected += new EventHandler(Connector_ServerDisconnected);
ClientStartup.Connector.ServerReconnect += new EventHandler(Connector_ServerReconnect);
}
public void BroadcastMessage(Message msg)
{
if (_bcaster == null)
throw new ServerNotAliveException();
try
{
_bcaster.BroadcastMessage(msg);
}
catch (SocketException ex)
{
Console.WriteLine(ex.Message);
}
catch (ThreadAbortException ex)
{
Console.WriteLine(ex.Message);
}
}
private void HandleMessage(Message msg)
{
switch (msg.Type)
{
case Message.MessageType.Custom:
if (MessageArrived != null)
MessageArrived(msg);
break;
case Message.MessageType.System:
if (SystemMessageArrived != null)
SystemMessageArrived(msg);
break;
}
}
private void InitializeObjects()
{
_bcaster = (IBroadcaster)RemotingHelper.GetObject(typeof(IBroadcaster));
_eventHelper = new BroadcastEventHelper();
_eventHelper.MessageArrivedLocally += new MessageArrivedHandler(HandleMessage);
_bcaster.MessageArrived += new MessageArrivedHandler(_eventHelper.LocallyHandleMessageArrived);
}
private void Connector_ServerDisconnected(object sender, EventArgs e)
{
_bcaster = null;
_eventHelper = null;
}
private void Connector_ServerReconnect(object sender, EventArgs e)
{
InitializeObjects();
}
public event MessageArrivedHandler MessageArrived;
public event MessageArrivedHandler SystemMessageArrived;
}
}
--------------------------------------------------------
Step 3 - Implementing the ClientsManager Class
Now we are more than ready to implement the IClientsManager interface. Add a new class to the Server assembly called ClientsManager, derived from MarshalByRefObject and implementing the new IClientsManager interface.
First the code:
------------------------------------------------
using System;
using System.Collections;
using System.Timers;
using Common;
namespace Server
{
public class ClientsManager:MarshalByRefObject, IClientsManager
{
private Timer _aliveTimer = new Timer(1000);
private Hashtable _currentClients = new Hashtable();
private long _currentMaxClientId = -1;
public ClientsManager()
{
_aliveTimer.Elapsed += new ElapsedEventHandler(aliveTimer_Elapsed);
_aliveTimer.Start();
}
public override object InitializeLifetimeService()
{
return null;
}
public long RegisterNewClient()
{
long id = ++_currentMaxClientId;
_currentClients[id] = DateTime.Now.Ticks;
Broadcaster bcast = (Broadcaster) RemoteSingletonObjectsList. Instance. GetRemoteSingletonObject( typeof(Broadcaster));
bcast. BroadcastSystemMessage (new SystemMessage(SystemMessage.SystemMessageType. ClientConnected, id,""));
return id;
}
public bool NotifyClientAlive(long clientId)
{
if (IsClientConnected(clientId))
{
_currentClients[clientId] = DateTime.Now.Ticks;
return true;
}
return false;
}
public bool IsClientConnected(long clientId)
{
return _currentClients.ContainsKey(clientId);
}
public ICollection GetListOfActiveClients()
{
return _currentClients.Keys;
}
private void aliveTimer_Elapsed(object sender, ElapsedEventArgs e)
{
_aliveTimer.Stop();
long now = DateTime.Now.Ticks;
foreach (long id in _currentClients.Keys)
{
long lastTicks = (long)_currentClients[id];
if (now - lastTicks >= 20000000)
{
_currentClients.Remove(id);
Broadcaster bcast = (Broadcaster) RemoteSingletonObjectsList. Instance.GetRemoteSingletonObject( typeof(Broadcaster));
bcast.BroadcastSystemMessage( new SystemMessage(SystemMessage.SystemMessageType. ClientDisconnected,id,""));
break;
}
}
_aliveTimer.Start();
}
}
}
-------------------------------------------------------
_currentClients is a HashTable - the keys are the clients Ids of connected clients. The values are time stamps. The Timer (_aliveTimer) will elapse every second, and check the timestamps. If a client failed to call NotifyClientAlive for more then 20 seconds or so then the server will remove the client from _currentClients and notify everybody using the BroadcastSystemMessage method of Broadcaster.
When a client calls RegisterNewClient, it is added to _connectedClients, and a new Id is returned. The Ids are resources handled by this class. In our case the Ids are sequential, so each client gets an Id larger by 1 than the previous client. This way we ensure that the Ids are unique. A system event will be broadcasted, notifying everybody about the new client.
Last, the NotifyClientAlive should be called by clients, passing their Ids, once in a while. The timestamp will be updated every call. This way if a client fails to notify the server, the _aliveTimer will clean it up.

Next we register the new class for remoting. Open the App.Config of the server and add this line (you should already know where to put the line - in the "service" tag)
------------------------------------------------------------
<wellknown mode= "Singleton" type= "Server.ClientsManager, Server" objectUri= "ClientsManager.soap"/>
------------------------------------------------------------

We also need to register the class in Client's App.Config file. Add this line to the "client" tag:
------------------------------------------------------------
<wellknown type= "Common.IClientsManager, Common" url= "tcp://localhost:16784/ EventServer/ClientsManager.soap"/>
------------------------------------------------------------
Step 4 - Changing ClientConnector Class in the Client
The ClientConnector class was created in the previous tutorial. Its purose is throwing events whenever the client is connected and disconneted to the server.
We will now add more functionality to this class.
-------------------------------------------------------------

using System;
using System.Threading;
using System.Timers;
using Common;
using Timer = System.Timers.Timer;
namespace Client
{
public class ClientConnector
{
public ClientConnector()
{
this.ServerReconnect += new EventHandler (ClientConnector_ServerReconnect);
this.ServerDisconnected += new EventHandler (ClientConnector_ServerDisconnected);
_serverConnectivityTimer = new Timer(1000);
_serverConnectivityTimer.Elapsed += new ElapsedEventHandler(serverConnectivityTimer_Elapsed);
_serverConnectivityTimer.Start();
}
public event EventHandler ServerDisconnected;
public event EventHandler ServerReconnect;
private Timer _serverConnectivityTimer;
private void serverConnectivityTimer_Elapsed(object sender, ElapsedEventArgs e)
{
_serverConnectivityTimer.Stop();
CheckServerConnectivity();
_serverConnectivityTimer.Start();
}
private bool _serverIsAlive = false;
private void CheckServerConnectivity()
{
try
{
if (_serverIsAlive && IsClientIdValid)
{
IClientsManager manager = (IClientsManager)RemotingHelper.GetObject(typeof(IClientsManager));
manager.NotifyClientAlive(this.ClientId);
}
IServerConnector serverConnector = (IServerConnector) RemotingHelper.GetObject (typeof(IServerConnector));
bool isAlive = serverConnector.IsAlive;
if (isAlive && !_serverIsAlive)
SetServerAlive();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Thread t = new Thread(new ThreadStart(SetServerNotAlive));
t.Start();
t.Join();
}
}
private void SetServerAlive()
{
if (!_serverIsAlive && ServerReconnect != null)
ServerReconnect(this,EventArgs.Empty);
_serverIsAlive = true;
}
public void SetServerNotAlive()
{
if (_serverIsAlive && ServerDisconnected != null)
ServerDisconnected(this,EventArgs.Empty);
_serverIsAlive = false;
}
public bool ServerAlive
{
get
{
_serverConnectivityTimer.Stop();
CheckServerConnectivity();
_serverConnectivityTimer.Start();
return _serverIsAlive;
}
}
private long _clientId = -1;
public event EventHandler ClientIdChanged;
public long ClientId
{
get { return _clientId; }
}
public bool IsClientIdValid
{
get { return _clientId >= 0; }
}
private void ClientConnector_ServerReconnect (object sender, EventArgs e)
{
IClientsManager manager = (IClientsManager) RemotingHelper.GetObject (typeof(IClientsManager));
_clientId = manager.RegisterNewClient();
if (ClientIdChanged != null)
ClientIdChanged(this,EventArgs.Empty);
}
private void ClientConnector_ServerDisconnected (object sender, EventArgs e)
{
_clientId = -1;
}

}
}
------------------------------------------------------------
the _clientId property is by default equal to -1. When the event ServerReconnect is raised, we assign the client with a new client Id, by calling RegisterNewClient (See step 3). The server will notify the other clients (Next step). When the event ServerDisconnected is raised we assign the client again with Id -1.
We already had a timer to check if the server is alive (remember the ServerConnector class from the previous tutorial?). We now need to call NoitifyClientAlive in the server (See step 3).
Last I added the ClientIdChanged, because it is a good practice to have <Property>Changed events.

Step 5 - Implementing the Client Side Object
Add a new class to the Client assembly, called ConnectedClients.
------------------------------------------------------------
using System;
using System.Collections;
using Common;
namespace Client
{
public class ConnectedClients
{
private MessageBroadcaster _message = new MessageBroadcaster();
private ArrayList _activeClients = new ArrayList();
public ConnectedClients()
{
_message.SystemMessageArrived += new MessageArrivedHandler(Message_SystemMessageArrived);
ClientStartup.Connector.ServerDisconnected += new EventHandler(Connector_ServerDisconnected);
ClientStartup.Connector.ServerReconnect += new EventHandler(Connector_ServerReconnect);
}
private void Message_SystemMessageArrived(Message msg)
{
SystemMessage systemMessage = msg as SystemMessage;
switch (systemMessage.SystemType)
{
case SystemMessage.SystemMessageType.ClientConnected:
AddClient(systemMessage.ClientId);
break;
case SystemMessage.SystemMessageType.ClientDisconnected:
if (systemMessage.ClientId == ClientStartup.Connector.ClientId) return;
_activeClients.Remove(systemMessage.ClientId);
if (ClientRemoved != null)
ClientRemoved(this,new ClientEventArgs( systemMessage.ClientId));
break;
}
}
private void AddClient(long clientId)
{
if (clientId == ClientStartup.Connector.ClientId) return;
_activeClients.Add(clientId);
if (ClientAdded != null)
ClientAdded(this,new ClientEventArgs(clientId));
}
public ArrayList ActiveClients
{
get { return _activeClients; }
}
public event ClientEventHandler ClientAdded;
public event ClientEventHandler ClientRemoved;
public event EventHandler ClientsCleared;
public class ClientEventArgs:EventArgs
{
public long ClientId
{
get { return _clientId; }
}
public ClientEventArgs(long clientId)
{
this._clientId = clientId;
}
private long _clientId = -1;
}
public delegate void ClientEventHandler(object sender, ClientEventArgs e);
private void Connector_ServerDisconnected(object sender, EventArgs e)
{
ClearClients();
}
private void ClearClients()
{
_activeClients.Clear();
if (ClientsCleared != null)
ClientsCleared(this,EventArgs.Empty);
}
private void Connector_ServerReconnect(object sender, EventArgs e)
{
RefreshList();
}
public void RefreshList()
{
ClearClients();
IClientsManager manager = (IClientsManager)RemotingHelper.GetObject(typeof(IClientsManager));
ICollection clients = manager.GetListOfActiveClients();
foreach (long id in clients)
AddClient(id);
}
}
}
-----------------------------------------------------------
I will now explain the class fully (It is quite long).
First of all we have the ActiveClients property. This property is the whole purpose of this class, right?
The class has 3 events: ClientAdded, ClientRemoved, and ClientsCleared (less important). The ClientEventHandler is defined within the scopr of this class, and the ClientEventArgs as well.
The class uses three events:
1. The SystemMessageArrived (See step 2) of MessageBroadcaster. The event indicate that a client connected or disconnected. In both cases we check that the event is not raised by chance by the client itself. After that we add the client Id or remove it from _activeClients list. Then we raise the ClientAdded or ClientRemoved event.
2. ServerReconnect of ClientConnector. In this case we refresh the list of clients by calling GetListOfActiveClients (See step 3), and adding each client to _activeClients.
3. ServerDisconnected of ClientConnector. We clear the clients list, and raise the ClientsCleared event.

Next, we need to add an instance of the ConnectedClients class to ClientStartup class (same as we did for ClientConnector). Change the ClientStartup class as follows:
-----------------------------------------------------

using System;
using System.Runtime.Remoting;
using System.Windows.Forms;
namespace Client
{
public class ClientStartup
{
private static ClientConnector _connector;
public static ClientConnector Connector
{
get
{ return _connector; }
}
private static ConnectedClients _clients;
public static ConnectedClients ConnectedClients
{
get { return _clients; }
}
[STAThread]
static void Main()
{
InitRemoting();

_connector = new ClientConnector();
_clients = new ConnectedClients();
Application.Run(new ClientForm());
}
private static void InitRemoting()
{
string fileName = "client.exe.config";
RemotingConfiguration.Configure(fileName);
}
}
}
-----------------------------------------
Step 6 - Add a Client Id to the Message
We can now add to the Message the property SenderId in order to know which client sent the message (we did not know this information before). For the special case of the Server sending messages, we will set the SERVER_ID to be -100 (arbitrarily selected).
Change the Message class (in Common assembly) as follows:
-------------------------------------------------------

[Serializable()]
public class Message
{
public enum MessageType
{
Custom,
System
}
public string _msg;
protected long _senderId;
public Message(long senderId, string msg)
{
this._msg = msg;
this._senderId = senderId;
}
public string Text
{
get { return _msg; }
}
public virtual MessageType Type
{
get { return MessageType.Custom;}
}
public static long SERVER_ID
{
get { return -100; }
}
public string TextWithSenderId
{
get
{
if (!IsSenderServer)
return "Client " + SenderId.ToString() + ": " + Text;
else
return "Server: " + Text;
}
}
public virtual long SenderId
{
get { return _senderId; }
}
public bool IsSenderServer
{
get { return _senderId == SERVER_ID; }
}
}
[Serializable()]
public class SystemMessage:Message
{
private SystemMessageType _type;
private long _clientId;
public enum SystemMessageType
{
ClientConnected,
ClientDisconnected
}
public SystemMessage(SystemMessageType type, long clientId, string message):
base(SERVER_ID, message)
{
_type = type;
_clientId = clientId;
}
public override Common.Message.MessageType Type
{
get { return MessageType.System; }
}
public SystemMessageType SystemType
{
get { return _type; }
}
public long ClientId
{
get { return _clientId; }
}
}
------------------------------------------------------
Notice the TextWithSenderId method, which formats the message with the identity of the sender.

Demo
Lets test things. We will modify both ClientForm and ServerForm.

ServerForm:
So far we had a ListBox for messages. We will add another ListBox for the clients list.
This is the code (without theauto-generated code):
------------------------------------------------------

public class ServerForm : Form
{
private ListBox lsbMessages;
private Button btnSend;
private ListBox lsbConnectedClients;

private Container components = null;
public ServerForm()
{
InitializeComponent();
_bcast = (Broadcaster) RemoteSingletonObjectsList.Instance. GetRemoteSingletonObject( typeof(Broadcaster));
_bcast.MessageArrived += new MessageArrivedHandler(bcast_MessageArrived);
}

(...)

private Broadcaster _bcast = null;
public void bcast_MessageArrived(Message msg)
{
if (msg.Type == Message.MessageType.Custom)
{
_msgToShow = msg;
Thread t = new Thread(new ThreadStart(ShowMessage));
t.Start();
}
else
{
HandleSystemMessage((SystemMessage)msg);
}
}
private void HandleSystemMessage (SystemMessage systemMessage)
{
switch (systemMessage.SystemType)
{
case SystemMessage.SystemMessageType. ClientConnected:
lsbConnectedClients.Items.Add("Client " + systemMessage.ClientId);
break;
case SystemMessage.SystemMessageType. ClientDisconnected:
lsbConnectedClients.Items.Remove("Client " + systemMessage.ClientId);
break;
}
}
private Message _msgToShow=null;
private void ShowMessage()
{
if (_msgToShow == null) return;
lsbMessages.Items.Add(_msgToShow.TextWithSenderId);
lsbMessages.SelectedIndex = lsbMessages.Items.Count -1;
_msgToShow = null;
}
private long index=0;
private void btnSend_Click(object sender, System.EventArgs e)
{
_bcast.BroadcastMessage(new Message(Message.SERVER_ID, "Server: This is message #" + (index++).ToString()) );
}
-----------------------------------------------------

ClientForm:
Again, we add another ListBox to show the list of active clients. Here is the code (without the auto-generated code):
----------------------------------------------------------

public class ClientForm : System.Windows.Forms.Form
{
private System.Windows.Forms.ListBox lsbMessages;
private System.Windows.Forms.Button btnSendMessage;
private System.Windows.Forms.ListBox lsbClients;
private System.ComponentModel.Container components = null;
public ClientForm()
{
InitializeComponent();
_messageBroadcaster.MessageArrived += new Common.MessageArrivedHandler(broadcaster_MessageArrived);
ClientStartup.ConnectedClients.ClientAdded += new Client.ConnectedClients.ClientEventHandler (ConnectedClients_ClientAdded);
ClientStartup.ConnectedClients.ClientRemoved += new Client.ConnectedClients.ClientEventHandler (ConnectedClients_ClientRemoved);
ClientStartup.ConnectedClients.ClientsCleared += new EventHandler(ConnectedClients_ClientsCleared);
ClientStartup.Connector.ClientIdChanged += new EventHandler (Connector_ClientIdChanged);
ClientStartup.Connector.ServerDisconnected += new EventHandler(Connector_ServerDisconnected);
ClientStartup.Connector.ServerReconnect += new EventHandler (Connector_ServerReconnect);
if (ClientStartup.Connector.IsClientIdValid)

{
this.Text = "Client " + ClientStartup.Connector.ClientId;

ClientStartup.ConnectedClients.RefreshList();
}
else
this.Text = "Client Not Connected";
}

(...)

MessageBroadcaster _messageBroadcaster = new MessageBroadcaster();
private long _index=0;
private void btnSendMessage_Click (object sender, System.EventArgs e)
{
Thread t = new Thread(new ThreadStart(SendMessage));
t.Start();
}
private void SendMessage()
{
try
{
Message msg = new Message (ClientStartup.Connector.ClientId, "This is message #" + (_index++).ToString());
_messageBroadcaster.BroadcastMessage(msg);
msg = null;
}
catch (ServerNotAliveException ex)
{
lsbMessages.Items.Add(ex.Message);
lsbMessages.SelectedIndex = lsbMessages.Items.Count-1;
}
}
private void broadcaster_MessageArrived(Message msg)
{
lsbMessages.Items.Add(msg.TextWithSenderId);
lsbMessages.SelectedIndex = lsbMessages.Items.Count-1;
}
private void Connector_ServerDisconnected(object sender, EventArgs e)
{
this.Text = "Client Not Connected";
}
private void Connector_ServerReconnect(object sender, EventArgs e)
{
this.Text = "Client " + ClientStartup.Connector.ClientId;
}
private void Connector_ClientIdChanged(object sender, EventArgs e)
{
this.Text = "Client " + ClientStartup.Connector.ClientId;
}
private void ConnectedClients_ClientAdded(object sender, Client.ConnectedClients.ClientEventArgs e)
{
lsbClients.Items.Add("Client " + e.ClientId.ToString());
}
private void ConnectedClients_ClientRemoved(object sender, Client.ConnectedClients.ClientEventArgs e)
{
lsbClients.Items.Remove("Client " + e.ClientId.ToString());
}
private void ConnectedClients_ClientsCleared(object sender, EventArgs e)
{
lsbClients.Items.Clear();
}
}
----------------------------------------------------------

Run the server and few instances of the client. See the Ids of the clients in the header of the form, and the lists update both in the server and the clients form. Try to disconnect the server and the client. Try to send messages.

Summary
This was a tutorial for a fully functional server-client application using Remoting.NET technology. At this point you have a generic Broadcaster for events, a reconnections mechanism, and a list of connected clients. I don't think that I have ever seen such a tutorial for Remoting over the net (you are welcome to correct me on this).

The next step will be the most impressive (in my opinion): a Multicaster class, which will enable distributing events to selected clients instead of all the clients.

Comments
Two things I don't like about the code I have posted. The first is the use of inheritance to create a SystemMessage (you saw at step 2 how much work was involved, and how many layers were effected). The second is the use of long Id instead of a class to represent the Client. It should not be hard to fix the second problem.

I will finish my (very long) article by saying that this was a very complex code to design, program and document. It works as far as I can tell, but if you see a bug, or a better way to do something, you are more than welcome to comment. Any bugs I will try to fix.

4 Comments:

At 4:05 AM, March 03, 2006, Anonymous Anonymous said...

This is a great example, works flawlessly, except when the client and server are on different machines, simply changing the app.config for the client to use the remote server URL results in SocketException.

 
At 5:25 AM, March 03, 2006, Blogger Yariv Hammer said...

There is a place in code you should also change from "localhost" to the ipaddress.
look for any "localhost" in the projects and change them.

 
At 11:55 PM, March 05, 2006, Anonymous Anonymous said...

Thanks for the response, I had changed the IP address from Localhost to a remote machine in the client, but it appears the server is selecting some random ports to communciate events back to the client. I get a socket exception in the client too. Argh, it looks like we may have to revert back to a message based client-server scenario using sockets. Any help greatly appreciated.

 
At 1:25 PM, March 06, 2006, Blogger Yariv Hammer said...

In the method InitRemoting in the ServerSetup class there is "localhost" as well. Replace it too.

 

Post a Comment

<< Home

Feel free to use everything here. Add links to my site if you wish.

Do not copy anything to other sites without adding link to here.

All the contents of the site belong to Yariv Hammer.