1. Introduction

Have you ever needed to exchange data between two .NET applications running on the same machine? For example a web site talking to a Windows service? The .NET Framework provides several good options for inter-process communication (IPC) like Web services and Remoting, the fastest being Remoting with a TCP channel and binary formatter.

The problem however is that Remoting is relatively slow, which most of the time is irrelevant, but if you need to make frequent "chatty" calls from one application to another and if your primary concern is performance, then Remoting might become an obstacle. What makes Remoting slow is not so much the communication protocol but the serialization.

Generally speaking Remoting is great but in the case when the IPC is confined to the local machine, it adds unnecessary overhead. That is why I started looking at alternatives, namely Named Pipes that will not involve binary serialization and will provide fast and lightweight IPC.

The first part of this article explores a way of implementing Named Pipes based IPC between .NET applications. In Part 2 we look at building a pipe server and a client communicating with it.

Remember that the situations where this solution would be most beneficial is when one application is exchanging frequent short text messages with another, located on the same machine or within the same LAN. For structured data exchange those text messages can also be XML documents or serialized .NET objects. No security layer is implemented because Named Pipes are accessible within the LAN only and it is assumed that security will be handled by the existing infrastructure.

For more information on Named Pipes visit the MSDN Library. The NamedPipeNative class, part of this solution, is based on the Named Pipes Remoting Channel by Jonathan Hawkins.

2. Classes

Let's now look at some of the classes and methods that are part of the Named Pipes solution. Diagram 1 below shows those classes and the relationships between them.

There are several interfaces, part of the solution, such as IClientChannel and IInterProcessConnection, all compiled in the AppModule.InterProcessComm assembly. Those interfaces are introduced in order to abstract the Named Pipes implementation from clients involved in the IPC. Following the fundamental object-oriented principle of "loose coupling", our client application will use the interfaces when exchanging messages with the server, which allows the specific IPC protocol to vary if necessary.

Outlined below are the main responsibilities of the classes, part of the .NET Named Pipes solution.

NamedPipeNative: This utility class exposes kernel32.dll methods for Named Pipes communication. It also defines constants for some of the error codes and method parameter values.

NamedPipeWrapper: This class is a wrapper around NamedPipesNative. It uses the exposed kernel32.dll methods to provide controlled Named Pipes functionality.

APipeConnection: An abstract class, which defines the methods for creating Named Pipes connections, reading and writing data. This class is inherited by the ClientPipeConnection and ServerPipeConnection classes, used by client and server applications respectively.

ClientPipeConnection: Used by client applications to communicate with server ones by using Named Pipes.

ServerPipeConnection: Allows a Named Pipes server to create connections and exchange data with clients.

PipeHandle: Holds the operating system native handle and the current state of the pipe connection.

Diagram 1: Named Pipes UML static diagram

3. Creating a Named Pipe

As part of the different Named Pipes operations, first we are going to see how a server Named Pipe is created.

Each pipe has a name as "Named Pipe" implies. The exact syntax of server pipe names is \\.\pipe\PipeName. The "PipeName" part is actually the specific name of the pipe. In order to connect to the pipe a client application needs to create a client Named Pipe with the same name. If the client is located on a different machine, the name should also include the server e.g. \\SERVER\pipe\PipeName.

The following static method from NamedPipeWrapper is used to instantiate a server Named Pipe.

By calling NamedPipeNative.CreateNamedPipe the above method creates a duplex Named Pipe of type message and sets it in blocking mode. It is also specified that unlimited instances of the pipe will be allowed.

If the pipe is created successfully, CreateNamedPipe returns the native pipe handle, which we assign to our PipeHandle object. The native handle is an operating system pointer to the Named Pipe and is further used in all pipe related operations. The PipeHandle class is introduced to hold the native handle and also to track the current state of the pipe. The Named Pipes states are defined in the InterProcessConnectionState enumeration and they correspond to the different operations - reading, writing, waiting for clients, etc.

Assuming that the server Named Pipe was created successfully, it can now start listening to client connections.

4. Connecting Client Pipes

The server Named Pipe needs to be set in a listening mode in order for client pipes to connect to it. This is done by calling the NamedPipeNative.ConnectNamedPipe method. Because our pipe was created in a blocking mode, calling this method will put the current thread in waiting mode until a client pipe attempts to make a connection.

A client Named Pipe is created and connected to a listening server pipe by calling the NamedPipeNative.CreateFile method, which in turn calls the corresponding Kernel32 method. The code below, part of NamedPipeWrapper.ConnectToPipe illustrates that.

After we create a PipeHandle object and build the pipe name, we call the NamedPipeNative.CreateFile method to create a client Named Pipe and connect it to the specified server pipe. In our example the client pipe is configured to cater for both reading and writing.

If the client pipe is created successfully, the CreateFile method returns the native handle corresponding to the client Named Pipe, which we are going to use in subsequent operations. If for some reason the client pipe creation failed, the method would return -1, which is set to be the value of the INVALID_HANDLE_VALUE constant.

There is one more thing that needs to be done before the client Named Pipe can be used for reading and writing. We need to set its handle mode to PIPE_READMODE_MESSAGE, which will allow us to read and write messages. This is done by calling NamedPipeNative.SetNamedPipeHandleState:

