会话和队列

会话示例演示如何通过消息队列(MSMQ)传输在排队通信中发送和接收一组相关消息。 此示例使用 netMsmqBinding 绑定。 此服务是自承载控制台应用程序,通过它可以观察服务接收排队消息。

注释

本示例的设置过程和生成说明位于本主题末尾。

在排队通信中,客户端使用队列与服务通信。 更确切地说,客户端将消息发送到队列。 服务从队列接收消息。 因此,服务与客户端不必同时运行,才能使用队列进行通信。

有时,客户端会发送一组与组中彼此相关的消息。 当消息必须一起处理或按指定顺序处理时,队列可用于将它们组合在一起,以便由单个接收应用程序进行处理。 当一组服务器上有多个接收应用程序,并且必须确保同一接收应用程序处理一组消息时,这一点尤其重要。 排队会话是一种机制,用于发送和接收一组必须一次性全部处理的相关消息。 排队的会话需要一个展示此模式的事务。

在示例中,客户端将大量消息作为单个事务范围内的会话的一部分发送到服务。

服务协定是IOrderTaker,它定义了一种适合用于队列的单向服务。 SessionMode 在以下示例代码中显示的合约中使用,表示这些消息是会话的一部分。

[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode=SessionMode.Required)]
public interface IOrderTaker
{
    [OperationContract(IsOneWay = true)]
    void OpenPurchaseOrder(string customerId);

    [OperationContract(IsOneWay = true)]
    void AddProductLineItem(string productId, int quantity);

    [OperationContract(IsOneWay = true)]
    void EndPurchaseOrder();
}

该服务以这样的方式定义服务操作:第一个操作加入事务中,但不会自动完成该事务。 后续操作也在同一个事务中登记,但也不自动完成事务。 会话中的最后一个操作会自动完成该事务。 因此,对服务协定中的若干个操作调用使用了同一个事务。 如果其中任何一个操作引发异常,则事务将回滚,并且会话将返回到队列中。 成功完成最后一个操作后,事务即已提交。 该服务使用PerSession作为InstanceContextMode,在同一服务实例上接收会话中的所有消息。

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class OrderTakerService : IOrderTaker
{
    PurchaseOrder po;

    [OperationBehavior(TransactionScopeRequired = true,
                                 TransactionAutoComplete = false)]
    public void OpenPurchaseOrder(string customerId)
    {
        Console.WriteLine("Creating purchase order");
        po = new PurchaseOrder(customerId);
    }

    [OperationBehavior(TransactionScopeRequired = true,
                                  TransactionAutoComplete = false)]
    public void AddProductLineItem(string productId, int quantity)
    {
        po.AddProductLineItem(productId, quantity);
        Console.WriteLine("Product " + productId + " quantity " +
                            quantity + " added to purchase order");
    }

    [OperationBehavior(TransactionScopeRequired = true,
                                  TransactionAutoComplete = true)]
    public void EndPurchaseOrder()
    {
       Console.WriteLine("Purchase Order Completed");
       Console.WriteLine();
       Console.WriteLine(po.ToString());
    }
}

该服务是自托管的。 使用 MSMQ 传输时,必须提前创建使用的队列。 这可以手动或通过代码完成。 在此示例中,服务包含 System.Messaging 用于检查队列是否存在的代码,并在必要时创建它。 队列名称使用 AppSettings 类从配置文件中读取。

// Host the service within this EXE console application.
public static void Main()
{
    // Get MSMQ queue name from app settings in configuration.
    string queueName = ConfigurationManager.AppSettings["queueName"];

    // Create the transacted MSMQ queue if necessary.
    if (!MessageQueue.Exists(queueName))
        MessageQueue.Create(queueName, true);

    // Create a ServiceHost for the OrderTakerService type.
    using (ServiceHost serviceHost = new ServiceHost(typeof(OrderTakerService)))
    {
        // Open the ServiceHost to create listeners and start listening for messages.
        serviceHost.Open();

        // The service can now be accessed.
        Console.WriteLine("The service is ready.");
        Console.WriteLine("Press <ENTER> to terminate service.");
        Console.WriteLine();
        Console.ReadLine();

        // Close the ServiceHost to shutdown the service.
        serviceHost.Close();
    }
}

MSMQ 队列名称是在配置文件的 appSettings 部分中指定的。 服务的终结点在配置文件的 system.serviceModel 节中定义,并指定 netMsmqBinding 绑定。

<appSettings>
  <!-- Use appSetting to configure MSMQ queue name. -->
  <add key="queueName" value=".\private$\ServiceModelSamplesSession" />
</appSettings>

<system.serviceModel>
  <services>
    <service name="Microsoft.ServiceModel.Samples.OrderTakerService"
        behaviorConfiguration="CalculatorServiceBehavior">
      ...
      <!-- Define NetMsmqEndpoint -->
      <endpoint address="net.msmq://localhost/private/ServiceModelSamplesSession"
                binding="netMsmqBinding"
                contract="Microsoft.ServiceModel.Samples.IOrderTaker" />
      ...
    </service>
  </services>
  ...
