Sector 0

June 21, 2008

RemotingLite – user manual

Filed under: — frank @ 18:43

Creating a host application

A service host is the server that processes all requests from clients. The class “ServiceHost” contains logic to receive and delegate method calls to the implementation of the interface. A remoting host must have a reference to an object that contains the methods that needs to be called remotely.

Let us start by defining an interface

public interface ICalculator
{
	int Sum(int a, int b);
	int Sum(params int[] values);
	void SquareValue(ref int value);
}
  • int Sum(int a, int b) : Adds two integers and returns the result.
  • int Sum(params int[] values) : Adds a variable number of integers and returns the result.
  • void SquareValue(ref int value) : Takes a value, squares it, and returns the result in the same variable as the input.

The implementation is pretty straight forward:

public class ConcreteCalculator : ICalculator
{
	public int Sum(int a, int b) { return a+b; }
	public int Sum(params int[] values)
	{
		int sum = 0;
		foreach(int n in values)
			sum += n;
		return sum;
	}
	public void SquareValue(ref int value) { value *= value; }
}

Now we have the concrete implementation we want. The next step is to use the ServiceHost class to start listening for incoming requests from clients.

static void Main(string[] args)
{
	using (ServiceHost host = new ServiceHost(typeof(ConcreteCalculator), 8000))
	{
		host.Open();
 
		Console.WriteLine("Host is running.\\nPress  to exit.");
		Console.ReadLine();
	}
}

ServiceHost has two constructors – one that takes a type of the concrete implementation, another which takes an instance of the same type. Both must be supplied with a port number that the server will be listening on for incoming requests.

  • public ServiceHost(Type remotedType, int port)
  • public ServiceHost(object singletonInstance, int port)

Important!! The host does not guarantee thread safety. Since the host can have numerous simultaneous connections on the same object, it is up to the developer to make the class in question thread safe.

The thread safe issue is due to the fact that the host will block access to the object completely each time a method is invoked. This is not very effective, and the developers using the host know where and what to lock in order to maximize efficiency.

Using ThreadPool threads

Under normal circumstances the service host will spawn a regular background thread for each connection. This is okay for smaller applications, but for larger systems the server machine might get slower due to too many threads running all at the same time. Therefore I added the option to use ThreadPool threads so that programmers have more control over the number of active threads.
Using ThreadPool threads is done by setting the property “UseThreadPool” to true. This has to be done before calling Open().

static void Main(string[] args)
{
	using (ServiceHost host = new ServiceHost(typeof(ConcreteCalculator), 8000))
	{
		host.UseThreadPool = true;
 
		host.Open();
 
		Console.WriteLine("Host is running.\\nPress  to exit.");
		Console.ReadLine();
	}
}

Creating a client

On the client side you have two choices. You can use the class ProxyFactory to create a proxy directly from the interface you have implemented on the server side. But if you want more control of access through the proxy you can inherit from the class ClientBase.

Subclassing ClientBase

The class ClientBase uses ProxyFactory to create a proxy of the interface. This can be accessed through the property “Proxy”.

public class CalculatorClient : ClientBase<ICalculator>, ICalculator
{
	public CalculatorClient(IPEndPoint endpoint) : base(endpoint) { }
 
	int Sum(int a, int b)
	{
		return Proxy.Sum(a, b);
	}
 
	int Sum(params int[] values)
	{
		return Proxy.Sum(values);
	}
 
	void SquareValue(ref int value)
	{
		Proxy.SquareValue(ref value);
	}
}

Notice that ClientBase has one constructor which takes an IPEndPoint as an argument. Thus you have to write a similar constructor that calls the base constructor. An IPEndPoint defines an end point which consists of an IP-address and a port number and is a way for the client to identify the server on the network.

A client that uses this class might look like this:

static void Main(string[] args)
{
	Console.Write("Enter host address or host name > ");
	string hostNameOrAddress = Console.ReadLine();
	IPAddress[] addressList = Dns.GetHostEntry(hostNameOrAddress).AddressList;
	IPEndPoint endpoint = new IPEndPoint(addressList[0], 8000);
 
	using(CalculatorClient client = new CalculatorClient(endpoint))
	{
		Console.WriteLine(client.Sum(10, 12));
	}
}

Creating a proxy through ProxyFactory

If you do not want to write any client side code that implements the interface you provided, you can use the proxy factory directly.

static void Main(string[] args)
{
	Console.Write("Enter host address or host name > ");
	string hostNameOrAddress = Console.ReadLine();
	IPAddress[] addressList = Dns.GetHostEntry(hostNameOrAddress).AddressList;
	IPEndPoint endpoint = new IPEndPoint(addressList[0], 8000);
 
	ICalculator client = ProxyFactory.CreateProxy<ICalculator>(endpoint);
	Console.WriteLine(client.Sum(10, 12));
	((IDisposable)client).Dispose();
}

Notice here that you cannot use “using” since we operate on an interface. But the proxy created by the factory inherits from a class that implements IDisposable. Calling Dispose() will notify the host that we want to close the connection, and then closes it. It is not strictly necessary to directly call Dispose() since the garbage collector of the runtime will do this when releasing the object. But be carefull not to have a reference to the client object, since this will prevent the garbage collector from disposing the object.

Exception handling

If a method on the host side – for one reason or the other – throws an exception, this exception can be caught on the client side. The exception originally thrown on the host is wrapped in a TargetInvocationException exception. To get the original exception you will have to use the InnerException property like this.

	...
	try
	{
		...
		//call the host
 
		...
	}
	catch(TargetInvocationException tie)
	{
		Exception originalException = tie.InnerException;
		Console.WriteLine(originalException.Message);
	}
	...

Limitations in the current version

The current version has a few limitations which a programmer needs to be aware of.

  • No support for properties. You have to write Get- and Set methods on your own.
  • When passing objects to the proxy it is currently not possible to alter the state.
  • No support for generic method paramters.

Generic method parameters

Currently the framework does no support methods with generics in their signature, whereas parameters which are in themselves generics are allowed.The following example illustrates what is not allowed and what is.

public interface SomeInterface
{
	...
	void SomeMethod<T>(); //NOT allowed
	T SomeOtherMethod<T>(); //NOT allowed
	void SomeThirdMethod<T,U>(T t, U u); //NOT allowed
 
	List<int> SomeFourthMethod(); //Allowed!
	List<ushort> SomeFifthMethod(List<string>); //Allowed!
	...
}

Passing objects by reference

Consider this example:

public class IntContainer
{
	public int a;
	public int b;
	public IntContainer(int a, int b)
	{
		this.a = a;
		this.b = b;
	}
}
 
public interface IMyInterface
{
	void ManipulateObject(IntContainer ic);
}

An implementation of this interface might take the IntContainer object and multiply the values “a” and “b” with 10. An example of using the interface might be:

	...
 
	IMyInterface instance = new MyInterfaceImpl();
	IntContainer ic = new IntContainer(10, 20);
	instance.ManipulateObject(ic);
 
	...

After the call to ManipulateObject the values ic.a and ic.b is respectively 100 and 200.

This is not the case when using RemotingLite, unfortunately. But passing the object by reference will fix the problem:

public interface IMyInterface
{
	void ManipulateObject(ref IntContainer ic);
}

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress