It’s been a long while since I had to write any remoting code. However I came across a requirement to run a small out of process application which needed to be controlled by another application, I won’t bore you with the details, but suffice to say remoting seemed to be a good fit for this.
Let’s look at some code
Let’s start by creating the server/service. In this instance I will host the service in a Console application. So we need to create a Console solutions/project (mine’s named RemoteServer) and then (to allow us to reuse an interface file) add a Class library (mine’s named RemoteInterfaces).
Add the following to the class library
[ServiceContract] public interface IRemoteService { [OperationContract(IsOneWay = true)] void Write(string message); }
Note: you’ll need a reference to System.ServiceModel in the RemoteInterfaces project, plus using System.ServiceModel; in the source
Add the class library as a reference to the RemoteServer console application.
Now, let’s create the implementation for the server…
[ServiceBehavior( ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.Single)] public class RemoteService : IRemoteService { private ServiceHost host; public void Start() { host = new ServiceHost(this); host.Open(); } public void Stop() { if (host != null) { if (host.State != CommunicationState.Closed) { host.Close(); } } } public void Write(string message) { Console.WriteLine(String.Format("[Service] {0}", message)); } }
We’re going to run this service as a singleton when the console starts, so in Program.cs we need to write the following
try { var svc = new RemoteService(); svc.Start(); Console.ReadLine(); svc.Stop(); } catch (Exception e) { Debug.WriteLine(e.Message); }
Finally we need to put some configuration in place to tell WCF how this service is to be hosted, so in the console application, add an App.config (if not already available) and add the following
finally App.config looks like
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="RemoteServer.RemoteService"> <endpoint binding="netTcpBinding" contract="RemoteInterfaces.IRemoteService"> <identity> <dns value="localhost"/> </identity> </endpoint> <host> <baseAddresses> <add baseAddress="net.tcp://localhost:8000/RemoteService"/> </baseAddresses> </host> </service> </services> </system.serviceModel></configuration>
Client implementation
Now, lets create a client to tests this service. For simplicity, we’ll assume the server is run automatically, either at startup or via our client application, but we won’t bother writing code for this. So when you’re ready to test the client just ensure you’ve run the server console first. You’ll need to reference the class library with the IRemoteService (RemoteInterfaces) also.
Note: you’ll need a reference to System.ServiceModel in the RemoteClient project, plus using System.ServiceModel; in the source
Change the client Main method to include the following
var channelFactory = new ChannelFactory<IRemoteService>( new NetTcpBinding(), new EndpointAddress("net.tcp://localhost:8000/RemoteService")); var channel = channelFactory.CreateChannel(); channel.Write("Hello World"); channelFactory.Close();
Now if you run this client, the server should output the Hello World text.
We’ve initially created this client by hard coding the endpoint etc. but it might be better if this was in the configuration.
In your App.config you could have the following
<system.serviceModel> <client configSource="Config\servicemodel-client-config.xml" /> </system.serviceModel>
Note: Obviously the name of the file us upto you.
or we could place the service code directly in the App.config, it should look like this (if in an external config file)
<?xml version="1.0" encoding="utf-8"?> <client> <endpoint name="RemoteServer.RemoteService" address="net.tcp://localhost:8000/RemoteService" binding="netTcpBinding" contract="RemoteInterfaces.IRemoteService"> <identity> <servicePrincipalName value=""/> </identity> </endpoint> </client>
Note: If placed within the App.config, wrap the client element in a system.serviceModel element
and now we can change our ChannelFactory in the Main method to look like this
var channelFactory = new ChannelFactory<IRemoteService>("*");
The configuration will thus be taken from the configuration file.
Let’s switch from using TCP to using a named pipe
So the previous code and configuration should work correctly using the TCP/IP protocol, but it does rely on a known port to be used. In scenarios where you’re deploying to a machine where you’re not 100% sure the port is available, you could ofcourse offer up several port options.
Another choice (which should also be more performant, although I’ve not tested this) for interprocess communications on the same machine, is to use a named pipe.
For this change the service’s App.config (or external file) on the server to use the following configuration
<services> <service name="RemoteServer.RemoteService"> <endpoint binding="netNamedPipeBinding" contract="RemoteInterfaces.IRemoteService"> <identity> <dns value="localhost"/> </identity> </endpoint> <host> <baseAddresses> <add baseAddress="net.pipe://localhost/RemoteServer/RemoteService"/> </baseAddresses> </host> </service> </services>
and in the client code we could use
channelFactory = new ChannelFactory<IRemoteService>( new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/RemoteServer/RemoteService"));
or replacing with configuration we could use replace our client configuration with
<client> <endpoint name="RemoteServer.RemoteService" address="net.pipe://localhost/RemoteServer/RemoteService" binding="netNamedPipeBinding" contract="RemoteInterfaces.IRemoteService"> <identity> <servicePrincipalName value=""/> </identity> </endpoint> </client>
Debugging
You might at times find issues with your client not finding an endpoint, an ipc name or port etc. How can we check if our server is actually connected to a port or pipe?
For tcp/http protocols, we can use netstat, for example
netstat -a | findstr "8000"
this will list display all connections and listening ports (-a switch) and pipe these through the findstr application to display only those with 8000 in the output. Hence if we’re running a server off of port 8000 it should be listed and we’ll know that the server is (at least) running.
For ipc/pipe, the simplest way is via Powershell by running the following
[System.IO.Directory]::GetFiles("\\.\\pipe\\")
this will list all pipes, which might be a little over the top.
An alternative to the above is use the sysinternals PipeList executable.
Whilst we could use the following for old style remoting code (to find a named pipe)
[System.IO.Directory]::GetFiles("\\.\\pipe\\") | findstr "RemoteService"
unfortunately this does not work for WCF named pipes as these are converted to GUID’s. See Named Pipes in WCF are named but not by you (and how to find the actual windows object name).