One of new things in .NET 3.5 was support for pipes (both named and anonymous). Since in some of my applications I used P/Interop for that exact purpose, it seemed quite logical to upgrade that code into native solution. As I started changing code I also started to hate that implementation.
Although new classes are quite easy to use, troubles start as soon as you start transferring little bigger amounts of data (more that cca 200 bytes). I spent better part of night troubleshooting problems with messages broken into multiple parts although buffer sizes were more than adequate and Message transmission was used.
Everything was worsened by design decision to kill off two things that make life easier when playing with those streams. First thing that I needed was ability to check whether there is more data before I start to read it (PeekNamedPipe). This comes in quite handy to dimension your buffers and generally detect when data stream is dry. I needed this function quite a lot because they also decided to kill timeouts for Read() function. Once you start reading data, there is no stopping. And since there is no way to know whether there is more data awaiting other than actually reading it I had quite a big problem at my hand.
I was on verge or restoring old P/Inteop code when I had revelation. Old project was based on .NET 2.0 and with this upgrade I will move it to 3.5 anyhow - why wouldn't I use Windows Communication Foundation. I played with it before with great success but here I wanted to see how simple it can be and main goal was just to integrate it in already existing flow.
Old application used notion of sending actions to other party and just giving responses to user and that simplified what I needed to do drastically.
Main thing to do is specifying how your communication interface will look like:
[ServiceContract(Namespace = "http://example.com/Command")]
interface ICommandService {
[OperationContract]
string SendCommand(string action, string data);
}
This code I needed in both server and client part of my code. Beautiful thing is that it is quite enough to have this file in both places and there is no need for separate assembly to hold common definitions.
Only thing left to be done in client was creating proxy to actually call that method. I opted for separate class, but I could be as easy written in any other existing class:
class CommandClient {
private static readonly Uri ServiceUri = new Uri("net.pipe://localhost/Pipe");
private static readonly string PipeName = "Command";
private static readonly EndpointAddress ServiceAddress = new EndpointAddress(string.Format(CultureInfo.InvariantCulture, "{0}/{1}", ServiceUri.OriginalString, PipeName));
private static readonly ICommandService ServiceProxy = ChannelFactory<ICommandService>.CreateChannel(new NetNamedPipeBinding(), ServiceAddress);
public static string Send(string action, string data) {
return ServiceProxy.SendCommand(action, data);
}
}
Server part was little bit more complicated but not significantly so:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class CommandService : ICommandService {
public string SendCommand(string action, string data) {
//handling incoming requests
}
}
static class CommandServer {
private static readonly Uri ServiceUri = new Uri("net.pipe://localhost/Pipe");
private static readonly string PipeName = "Command";
private static CommandService _service = new CommandService();
private static ServiceHost _host = null;
public static void Start() {
_host = new ServiceHost(_service, ServiceUri);
_host.AddServiceEndpoint(typeof(ICommandService), new NetNamedPipeBinding(), PipeName);
_host.Open();
}
public static void Stop() {
if ((_host != null) && (_host.State != CommunicationState.Closed)) {
_host.Close();
_host = null;
}
}
}
Once server application starts just call CommandServer.Start() and all requests from client side will be auto-magically instantiated in CommandService. Whatever you decide to return from that method arrives directly to client. It cannot be simpler than that.
I created small example but do notice that it is a console application and no multi-threading issues are being addressed here. Since WCF is working on separate thread any conversion into Windows application will most probably require playing with delegates and that is subject for some other story.
P.S. Do not forget to add reference to System.ServiceModel assembly.
Could you explain a bit about Anonymous pipe?
I have tried named pipe for IPC but I want to restrict its use locally. does setting net.pipe://localhost/ make it Anonymous pipe?
Implementation of named pipes in WCF restricts it’s use to local machine only (see http://msdn.microsoft.com/en-us/library/ms733769.aspx).
Anonymous pipes are not supported in WCF.
Really well explained. I was having the same problems as you with System.IO.Pipes. It was working alright at first, but later on it wasn’t as stable as I thought. You example works like a charm.
Thanks for the post.
Thanks for sharing your solution. I successfully implemented it and everything worked fine… until I noticed I was getting timeout errors.
I have the server part running continuously, and the client program just sending commands to it occasionally – it could be hours in between communications.
So, when the client tries to send a command to the server, I get a “timeout, pipe is closing” error.
Can I specify an infinite timeout value; or is there any way to reset the communication pipe from the “client” in order to fix this issue?
(Closing and opening the client program resets the pipe and communication resumes ok; but I’d need a way to do it without restarting the entire program).
Thanks in advance for any help.
@Carlos Martins:
If you check the documentation for NamedPipeBindings (http://msdn.microsoft.com/en-us/library/system.servicemodel.channels.binding.receivetimeout(v=VS.90).aspx) you’ll see that they have a default 10 minute timeout.
I haven’t tested this yet, but replace this line
_host.AddServiceEndpoint(typeof(ICommandService), new NetNamedPipeBinding(), PipeName);
with the following
NetNamedPipeBinding namedPipeBinding = new NetNamedPipeBinding();
namedPipeBinding.ReceiveTimeout = TimeSpan.MaxValue;
_host.AddServiceEndpoint(typeof(ICommandService), namedPipeBinding, PipeName);
that might do the trick.
Thank you