Background
I’ve used TCP Transport to make my projects work. Some of my projects are working separately on different machines which use WCF to get communication with each other. For example, we need get Market Data from Bloomberg’s service (it’s legal), and then transfer real-time these data to other apps which are installed on my colleagues’ PC. In this case, WCF TCP transport is really a good choice. But I even use TCP transport when the data are only needed to be transferred on the same PC. In fact, it works well as I wished. But I ignored another problem that anyone else can also access this PC’s WCF service. It could be very dangerous if a hacker wanted our business down. I revised WCF transports once again and I found another method which might be of help to escape from hacker’s attack. That’s WCF Named Pipe Transport.
Introduction
MSDN’s definition, when to use the Named Pipe Transport?
- A named pipe is an object in the Windows operating system kernel, such as a section of shared memory that processes can use for communication. A named pipe has a name, and can be used for one-way or duplex communication between processes on a single machine.
- When communication is required between different WCF applications on a single computer, and you want to prevent any communication from another machine, then use the named pipes transport. An additional restriction is that processes running from Windows Remote Desktop may be restricted to the same Windows Remote Desktop session unless they have elevated privileges.
A named pipe itself is just an object in the window kernel. It provides access for different clients to a shared resource. A pipe is identified by its name and can be used for one-way or duplex communication scenarios. While there may be only one pipe server, there may be more than one client applications using a pipe. With the NetNamedPipeBinding class, WCF offers an abstraction to pipe objects, making it really easy to use them for inter-process communication. I’m trying write a sample. I encountered lot of questions. I looked up several articles and the book written by Juval Löwy, I found some good articles:
Sample
Here is a sample that I’m writing, this sample serves for creating a communication route for several processes running on the same machine. (Sample can only be downloaded from CodeProject page.)
In the sample, it contains two client WPF application (Client1
and Client2
), one host WPF application (WpfWcfNamedPipeBinding
). Clients can send message to host, in return, host can also send message to clients. Client1
can also send messages to Client2
through the host.
During the development, here are some questions that I didn’t understand ————-
- I cannot create a
namedPipeBinding
Duplex Service Reference. The Client test WCF says that the Metadata contains a reference cannot be resolved. Once I removed CallbackContract
from the service contract, it works good. So I had to create my own client proxy instead of Visual Studio. (SessionMode.Required
& CallbackContract
& InstanceContextMode.PerSession
)
Create a WCF service library. Write a communication contract for this service.
I’d like to have a duplex contract, because it’s more practice. For example, we have a server. Client1
requests some data from server. The callback function will be called from the server after client requested further information and the server had enough time to retrieve them.
MSDN: A duplex service, therefore, can send messages back to the client endpoint, providing event-like behavior. Duplex communication occurs when a client connects to a service and provides the service with a channel on which the service can send messages back to the client. Note that the event-like behavior of duplex services only works within a session. To create Duplex contract, you need to create a pair of interfaces, a service contract and a callback contract. The callback contract is the interface that defines the operations that the service can call on the client endpoint. A duplex contract does not require a session, although the system-provided duplex bindings make use of them. To define a callback contract, the ServiceContract
attribute offers the CallbackContract
property of the type Type
.
[ServiceContract(SessionMode = SessionMode.Required,
CallbackContract = typeof(INamedPipeBindingCallbackService))]
public interface INamedPipeBindingService
[ServiceContract]
public interface INamedPipeBindingCallbackService
In this example, the service uses the PerSession
instance mode to maintain the result for each session. Because server also needs to send message to client without client’s request, the server needs to maintain the connect session of client. And this is set in the implementation of service contract:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class NamedPipeBindingService : INamedPipeBindingService
Below is the full service contract interface defined in this sample. It contains a service contract and a callback service contract. In the OperationContract
attribute, I set IsOneWay = true
, because the service may want to invoke the callback reference that’s passed in during the execution of a contract operation. However such invocations are disallowed by default. By default, the service class is configured for single-threaded access: the service instance context is associated with a lock, and only one thread at a time can own the lock and access the service instance inside that context. Calling out to the client during an operation call requires blocking the service thread and invoking the callback. The problem is that processing the reply message from the client on the same channel once the callback returns requires reentering the same context and negotiating ownership of the same lock, which will result in a deadlock. Note that the service may still invoke callbacks to other clients or call other services; it is the callback to its calling client that will cause the deadlock. Set IsOneWay = true
is a method to avoid deadlock. Instead of two other methods, set IsOneWay = true
can maintain the service as single-threaded without release the lock. Set IsOneWay = true
enables the service to call back even when the concurrency mode is set to single-threaded, because there will not be any reply message to contend for the lock.
[ServiceContract(SessionMode = SessionMode.Required,
CallbackContract = typeof(INamedPipeBindingCallbackService))]
public interface INamedPipeBindingService
{
[OperationContract(IsOneWay = true)]
void Message(CommunicatUnit composite);
#region Callback Connection Management
[OperationContract(IsOneWay = true)]
void Connect();
[OperationContract(IsOneWay = true)]
void Disconnect();
#endregion
}
[ServiceContract]
public interface INamedPipeBindingCallbackService
{
[OperationContract(IsOneWay = true)]
void Message(CommunicatUnit composite);
}
In the service contract, there is a region “Callback Connection Management“. It is because of the mechanism of callback supplied by WCF, we have to come up ourselves with some application-level protocol or a consistent pattern for managing the life cycle of the connection. The service can only call back to the client if the client-side channel is still open, which is typically achieved by not closing the proxy. Keeping the proxy open will also prevent the callback object from being garbage-collected. You may always want to add the Connect()
and Disconnect()
pair on a sessionful service simply as a feature, because it enables the client to decide when to start or stop receiving callbacks during the session.
If the services maintain a reference on a callback endpoint and the client-side proxy is closed or the client application itself is gone, when the service invokes the callback, it will get an ObjectDisposedException
from the service channel. It is therefore preferable for the client to inform the service when it no longer wishes to receive callbacks or when the client application is shutting down. In NamedPipeBindingService
, InstanceContextMode = PerSession
, read comments written on NamedPipeBindingService
to get more information.
After the creation of service contract, it turns to implement contracts. You could find implementation of contract in the sample’s source code. For complete namedPipeBinding
service part, all we need to do is just define service endpoints.
Service Endpoint, this is always an obstacle for my WCF study, and this time I decided to read all pages about Endpoints from book of Juval Löwy.
- Every end point must have three elements,
Address
, Contract
and Binding
, and the host exposes the endpoint. Logically, the endpoint is the service’s interface and is analogous to a CLR or COM interface. - All endpoints on a service have unique address, and a single service can expose multiple endpoints.
- Use fully qualified type names for specifying the service and the contract type.
- If the endpoint provides a base address, that address scheme must be consistent with the binding, such as HTTP with
WSHttpBinding
- Administrative configuration (App.config) provides the flexibility to change the service address, binding, and even exposed contracts without rebuilding and redeploying the service.
- Use
bindingConfiguration
to enable transaction propagation. The default binding implicitly configures all endpoints that don’t explicitly reference a binding configuration. Only on default binding configuration per binding type. - For IPC namedPipedBinding, it seems that we should not define a port like “net.tcp//localhost:10001/”.
Metadata Exchange endpoint. Two options for publishing a service’s metadata: Metadata over HTTP-GET
and use a dedicated endpoint. Publishing metadata over HTTP-GET
is merely a WCF feature, there are no guarantees that other platforms you interact with will support it. Publishing the metadata over a special endpoint is a better way.
WCF provides dedicated binding transport elements for the HTTP, HTTPS, TCP, and IPC protocols. There is no need to enable the HTTP-GET
option. The host implements the MEX endpoint for your service to include the serviceMetadata
tag in the behavior.
<system.serviceModel>
<bindings>
<netNamedPipeBinding>
<binding name="NamedPipeBinding_INamedPipeBindingService"
closeTimeout="00:05:00" openTimeout="00:20:00"
receiveTimeout="00:20:00"
sendTimeout="00:20:00" transactionProtocol="OleTransactions"
hostNameComparisonMode="StrongWildcard" maxConnections="10"
maxBufferPoolSize="50000000" maxBufferSize="50000000"
maxReceivedMessageSize="50000000">
<readerQuotas maxDepth="32" maxStringContentLength="50000000"
maxArrayLength="50000000" maxBytesPerRead="50000000"
maxNameTableCharCount="50000000" />
<security mode="Transport" />
</binding>
</netNamedPipeBinding>
</bindings>
<services>
<service name="WcfServiceLibraryNamedPipe.NamedPipeBindingService"
behaviorConfiguration="MEX">
<endpoint address="net.pipe://localhost/WcfServiceLibraryNamedPipe/NamedPipeBindingService"
binding="netNamedPipeBinding"
bindingConfiguration="NamedPipeBinding_INamedPipeBindingService"
contract="WcfServiceLibraryNamedPipe.INamedPipeBindingService" />
<endpoint address=
"net.pipe://localhost/WcfServiceLibraryNamedPipe/NamedPipeBindingService/mex"
binding="mexNamedPipeBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MEX">
<serviceMetadata httpGetEnabled="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Host
Once service contract and callback contract are defined, we can create a host for making this named pipe service work. Here is nothing special than others, this sample uses ServiceHost
class for creating and maintaining the host service. Just one thing, this sample recreated the implementation of service contract in the project of host, so please review the Administrative configuration. In the host’s implementation of service contract, we still set InstanceContextMode = PerSession
(as mentioned above, it specifies the lifetime of the InstanceContext
object, WCF can create a new InstanceContext
object for every session, InstanceContext
manages the association between the channel and the user-defined service objects).
In the implementation of contract service, it records every client’s callback channel reference. Host uses these callback channel references to resend messages to clients.
Client
Instead of generating proxy with Visual Studio, my sample creates its own client proxy because I didn’t successfully add service reference in my client project. This sample uses DuplexChannelFactory
to connect to host. Client1
in the sample uses the implementation of callback service contract to be the first parameter of constructor of DuplexChannelFactory
, client2
in the sample uses InstanceContext
to be the parameter.
It also needs to define endpoint name for creating the duplex channel between clients and host. But it is easier than the host side. We just need to add client
endpoint to let the program find its channel’s enter.
</pre>
<system.serviceModel>
<client>
<endpoint address="net.pipe://localhost/WpfWcfNamedPipeBinding/NamedPipeBindingService"
binding="netNamedPipeBinding"
contract="WcfServiceLibraryNamedPipe.INamedPipeBindingService"
name="NamedPipeBindingServiceEndpoint" />
</client>
</system.serviceModel>
<pre>
Once the basic acknowledge is clearly understood, the left work is just easy. Please review the code source of the sample for a better understanding.
Points of Interest
I didn’t achieve processes inter communication with a service reference, because I encountered problems. But I use another method to realize WCF duplex namedPipeBinding
communication between processes.
This article didn’t concentrate to present my sample, instead of it, this article talked more about basic WCF acknowledge. We can write a WCF application easily with samples on CodeProject. But we should be responsible for our project!