Each client pipe communicates with an instance of the server pipe. If the server pipe has reached its maximum number of instances, then creating a client pipe will return an error. In such case it is useful to check for the error type, wait for some time and then make another attempt to create the client Named Pipe. Checking for the error type is done by the NamedPipeNative.GetLastError method:

5. Writing and Reading Data

Named Pipes do not support stream seeking, which means that when reading from a named pipe we cannot determine in advance the size of the message. As a workaround a simple message format is introduced, which allows us to first specify the length of the message and then read or write the message itself.

Our solution will not need to cater for very large messages so we are going to use a System.Int32 variable to specify the message length. In order to represent an Int32 we need four bytes, so the first four bytes of our messages will always contain the message length.

5.1. Writing Data to a Named Pipe

The NamedPipeWrapper.WriteBytes method below writes a message to a Named Pipe, represented by the handle provided as an input parameter. The message itself has been converted to bytes using UTF8 encoding and is passed as a bytes array.

5.2. Reading from a Named Pipe

In order to read a message from a Named Pipe we first need to find its length by converting the first four bytes to an integer. Then we can read the rest of the data. The NamedPipeWrapper.ReadBytes method below illustrates that.

6. Other Named Pipes Operations

Some other operations, part of the IPC, include disconnecting, flushing and closing Named Pipes.

DisconnectNamedPipe disconnects one end of the pipe from the other. Disconnecting a server pipe allows the latter to be reused by releasing it from the client pipe. This technique is shown in Part 2 of the article where we build a multithreaded Named Pipes server.

FlushFileBuffers writes to the pipe any buffered data. It is often used in conjunction with writing operations before closing a Named Pipe.

CloseHandle is used to close a Named Pipe and release its native handle. It is important to always close a pipe after finishing working with it in order to release any related resources, therefore any Named Pipes operations should be wrapped in a try-catch-finally block and the CloseHandle method should be placed in the finally part.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Share

About the Author

Ivan Latunov is a Software Architect with long term commercial experience in the areas of software architecture and engineering, design and development of web and distributed applications and business and technical analysis. Technical blog. Website: ivanweb.com.

Comments and Discussions

I changed the client a little so that I can connect to another computer instead of "\\.\" and then tried to run it from another computer. I got a System.Security.SecurityException. I then thought that was because of windows firewall, so I added client program into the exception list. But the error still occurs. I'm not very experienced in c# or windows programming, does anyone have any ideas?

When using a unidirectional server->client communication, if the server closes the pipe, the client keeps waiting in ReadFile() operation. The MSDN specified that the ReadFile() should exit in case of an error (which should be the case, because the connection is closed by the server) and return 0, but in this case it's blocked.

Have you made some performance comparison with sockets? My tests show that named pipes speed is almost as good as when using sockets, the difference being insignificant. It however depends on the specific implementation and the message that are sent across.

The sample solution that I have provided reuses the server pipes but on the client a new pipe connection is created for each request and then closed when the response is received.

It should be faster if you don't close the client connections but this means keeping a persistent connection to the server, which in general should be fine with one client and one server.

If you have any performance test results, especially in comparison with sockets please post them here.

When you say testing are you:
1. testing the client and server on the same machine
2. across the LAN
3. across the Internet?

...because named pipes in 1. is faster than TCP in the same scenario because named pipes will run in Kernal Mode - no network layer to worry about.

In #2, there is little difference between TCP and NP.
In #3, Named Pipes is dreadful. The reason is because the sender is only as fast as the weakest link in the network chain. i.e. the client running on a remote computer over the Internet. THe server can only send as fast as the client will extract bytes off the NP. It is sort of like a synchronous queue.

It works when the pipe server and client are in 2 windows forms application.
It still work when the server pipe is a Windows Service and the client pipe is in a Windows Form application.
It doesn't work when the server pipe is a Windows Service and the client pipe is in a web component called from an aspx page.
Does someone could help me ???
Thanks.

When running this code with a windows service, the service is able to start and does the operations(Well actually it says it's not able to start but is really running, and the status is continually shown as "starting" instead of "started"), and there isn't any way for the service to stop unless by rebooting the computer. Do you have any clue to why that is?

I attempted to open this with Visual Studio.NET, 7.0, and got an error saying solution wasn't a valid solution. Then when I attempted to open the .csproj files, I was told the projects were created with a more recent version of Visual Studio.

I upgraded to the 7.1, but got similar results. Is anyone else experiencing this problem?

Admittedly, I'm new to this, but I have a server form that creates the pipe. If I set up the client as a form, I can pass messages between the two with no problem. But if I want to have a web service send messages to the server form, using the same code as the client form to connect to the pipe, I get an error message of the following:

"Error connecting to pipe \\.\\pipe\pipename"

Does this have anything to do with trying to utilize the pipe from an IIS-hosted object such as a web service?

Again, this exact same code in a windows form works beautifully (you got my 5 star!). Just a shot in the dark here but could it be that the web service may need to impersonate the same user as the form?

The web service cannot access the server form's named pipe under the ASPNET account. You have to impersonate the identity of that which the server form is using. It would be nice if there was a way to programmatically set on the server the various accounts to which the named pipe is accessible, rather than have to impersonate from the web service.