- Implementing a Remote Server
- Defining Shared Code
- Handling Server Events on the Client
- Multithreading Caution
- Summary
Defining Shared Code
Remote objects are either serializable, marshal-by-value copies of server objects, or proxied, marshal-by-reference values. For client and server to have two-way communications, the event message can be serializable, because copies of the event message are okay, but remotable reference objects are needed for the client and server. We need remotable objects on both endsclient and serverbecause the client is invoking methods on the server and the server is invoking methods on the client. On the client, we're using behaviors of the server, and on the server, we're invoking the delegatethe event handlerdefined on the client.
The basic code shared between client and server is very similar. We need to inherit from MarshalByRefObject and override the InitializeLifetimeService method. MarshalByRefObject means that client and server will have a proxy reference to each other. InitializeLifetimeService is used to obtain a lifetime lease for the service, indicating how long the remote objects should stay alive.
There is one additional difference between the remotable client and server shared code; that is, that the client object needs to create a remote instance of the server's remotable object.
There are other ways to share code between clients and servers. For example, we can use interfaces and share only interfaces, or we can use the sproxy.exe utility to generate stubs, which is what WSDL and XML web services do. Listing 3 contains the code shared between client and server.
Listing 3 Shared Code Referenced by Client and Server To Permit Two-Way Remote Communications
using System; using System.Runtime.Remoting; namespace General { public class Shared : MarshalByRefObject { public event EventHandler MyEvent; public void RaiseEvent() { MyEvent(this, EventArgs.Empty); } public override object InitializeLifetimeService() { return null; } } public class Client : MarshalByRefObject { private Shared shared = null; public override object InitializeLifetimeService() { return null; } public Client() { RemotingConfiguration.Configure("Client.exe.config"); shared = new Shared(); shared.MyEvent += new EventHandler(HandlesMyEvent); } public void RaiseRemoteEvent() { shared.RaiseEvent(); } public void HandlesMyEvent(object sender, EventArgs e) { Console.WriteLine(string.Format("Event raised by {0}", sender.ToString())); } } }
The server's remotable object is called Shared and the client's remotable object is called Client. By overriding InitializeLifetimeService and returning null, we encourage the service objects to hang around indefinitely. We can configure the lifetime service, or lease, both programmatically and via the .config files.
Our simplified server class has one function, to raise an event on the client. The simplified client has four functions. The client creates an instance of the remote object. The client assigns its event handler to the server's public event. The client invokes the service's behaviors, and the client handles the event raised by the server.
To complete the example, we need a client and the client's app.config file referenced in the constructor for the Client class.