|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionMost introductory articles about remoting focus on console-based applications, but frequently beginners ask, "How can I do remoting with a GUI?". I was asking the same question several months ago, but I couldn't find any useful introductory code samples on the Web that adequately explained how remoting should be done using a GUI, especially on the server. This article is an attempt to fill that gap. There are a few introductory articles about remoting that do use GUIs and could be very useful to you, but they do not address all the topics discussed in this article, and only the third article shows a GUI being used for the server.
PrerequisitesThis article will give you a good idea of how to use remoting in a GUI application. You do not need to know a whole lot about remoting before reading this article, but you should be familiar with remoting basics- you should understand how a basic remoting console-based application works. A nice intro is at gotdotnet.com. There are many design considerations when creating a distributed application using remoting, and I suggest you do a lot more research on the topic before creating any full-blown applications with it. A great resource is Advanced .NET Remoting 2nd Edition (Ingo Rammer and Mario Szpuszta, Apress, March 2005). Ingo goes into depth regarding remoting events and delegates which are common in GUI applications. He also provides some very useful advice regarding GUI usage with remoting in his article PRB: GUI application hangs when using non-[OneWay]-Events. This article is meant only as a simple introduction and only illustrates synchronous and asynchronous calls. High Level DesignLet's start with a high level look at the application we are going to develop. A Client application will make calls on a remote object called Greeter which is housed in the Server's application space. The Server will register itself with the Greeter to be notified when a call is made on the Greeter. The Greeter will notify the Server of the call, and the Server will display the information to its GUI.
Greeter Remote ObjectThe // Greeter.cs
using System;
using System.Threading;
namespace guiexample
{
public class Greeter : MarshalByRefObject
{
// Used by GUI to listen for SayHello calls
public delegate void HelloEventHandler(object sender, HelloEventArgs e);
public event HelloEventHandler HelloEvent;
// Time in msec to wait until we respond to the Client
private int mRespondTime;
public int RespondTime
{
get { return mRespondTime; }
set { mRespondTime = Math.Max(0, value); }
}
public Greeter()
{
// Default no argument constructor
}
public override Object InitializeLifetimeService()
{
// Allow this object to live "forever"
return null;
}
public String SayHello(String name)
{
// Inform the GUI that SayHello was called
if (HelloEvent != null)
HelloEvent(this, new HelloEventArgs(name));
// Pretend we're computing something that takes a while
Thread.Sleep(mRespondTime);
return "Hello there, " + name + "!";
}
}
}
The The ServerIn the public Server()
{
// Required for Windows Form Designer support
InitializeComponent();
// Register our tcp channel
ChannelServices.RegisterChannel(new TcpChannel(50050));
// Register an object created by the server
rmGreeter = new Greeter();
ObjRef refGreeter = RemotingServices.Marshal(rmGreeter, "Greeter");
rmGreeter.RespondTime = Convert.ToInt32(txbRespond.Text);
// Register ourself to receive updates that we will display in our GUI
rmGreeter.HelloEvent += new Greeter.HelloEventHandler(Server_HelloEvent);
}
The private void Server_HelloEvent(object sender, HelloEventArgs e)
{
lblHello.Text = "Saying hello to " + e.Name;
}
But this could cause a deadlock. The reason is because the event is running in a thread that didn't create the For more information on using private delegate void SetLabelTextDelegate(string text);
// For updating label
private void SetLabelText(string text)
{
lblHello.Text = text;
}
// Called when the Greeter object tells us SayHello has been called
private void Server_HelloEvent(object sender, HelloEventArgs e)
{
// Pull out name from the event
string text = "Saying hello to " + e.Name;
// Set the label text with the UI thread to avoid possible application hanging
this.BeginInvoke(new SetLabelTextDelegate(SetLabelText), new object[] {text});
}
The public class HelloEventArgs : EventArgs
{
private string mName;
public string Name
{
get { return mName; }
}
public HelloEventArgs(string name)
{
mName = name;
}
}
Our private void txbRespond_TextChanged(object sender, System.EventArgs e)
{
try
{
// Set delay before we respond to the client
int delay = Convert.ToInt32(txbRespond.Text);
if (delay >= 0)
rmGreeter.RespondTime = delay;
}
catch (Exception) {}
}
ClientThe public Client()
{
// Required for Windows Form Designer support
InitializeComponent();
// Register our tcp channel
ChannelServices.RegisterChannel(new TcpChannel());
rmGreeter = (Greeter)Activator.GetObject(
typeof(guiexample.Greeter), "tcp://localhost:50050/Greeter");
lblResult.Text = "";
}
The We use a private void btnCallSynch_Click(object sender, System.EventArgs e)
{
lblResult.Text = "";
// Force update since the remote method
// call blocks us from receiving window messages
lblResult.Refresh();
try
{
lblResult.Text = rmGreeter.SayHello(txbName.Text);
}
catch (Exception ex)
{
MessageBox.Show(this, "Unable to call SayHello. " +
" Make sure the server is running.\n"
+ ex.Message, "Client", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
The second button, Call Asynchronously, uses an asynchronous call to As an aside, you may be wondering about OneWay calls. OneWay calls allow you to call a void function without blocking the calling thread. Unfortunately these types of calls can cause serious performance hits when the recipients of these calls go down. For more information on why you should avoid OneWay calls, read the section entitled Why [OneWay] Events Are a Bad Idea from Ingo's book Advanced .NET Remoting. private void btnCallAsynch_Click(object sender, System.EventArgs e)
{
lblResult.Text = "";
// Produce an asynchronous call to the remote method SayHello from which
// we'll immediately return
AsyncCallback cb = new AsyncCallback(this.SayHelloCallBack);
SayHelloDelegate d = new SayHelloDelegate(rmGreeter.SayHello);
IAsyncResult ar = d.BeginInvoke(txbName.Text, cb, null);
}
When the The private delegate void SetLabelTextDelegate(string text);
// For updating label
private void SetLabelText(string text)
{
lblResult.Text = text;
}
public void SayHelloCallBack(IAsyncResult ar)
{
SayHelloDelegate d = (SayHelloDelegate)((AsyncResult)ar).AsyncDelegate;
try
{
// Pull out the return value
string text = (string)d.EndInvoke(ar);
// Set the label text with the UI thread
// to avoid possible application hanging
this.BeginInvoke(new SetLabelTextDelegate(SetLabelText),
new object[] {text});
}
catch (Exception ex)
{
MessageBox.Show(this, "Unable to call SayHello." +
" Make sure the server is running.\n" +
ex.Message, "Client", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Running the ApplicationDownload the application and run the server (server.exe) and the client (client.exe). If you want to run the application on different computers, you'll need to change "localhost" to the names of the computers the client and server are running on, and rebuild the application. I have provided a make.bat file that uses the C# command-line compiler csc to rebuild the application: csc /t:library /r:System.Runtime.Remoting.dll Greeter.cs HelloEventArgs.cs
csc /r:Greeter.dll /r:System.Runtime.Remoting.dll
/target:winexe /out:server.exe Server.cs
csc /r:Greeter.dll /r:System.Runtime.Remoting.dll
/target:winexe /out:client.exe Client.cs
ImprovementsThere are several improvements we could make to our application that don't improve its execution speed but do improve its design: use the Singleton design pattern to ensure the Singleton Design PatternOur We need to make the static Greeter mInstance = null;
private Greeter()
{
// Private constructor prevents instantiation with "new"
}
public static Greeter Instance
{
get
{
if (mInstance == null)
mInstance = new Greeter();
return mInstance;
}
}
The Greeter g = Greeter.Instance;
Now we are assured that the Using InterfacesThe current implementation allows a Let's first create an interface for the public interface IClientGreeter
{
String SayHello(String name);
}
Now let's create an interface for the public interface IServerGreeter
{
int RespondTime
{
get;
set;
}
event Greeter.HelloEventHandler HelloEvent;
}
The public class Greeter : MarshalByRefObject, IServerGreeter, IClientGreeter
...
int IServerGreeter.RespondTime
...
String IClientGreeter.SayHello(String name)
...
Now we need to change the way the private IClientGreeter rmGreeter;
...
rmGreeter = (guiexample.IClientGreeter)Activator.GetObject(
typeof(guiexample.IClientGreeter), "tcp://localhost:50050/Greeter");
We'll make similar code changes on the private IServerGreeter rmGreeter;
...
Greeter g = new Greeter();
ObjRef refGreeter = RemotingServices.Marshal(g, "Greeter");
rmGreeter = (IServerGreeter)g;
Now if you tried to set the A nice benefit to using interfaces is that you can completely decouple the implementation of the remote object from the client. If we create a DLL for our We would compile our system like so: csc /t:library /r:System.Runtime.Remoting.dll
Greeter.cs HelloEventArgs.cs IServerGreeter.cs IClientGreeter.cs
csc /t:library /r:System.Runtime.Remoting.dll IClientGreeter.cs
csc /r:Greeter.dll /r:System.Runtime.Remoting.dll
/target:winexe /out:server.exe Server.cs
csc /r:IClientGreeter.dll /target:winexe /out:client.exe Client.cs
The server would need Greeter.dll and server.exe, and the client would need IClientGreeter.dll and client.exe to execute. Configuration FilesWhen you are first learning about remoting, it is much easier to understand when all the channel and objects are explicitly created directly in the source code. But later when you want to run your programs on different computers or use different ports, you find that it's a real pain to have to change your code and recompile. That's where configuration files come into play. Let's create a server.exe.config file for the <configuration>
<system.runtime.remoting>
<application>
<channels>
<channel ref="tcp" port="50050" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
Let us replace the RemotingConfiguration.Configure("server.exe.config");
And let's create a client.exe.config file for the <configuration>
<appSettings>
<add key="GreeterUrl" value="tcp://localhost:50050/Greeter" />
</appSettings>
<system.runtime.remoting>
<application>
<channels>
<channel ref="tcp" port="0" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
This allows us to remove the // Create a TCP channel
RemotingConfiguration.Configure("client.exe.config");
Greeter g =
(Greeter)Activator.GetObject(typeof(guiexample.Greeter),
System.Configuration.ConfigurationSettings.AppSettings["GreeterUrl"]);
Now we can deploy our application and just change the config files whenever our server's URL or port number changes. ConclusionI've presented some very elementary concepts when using remoting in a GUI environment that I have been unable to find anywhere else on the Web. Here are the important points to remember when using GUIs with remoting:
I hope you'll find this article helpful. Feel free to leave me any constructive criticism. Revision History
| ||||||||||||||||||||