In another post I looked at remoting using WCF but what were things like before WCF (or what’s an alternative to WCF)? I thought I’d post just the bare bones for getting a really simple service and client up and running.
Note: Microsoft recommends new remoting code uses WCF, so this post is more for understanding legacy code.
Note: I am not going to go into the lifecycle, singletons/single instances etc. here, I’m just going to concentrate on the code to get something working.
The Server
As per my previous post on WCF remoting, we’re going to simply create a console application to act as our server, so go ahead an create one (mine’s named OldRemoteServer) and then add the following
public interface IRemoteService { void Write(string message); }
into a file of it’s own, then our client can link to it in our client’s Visual Studio solution.
Here’s an implementation for the above
public class RemoteService : MarshalByRefObject, IRemoteService { public void Write(string message) { Console.WriteLine(message); } }
Note: The implementation needs to be derived from MarshalByRefObject or the class needs to be marked with the Serializable attribute or implement ISerializable which obviously makes sense. We’re solely going to look at MarshalByRefObject implementation for now as these will be passed by reference to the client, whereas the serializable implementations are aimed at passing by value.
Now let’s put some code in the Main method of our Console app.
var channel = new TcpChannel(1002); ChannelServices.RegisterChannel(channel, false); RemotingConfiguration.RegisterWellKnownServiceType( typeof(RemoteService), "Remote", WellKnownObjectMode.Singleton); // now let's ensure the console remains open Console.ReadLine();
In the above code we’re using a Tcp channel with the port number 1002 and in the RegisterWellKnownServiceType, we’re registering the service type (this should be the implementation, not the interface) and supply a name “Remote” for it. In this case I’m also setting it to be a Singleton.
You’ll need to add a reference to System.Runtime.Remoting and the following using clauses
using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp;
The Client
Create another Console application to simply test this and add the file with the IRemoteService interface.
I’ve simply added it by selecting the client solution, the Add… | Existing Item, locate the file and instead of clicking the Add button select the dropdown to Add As Link. Then if you change the interface it’ll be immediately reflected in the client – ofcourse in a more complex application we’d have the interfaces in their own assembly.
Now to get the client to call the server we simply place the following in the Main method of the Console app.
var obj = (IRemoteService)Activator.GetObject( typeof(IRemoteService), "tcp://localhost:1002/Remote"); obj.Write("Hello World");
In the above you’ll see that we use the Activator to get an object and supply the type, then the next line shows we’re using the Tcp channel, port 1002 as set up in the server and the name from the server we game our object “Remote”.
This creates a transparent proxy which we then simply call the interface method Write on.
Configuration
In the above example code I’ve simply hard-coded the configuration details but ofcourse we can create a .config file to instead handle such configurations.
Let’s replace all the server Main method code with the following
RemotingConfiguration.Configure("Server.config", false);
Add an App.config to the project, we’re going to rename it Server.config for this example and ensure that the files’s properties (in Visual Studio) are set to Copy Always (or Copy if newer) to ensure the copy in the bin folders is upto date.
Now here’s the Server.config which recreates the singleton, tcp, port 1002 settings previously handled in code
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.remoting> <application> <service> <wellknown type="OldRemoteServer.RemoteService, OldRemoteServer" objectUri="Remote" mode="Singleton" /> </service> <channels> <channel ref="tcp" port="1002"/> </channels> </application> </system.runtime.remoting> </configuration>
Now if you run the server and then the client, everything should work as before.
Next up, let’s set the client up to use a configuration file…
So we’ll add an App.config file to the client now, but let’s name it Client.config and again set the Visual Studio properties to ensure it’s always copied to the bin folder.
Add the following to the configuration file
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.remoting> <application> <client> <wellknown type="OldRemoteServer.RemoteService, OldRemoteServer" url="tcp://localhost:1002/Remote" /> </client> </application> </system.runtime.remoting> </configuration>
It might seem a little odd that we’re declaring the type as the implementation within the server code, but the reason will hopefully become clear.
Add a reference within the client to the RemoteServer (if you have the implementation in a DLL, all the better, we didn’t do that, so I’m referencing the server EXE itself). This now give us access to the implementation of the RemoteService.
Change the client Main method to
RemotingConfiguration.Configure("Client.config", false); var obj = new RemoteService(); obj.Write("Hello World");
don’t forget to add the using clause
using System.Runtime.Remoting;
This bit might seem a little strange, based upon what we’ve previously done and how we’ve kept a separation of interface and implementation. Aren’t we now simply creating a local instance of the RemoteService, you might ask.
Well try it, run the server and then the client and you’ll find .NET has created a transparent proxy for us and calls to the RemoteService will in fact go to the server.
Whilst this makes things very easy, I must admit I prefer to not reference the implementation of the RemoteService.
What about named pipes?
Let’s now look at the changes to use the IPC protocol (for implementing named pipes) in .NET remoting. I’ll just briefly cover the changes required to implement this.
To start with let’s rewrite the server and client in code. So first the Main method in the server should now look like
var channel = new IpcChannel("ipcname"); ChannelServices.RegisterChannel(channel, false); RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteService), "Remote", WellKnownObjectMode.Singleton); Console.ReadLine();
So the only real difference from the Tcp implementation is the use of an IpcChannel and the name supplied instead of a port.
The client then looks like this
var obj = (IRemoteService)Activator.GetObject( typeof(IRemoteService), "ipc://ipcname/Remote"); obj.Write("Hello World");
Simple enough.
Now let’s change the code to use a configuration file.
The server Main method should now look like this
RemotingConfiguration.Configure("Server.config", false); Console.ReadLine();
and the Server.config should look like this
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.remoting> <application> <service> <wellknown type="OldRemoteServer.RemoteService, OldRemoteServer" objectUri="Remote" mode="Singleton" /> </service> <channels> <channel ref="ipc" portName="ipcname"/> </channels> </application> </system.runtime.remoting> </configuration>
The client code should be
RemotingConfiguration.Configure("Client.config", false); var obj = new RemoteService(); obj.Write("Hello World");
and it’s Client.config should look like this
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.remoting> <application> <client> <wellknown type="OldRemoteServer.RemoteService, OldRemoteServer" url="ipc://ipcname/Remote" /> </client> </application> </system.runtime.remoting> </configuration>