</system.serviceModel>

客户端创建事务范围。 会话中的所有消息都将发送到该事务范围中的队列,使其被视为一个原子单元,其中的所有消息都将成功或失败。 通过调用 Complete 提交事务。

//Create a transaction scope.
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
    // Create a client with given client endpoint configuration.
    OrderTakerClient client = new OrderTakerClient("OrderTakerEndpoint");
    // Open a purchase order.
    client.OpenPurchaseOrder("somecustomer.com");
    Console.WriteLine("Purchase Order created");

    // Add product line items.
    Console.WriteLine("Adding 10 quantities of blue widget");
    client.AddProductLineItem("Blue Widget", 10);

    Console.WriteLine("Adding 23 quantities of red widget");
    client.AddProductLineItem("Red Widget", 23);

    // Close the purchase order.
    Console.WriteLine("Closing the purchase order");
    client.EndPurchaseOrder();

    //Closing the client gracefully closes the connection and cleans up resources.
    client.Close();

    // Complete the transaction.
    scope.Complete();
}

注释

你只能为会话中的所有消息使用单个事务,并且必须在提交事务之前发送会话中的所有消息。 关闭客户端会关闭会话。 因此,必须在完成事务之前关闭客户端,以便向队列发送会话中的所有消息。

运行示例时,客户端和服务活动会显示在服务和客户端控制台窗口中。 可以看到服务从客户端接收消息。 在每个控制台窗口中按 Enter 可以关闭服务和客户端。 由于队列正在使用中,因此客户端和服务不必同时启动和运行。 即使关闭客户端再启动服务,仍然可以接收到消息。

在客户端上。

Purchase Order created
Adding 10 quantities of blue widget
Adding 23 quantities of red widget
Closing the purchase order

Press <ENTER> to terminate client.

在服务上。

The service is ready.
Press <ENTER> to terminate service.

Creating purchase order
Product Blue Widget quantity 10 added to purchase order
Product Red Widget quantity 23 added to purchase order
Purchase Order Completed

Purchase Order: 7c86fef0-2306-4c51-80e6-bcabcc1a6e5e
        Customer: somecustomer.com
        OrderDetails
                Order LineItem: 10 of Blue Widget @unit price: $2985
                Order LineItem: 23 of Red Widget @unit price: $156
        Total cost of this order: $33438
        Order status: Pending

设置、生成和运行示例

  1. 确保已为 Windows Communication Foundation 示例 执行One-Time 安装过程。

  2. 若要生成解决方案的 C#、C++ 或 Visual Basic .NET 版本,请按照 生成 Windows Communication Foundation 示例中的说明进行作。

  3. 若要在单台计算机或跨计算机配置中运行示例,请按照 运行 Windows Communication Foundation 示例中的说明进行操作。

默认情况下使用 NetMsmqBinding 启用传输安全。 MSMQ 传输安全性有两个相关属性, MsmqAuthenticationModeMsmqProtectionLevel.默认情况下,身份验证模式设置为Windows,保护级别设置为 。Sign 要使 MSMQ 提供身份验证和签名功能,它必须是域的一部分,并且必须安装 MSMQ 的 Active Directory 集成选项。 如果在不符合这些条件的计算机上运行此示例,则会收到错误。

在加入工作组的计算机上运行示例

  1. 如果计算机不是域的一部分,或者未安装 Active Directory 集成,请将身份验证模式和保护级别设置为 None 关闭传输安全性,如以下示例配置所示。

    <system.serviceModel>
      <services>
        <service name="Microsoft.ServiceModel.Samples.OrderTakerService"
                 behaviorConfiguration="OrderTakerServiceBehavior">
          <host>
            <baseAddresses>
              <add baseAddress=
             "http://localhost:8000/ServiceModelSamples/service"/>
            </baseAddresses>
          </host>
          <!-- Define NetMsmqEndpoint -->
          <endpoint
              address=
            "net.msmq://localhost/private/ServiceModelSamplesSession"
              binding="netMsmqBinding"
              bindingConfiguration="Binding1"
           contract="Microsoft.ServiceModel.Samples.IOrderTaker" />
          <!-- The mex endpoint is exposed at-->
          <!--http://localhost:8000/ServiceModelSamples/service/mex. -->
          <endpoint address="mex"
                    binding="mexHttpBinding"
                    contract="IMetadataExchange" />
        </service>
      </services>
    
      <bindings>
        <netMsmqBinding>
          <binding name="Binding1">
            <security mode="None" />
          </binding>
        </netMsmqBinding>
      </bindings>
    
        <behaviors>
          <serviceBehaviors>
            <behavior name="OrderTakerServiceBehavior">
              <serviceMetadata httpGetEnabled="True"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
    
      </system.serviceModel>
    
  2. 在运行示例之前,请确保在服务器和客户端上更改配置。

    注释

    将安全模式设置为None等效于设置MsmqAuthenticationModeMsmqProtectionLevel,并将Message安全性设置为 None