One-Way and Duplex Communication
A message exchange pattern describes the way messages are sent between applica-tions.
There are three classic message exchange patterns: request-reply, one-way, and duplex (callbacks).
Figure 3-7 illustrates these patterns.
So far in this book, I have focused on examples that use the classic request-reply pattern, that is, service operations that receive parameters and return values synchronously while clients wait for the response.
There are cases where clients want to send messages and do not need a response—that is, the operation performs a function that can succeed or fail and the client either does not care, or does not care right away.
In other cases, communication may be initiated at the other end of a communication channel—for example, a service sending unsolicited messages to a client application.
This type of communication implies two-way communication between client and service, and it requires the support of a binding that can support duplex communications.
Figure 3-7. Message exchange patterns
One-way operations are useful because once the message reaches the receiving end, the client connection is released so that the client can continue doing other work.
Furthermore, they won’t be affected if a problem occurs at the other end, since no exceptions are reported.
One-way messages are also known as asynchronous messages.
Duplex or two-way operations play a key role in applications that send notifications,issue events, or implement some form of publish-subscribe pattern.
In this section, I’ll explain how to implement one-way operations and duplex patterns while also discussing the following concepts:
• How to implement message exchange patterns with WCF
• Scenarios for one-way operations and duplex communications
• WSDualHttpBinding features
• Supporting callbacks
• Concurrency issues for the service and client
Before you do a lab that shows you how to work with one-way and duplex communications, I’ll spend some time explaining message exchange patterns and callback contracts.
After you complete the lab, I’ll talk about WSDualHttpBinding in greater detail and discuss related duplex communication topics.
Message Exchange Patterns
You can implement all three message exchange patterns, request-reply, one-way and duplex, using service contracts.
Service contracts expose these messaging patterns as follows:
• Request-reply is the default behavior for service operations that are not labeled as one-way operations.
• One-way operations are labeled using the OperationContractAttribute.
• Duplex communication is possible when a callback contract is supplied to the service contract, and a binding that supports duplex is selected.
Request-reply
By default, all service operations use the request-reply pattern—even if they don’t explicitly return a result (returning void).
The following two operation signatures are valid examples of request-reply operations:
[OperationContract] string HelloIndigo(string message); [OperationContract] void HelloIndigo(string message);
With this pattern, clients will block until they receive a reply or time out.
At the service, the dispatcher waits for the operation to complete before sending a reply.
If an exception occurs at any point during processing at the service, a SOAP fault (see Chapter 8) is included in the reply.
One-way
The OperationContractAttribute has an IsOneWay property that defaults to false.
You can set it to true for operations that return void, as shown here:
[OperationContract(IsOneWay=true)] void HelloIndigo(string message);
This setting turns a request-reply operation into a one-way operation.
With one-way operations, the service model releases the client connection once the message arrives to the channel and before invoking the operation.
That means the client doesn’t block; the call is asynchronous.
That also means that clients do not receive any notification of exceptions that occur.
Note:
Although exceptions are not propagated to the client, an uncaught exception at the service will cause the server channel to fault.
When sessions are involved, subsequent calls to the service will fail and the client channel is rendered useless.
Sessions are discussed in Chapter 5.
One-way operations are useful in many scenarios, including:
Fire-and-forget
Operations that need not report success or failure, such as logging or auditing activities or event publishing.
Polling
Long running operations where clients can find out the results of the operation through another request.
Duplex
One-way operations should be used for callbacks and publish-subscribe scenarios where the service may need to call the client independent of a particular request.
Duplex
Duplex communication refers to bi-directional flow between a client and service.
To implement this pattern, service contracts provide a callback contract.
A callback contract looks just like a service contract; it is an interface that has methods marked with
the OperationContractAttribute as shown here:
public interface IHelloIndigoServiceCallback
{
[OperationContract]
void HelloIndigoCallback(string message);
}
Notice that the ServiceContractAttribute is missing.
That's because it is implied by the usage of this contract.
Alone, the contract is not a callback contract.
It becomes one when it is associated to a service contract implemented by a service type.
The ServiceContractAttribute has a CallbackContract property that can be associated with an interface that contains service operations.
This example associates IHelloIndigoServiceCallback as the callback contract for the IHelloIndigoService service contract:
[ServiceContract(Namespace="http://www.thatindigogirl.com/samples/2006/06",
CallbackContract=typeof(IHelloIndigoServiceCallback))]
public interface IHelloIndigoService
Operations defined in a callback contract are not required to be one-way;
however,they should be one-way to avoid contention, as shown here:
public interface IHelloIndigoServiceCallback
{
[OperationContract(IsOneWay=true)]
void HelloIndigoCallback(string message);
}
Likewise, for service contracts that implement a callback contract, any operation that will call back to the client should be designed as a one-way operation. I’ll talk about this after the lab.
Both TCP protocol and named pipes support bidirectional flow.
TCP protocol supports full-duplex messaging, while named pipes are half-duplex—meaning messages can move in both directions, but not simultaneously.
For both of these protocols, the network connection can support calls initiated by the service to the client. Consequently, NetNamedPipeBinding and NetTcpBinding support callback contracts.
HTTP protocol does not support duplex communication—responses are valid only for each request.
This prevents HTTP-based bindings such as BasicHttpBinding and WSHttpBinding from supporting duplex.
To address this limitation, WCF provides WSDualHttpBinding.
This binding uses composite duplex communication, which means two communication channels are created to support calls in both directions.
I’ll more talk about this binding shortly.
Working with Callbacks
To define a callback contract, you create an interface that implements one or more operations decorated with the OperationContractAttribute.
Though not required,these operations should be one-way to reduce the potential for deadlocks.
This shouldn’t have any impact since the service shouldn’t generally care if an exception takes place at the client during a callback.
Callbacks are customarily fire-and-forget operations.
Once associated with a service contract, the callback contract is emitted as part of the WSDL document for the service so that clients can generate an equivalent callback contract during proxy generation.
Note:
Recall from Chapter 2 that WSDL describes each operation with <input>, <output>, and <fault> elements.
Callback contracts add operations to the WSDL document that have no <input>, one <output>,and may include <fault> elements.
Duplex proxies
Proxy generation produces slightly different results for duplex scenarios:
• Configuration settings for each endpoint are generated as expected.
• A client-side version of the service contract and the callback contract are created.
• The proxy inherits DuplexClientBase<T> instead of ClientBase<T>.
DuplexClientBase<T> provides a special constructor that requires a reference to the callback contract implementation, wrapped in an InstanceContext, as shown here:
public partial class HelloIndigoContractClient :
System.ServiceModel.DuplexClientBase<Client.localhost.HelloIndigoContract>,
Client.localhost.HelloIndigoContract
{...}
DuplexChannelFactory<T>
You can also create the duplex channel proxy at the client directly, using DuplexChannelFactory<T> instead of ChannelFactory<T>.
As with DuplexClientBase<T>,the constructor of the factory requires you to pass the callback instance, as shown here:
CallbackType cb = new CallbackType();
InstanceContext context = new InstanceContext(cb);
WSDualHttpBinding binding = new WSDualHttpBinding();
binding.ClientBaseAddress=new Uri("http://localhost:8100");
DuplexChannelFactory<IHelloIndigoService> factory = new DuplexChannelFactory<IHelloIndigoService>(context,binding, new EndpointAddress("http://localhost:8000/wsDual"));
IHelloIndigoService proxy = factory.CreateChannel();
Note:
A sample that uses DuplexChannelFactory<T> to construct the duplex channel can be found here: <YourLearningWCFPath>SamplesBindingsDuplexChannelFactory.
Callback instance
Clients must provide an implementation for the callback contract.
You can implement the callback contract on any type.
After you construct an instance of this type,you wrap it in a InstanceContext for the proxy to reference. Ultimately, this creates a client endpoint where the service can send messages.
CallbackType cb = new CallbackType();
InstanceContext context = new InstanceContext(cb);
The lifetime of the client endpoint is critical to two-way communication with the service.
By default, its lifetime will match the lifetime of the proxy.
So, if you keep the proxy alive for the duration of the application, the service will be able to issue it callbacks for the same duration (assuming no other problems fault the client channel).
When the proxy is closed or disposed through garbage collection, the client endpoint is destroyed along with it.
Note:
Services have no control over the way clients manage the callback instance lifetime.
Thus services should catch any exceptions when issuing callbacks and verify that the callback channel has not faulted after every call.
Channel state is discussed in Chapter 5.
Client endpoint
Duplex proxies generate an endpoint for services to call over the established communication channel.
Duplex protocols such as named pipes or TCP implicitly support callbacks.
It isn’t necessary to indicate an explicit endpoint for the service to issue callbacks to the client over these protocols.
For HTTP, however, callbacks can work only if the client explicitly creates an endpoint (like a service endpoint) to receive messages.
In other words, another communication channel must be created for calls in the opposite direction.
DuplexClientBase<T> handles creating the client endpoint, and WSDualHttpBinding provides a property to configure the base address for this endpoint.
When this base address isn’t specified, one is generated on your behalf using port 80 and with a temporary address that includes a unique GUID:
http://localhost:80/Temporary_Listen_Addresses/7e7e9b66-2112-473d-8401-a8404fde6a8d/
This is a problematic for machines with IIS installed and running on port 80, and in general it can be a problem since other applications may also consume specific ports.
To provide a base address declaratively, you use the configuration setting shown here:
<wsDualHttpBinding>
<binding name="WSDualHttpBinding_HelloIndigoContract" clientBaseAddress="http://localhost:8100" />
</wsDualHttpBinding>
You can also set this property dynamically in code:
WSDualHttpBinding binding = m_proxy.Endpoint.Binding as WSDualHttpBinding;
binding.ClientBaseAddress = new Uri("http://localhost:8100");
Practically speaking, declarative configuration offers more flexibility.
You should avoid hardcoding ports in the client code.
You could also do interesting things such as dynamically finding an open port within a particular range to support the client endpoint.
=====华丽的分割线====
In this lab, you will explore duplex communication over NetTpcBinding and WSDualHttpBinding.
First, you will design a callback contract;
associate it to a service contract;
and implement the service contract and related code inside the service to issue callbacks.
You’ll host this service in a console application and consume it from two clients—a console application and a Windows application—to compare results.
In the process, you’ll test the differences between using request-reply and one-way where callbacks are concerned and compare the functionality of the two bindings.
Designing the callback contract
The first thing you’ll do is create a new callback contract.
You’ll create an interface with service operations representing the callback operation.
1. Open the startup solution for this lab, located in
<YourLearningWCFPath>LabsChapter3DuplexDuplex.sln.
This solution contains a partially completed service project, service host, console client, and Windows Forms client.
2. First, implement the service starting with the callback contract.
Go to the HelloIndigo project and open HelloIndigoService.cs.
Add the following callback contract definition:
public interface IHelloIndigoServiceCallback
{
[OperationContract]
void HelloIndigoCallback(string message);
}
Note:
Callback contracts do not require a ServiceContractAttribute, but it would not cause any problems if the attribute were present.
The problem is that this could become confusing if you set the Name, and Namespace attributes for the attribute on the callback contract since they are ignored when the contract is used as a callback contract.
The callback contract inherits those settings from the service contract to which it is associated.
Operations must still be decorated with the OperationContractAttribute to be included in the WSDL document.
3. Associate this callback contract with the service contract so that its metadata will be included in the service description and WSDL document.
Do this by setting the CallbackContract property of the ServiceContractAttribute, as shown here:
[ServiceContract(Name="HelloIndigoContract",Namespace="http://www.thatindigogirl.com/samples/2006/06",CallbackContract=typeof(IHelloIndigoServiceCallback))] public interface IHelloIndigoService
Now you have a callback contract, and have associated it with the service contract.
The next step is to write code in the service to issue callbacks.
Issuing callbacks
The service type has already been created, but the HelloIndigo() operation has not yet been implemented.
Now you’ll add code to the method that issues callbacks to clients.
1. When clients create a proxy for a service contract that supports callbacks, they are required to implement the callback contract and supply a channel to receive callbacks.
The client proxy will send information about the callback endpoint with each call to the service.
You can access this through the OperationContext GetCallbackChannel<T>() method.
This returns a strongly typed reference to a proxy that can be used to invoke any callback operation at the client.
The code to add to HelloIndigo() is shown here in bold:
public void HelloIndigo(string message)
{
IHelloIndigoServiceCallback callback =
OperationContext.Current.GetCallbackChannel<IHelloIndigoServiceCallback>();
callback.HelloIndigoCallback(message);
}
Note:
Each operation that supports callbacks will have similar code.
Services can also save a reference to the callback endpoint and invoke callbacks outside the context of a call from the client—for example, in publish-subscribe scenarios (I’ll discuss this after the lab).
2. Build the HelloIndigo service project to verify it compiles.
Choosing a duplex-compatible binding
Now you will host the service using the Host console application.
In fact, the project already contains the necessary code to host the service and has a single endpoint con-
figuration over WSHttpBinding.
1. Test this implementation by running the Host project.
During ServiceHost initialization, an InvalidOperationException is thrown indicating that the service
contract requires a duplex binding, and WSHttpBinding is unable to support this.
2. Configure the endpoint to support duplex communication.
Go to the Host project and open the app.config file.
Edit the endpoint so that it uses NetTcpBinding, as shown here in bold:
<endpoint address="HelloIndigoService" binding="netTcpBinding" contract="HelloIndigo.IHelloIndigoService" />
3. Run the Host project again to verify there are no exceptions.
Leave it running for the next section of the lab.
Implementing the callback contract
Now you are going to implement the callback contract.
Recall that the WSDL document for the service will publish enough information to describe the callback contract for the service.
That means that SvcUtil will be able to generate an equivalent callback contract at the client and create a special type of proxy that supports duplex communication.
You’ll test this by adding a service reference to both clients.
1. Go to the Client project and add a service reference using the path http://localhost:8000 for the service URI and localhost as the namespace.
Do the same for WinClient.
2. Both clients now have proxies and configuration necessary to call the service.
In addition, they both have a copy of the service metadata associated with the callback—the callback contract.
You will implement the callback contract for both clients in order to provide an endpoint for the service to invoke.
Go to the Client project first and add a new class to the project.
Name the file CallbackType.cs.
Open the file and modify the class definition so that it implements the callback contract, as shown in Example 3-20.
When the callback is invoked, this code will write the current thread identifier to the console.
For this code to work, you must add a using statement for System.Threading.
Example 3-20. Implementing the callback contract on a separate type
using System.Threading;
class CallbackType: localhost.HelloIndigoContractCallback
{
public void HelloIndigoCallback(string message)
{
Console.WriteLine("HelloIndigoCallback on thread {0}",
Thread.CurrentThread.GetHashCode());
}
}
3. Construct the callback type in the Main() entry point, then construct the proxy and invoke HelloIndigo().
In duplex scenarios, the proxy constructor requires you to provide the callback instance wrapped in an InstanceContext type.
Example 3-21 shows the code to add to Program.cs to achieve this.
Note:The proxy generated for duplex contracts derives from a different base type, DuplexClientBase<T>.
This type takes the callback instance and ultimately turns it into a client endpoint that the service can reach.
Example 3-21. Code to construct the callback type and construct the proxy
using Client.localhost;
static void Main(string[] args)
{
Console.WriteLine("Client running on thread {0}",
Thread.CurrentThread.GetHashCode());
Console.WriteLine();
CallbackType cb = new CallbackType();
InstanceContext context = new InstanceContext(cb);
using (HelloIndigoContractClient proxy = new HelloIndigoContractClient(context))
{
Console.WriteLine("Calling HelloIndigo()");
proxy.HelloIndigo("Hello from Client.");
Console.WriteLine("Returned from HelloIndigo()");
Console.ReadLine();
}
}
4. Compile the solution and test this by running the Host and then the Client.
You’ll receive an InvalidOperationException at the service stating that the operation will deadlock.
“System.ServiceModel.AddressAlreadyInUseException”类型的未经处理的异常在 System.ServiceModel.dll 中发生
其他信息: HTTP 无法注册 URL http://+:8000/。另一应用程序已使用 HTTP.SYS 注册了该 URL。
That’s because the service is not reentrant or multithreaded.
If the service operation calls another service, it will not be able to return.
Note:
I’ll be talking about concurrency issues in Chapter 5, but I’ll broach the subject after the lab as it relates to the duplex discussion.
5. To fix this issue, add a ServiceBehaviorAttribute to the service type in HelloIndigoService.cs and set the ConcurrencyMode property to Reentrant, as shown here:
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)]
public class HelloIndigoService : IHelloIndigoService
6. Compile the solution and test the Host and Client again, this time it will work.
Notice that the callback operation is executed on a separate thread from the initial application thread, and that the callback is received before the call to HelloIndigo() returns from the service.
7. Now add some code to complete the WinClient application.
This time, you’ll implement the callback contract directly on Form1.
Open Form1.cs and add the code shown in Example 3-22.
Don’t forget the using statement for the proxy namespace.
Example 3-22. Implementing the callback contract on an existing type
using WinClient.localhost;
public partial class Form1 : Form, HelloIndigoContractCallback
{
// other code
public void HelloIndigoCallback(string message)
{
MessageBox.Show(string.Format("Received callback on thread {0}. {1}",
System.Threading.Thread.CurrentThread.GetHashCode(), message));
}
}
8. The code to invoke the service will be similar to the code for the Client application, but the scope of the proxy will be for the lifetime of the Windows application.
Inside Form1 type, add a proxy reference scoped to the lifetime of the form.
In the form constructor, create the InstanceContext type to wrap Form1 as the callback instance, and instantiate the proxy.
Lastly, add code to the cmdInvoke_Click() event handler to call HelloIndigo().
The resulting additions to Form1.cs are shown in bold in Example 3-23.
Example 3-23. Scoping the proxy and callback instance
public partial class Form1 : Form, HelloIndigoContractCallback
{
private HelloIndigoContractClient m_proxy = null;
public Form1()
{
InitializeComponent();
InstanceContext context = new InstanceContext(this);
m_proxy = new HelloIndigoContractClient(context);
}
private void cmdInvoke_Click(object sender, EventArgs e)
{
m_proxy.HelloIndigo("Hello from WinClient");
}
// other code
}
9. Compile the solution and test this by running the Host and then the WinClient application.
Click the Invoke Service button to test the callback.
The UI will become locked, and eventually you’ll receive a TimeoutException.
That’s because the client UI thread is blocking while the client calls in to the service, but the service is attempting to reach the client on the same thread.
Note:
With a Windows client, the callback implementation will synchronize with the UI thread by default.
That means the callback executes on the same thread as the primary thread for the application.
This is problematic if the calls to the service and callbacks are not one-way operations.
发送到 http://lujuntao/Temporary_Listen_Addresses/57e8d66a-162a-480a-ace5-560e742e4b06/391ce2c2-cbaf-4130-94a4-9f36d2881faa 的请求操作在配置的超时(00:00:57.7048687)内未收到回复。
分配给该操作的时间可能是更长超时的一部分。
这可能由于服务仍在处理操作或服务无法发送回复消息。
请考虑增加操作超时(将通道/代理转换为 IContextChannel 并设置 OperationTimeout 属性)并确保服务能够连接到客户端。
10. You can fix the deadlock problem by instructing the callback endpoint to run on a separate thread from the UI thread.
Open Form1.cs and apply the CallbackBehaviorAttribute to the callback type,setting its UseSynchronizationContext to false, as shown here:
[CallbackBehavior(UseSynchronizationContext=false)]
public partial class Form1 : Form, HelloIndigoContractCallback
11. Compile the solution and run the same test as before.
This time you’ll receive the callback on another thread without any contention.
Issuing callbacks over WSDualHttpBinding
不太清楚下面的这个section的作用,因为之前用的就是WSDualHttpBinding ,提供的示例代码,本来就用了
In this section,you will use WSDualHttpBinding to support callbacks and learn some of the required configuration settings to support it.
1. Now switch the binding from NetTcpBinding to WSDualHttpBinding.
Go to the Host project,open app.config and modify the endpoint configuration,as shown here:
<endpoint address="HelloIndigoService" binding="wsDualHttpBinding" contract="HelloIndigo.IHelloIndigoService" />
Update the configuration for both clients to reflect this change.
2. The next thing you’ll do is set an appropriate base address for the callback on the client.
Try to test the Client application first,by running the Host and then the Client.
You may receive an AddressAlreadyInUseException because the service model is attempting to register port 80 for the WSDualHttpBinding callback endpoint.
That’s because an application has already acquired that port,usually IIS.
Note:
If you don’t receive this error,port sharing may be enabled on the machine.
To address this,you’ll modify the binding configuration and supply a new base address for the callback endpoint.
Open the app.config for the Client project and edit the <wsDualHttpBinding> section to add the following bold setting for clientBaseAddress:
<wsDualHttpBinding>
<binding name="WSDualHttpBinding_HelloIndigoContract" clientBaseAddress="http://localhost:8100" ... >
<!-- other settings -->
</binding>
</wsDualHttpBinding>
Note:
ClientBaseAddress is a local setting for the client,so it is not provided through proxy generation.
To provide other than the default port 80,you must configure this declaratively or programmatically on the binding.
3. Compile the solution and run the same test. This time it should work as expected.
4. Now,configure the same setting for WinClient programmatically.
In the Form1 constructor,set the ClientBaseAddress property for the proxy by adding the following code shown in bold:
m_proxy = new HelloIndigoContractClient(context);
WSDualHttpBinding binding = m_proxy.Endpoint.Binding as WSDualHttpBinding;
if (binding!=null)
binding.ClientBaseAddress=new Uri("http://localhost:8101");
5. Compile the solution again and test the WinClient application again; it should
work as expected.