寄不出的信件佇列
無效信件範例會示範如何處理已傳遞失敗的訊息。 此範例是以交易 MSMQ 繫結範例作為基礎。 這個範例會使用 netMsmqBinding
繫結。 這個服務是自我裝載的主控台應用程式,可讓您觀察接收佇列訊息的服務。
注意
此範例的安裝程序與建置指示位於本主題的結尾。
注意
這個範例會示範每個應用程式的無效信件佇列,而此佇列只能在 Windows Vista 中使用。 您可以修改此範例,以便在 Windows Server 2003 和 Windows XP 上使用 MSMQ 3.0 的預設全系統佇列。
在佇列通訊中,用戶端會使用佇列與服務通訊。 更精確地說,用戶端會傳送訊息至佇列。 服務會接收來自佇列的訊息。 因此,服務與用戶端不需同時執行,就能使用佇列通訊。
因為佇列通訊會有一定的期間不活動,您可能需要在訊息上建立與某段存留時間值的關聯,以確保不會在過了這段時間,還將訊息傳遞到應用程式。 另有一些情況,應用程式必須獲知訊息是否傳遞失敗。 如果發生這些情況,像是訊息的存留時間已過,或是訊息傳遞失敗等,都會將訊息放在寄不出的信件佇列中。 進行傳送的應用程式就可以接著從寄不出的信件佇列讀取訊息,然後採取更正動作 (舉凡不做動作到修改傳遞失敗原因等),再重新傳送訊息。
在 NetMsmqBinding
繫結中,會使用下列屬性來表示寄不出的信件佇列:
DeadLetterQueue 屬性,用來表示用戶端所需之寄不出的信件佇列種類。 這個列舉具有下列值:
None
:沒有用戶端需要之寄不出的信件佇列。System
:系統之寄不出的信件佇列,用來存放無法傳遞的訊息。 電腦上執行的所有應用程式會共用系統之寄不出的信件佇列。Custom
:使用 CustomDeadLetterQueue 屬性所指定的自訂寄不出的信件佇列,用來存放無法傳遞的訊息。 這項功能僅於 Windows Vista 上提供。 當應用程式必須使用自己的寄不出的信件佇列,而不與執行於同一台電腦的其他應用程式共用時,會使用這項功能。CustomDeadLetterQueue 屬性,用來表示要當做寄不出的信件佇列使用的特定佇列。 這僅適用於 Windows Vista。
在這個範例中,用戶端會從異動範圍內傳送一批訊息至服務,並隨意為這些訊息指定很低的「存留時間」值 (約 2 秒)。 用戶端還會指定要使用的自訂寄不出的信件佇列,以便將過期的訊息加入。
用戶端應用程式可以讀取寄不出的信件佇列中的訊息,然後重新嘗試傳送訊息,或是修正導致原始訊息置入寄不出的信件佇列的錯誤,再傳送該訊息。 在範例中,用戶端會顯示錯誤訊息。
服務合約是 IOrderProcessor
,如下列範例程式碼所示。
[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples")]
public interface IOrderProcessor
{
[OperationContract(IsOneWay = true)]
void SubmitPurchaseOrder(PurchaseOrder po);
}
範例中的服務程式碼即是交易 MSMQ 繫結的服務程式碼。
與服務進行的通訊會在交易範圍內發生。 服務會讀取佇列中的訊息、執行作業,然後顯示作業的結果。 應用程式也會為寄不出的信件訊息建立寄不出的信件佇列。
//The service contract is defined in generatedClient.cs, generated from the service by the svcutil tool.
//Client implementation code.
class Client
{
static void Main()
{
// Get MSMQ queue name from app settings in configuration
string deadLetterQueueName = ConfigurationManager.AppSettings["deadLetterQueueName"];
// Create the transacted MSMQ queue for storing dead message if necessary.
if (!MessageQueue.Exists(deadLetterQueueName))
MessageQueue.Create(deadLetterQueueName, true);
// Create a proxy with given client endpoint configuration
OrderProcessorClient client = new OrderProcessorClient("OrderProcessorEndpoint");
// Create the purchase order
PurchaseOrder po = new PurchaseOrder();
po.CustomerId = "somecustomer.com";
po.PONumber = Guid.NewGuid().ToString();
PurchaseOrderLineItem lineItem1 = new PurchaseOrderLineItem();
lineItem1.ProductId = "Blue Widget";
lineItem1.Quantity = 54;
lineItem1.UnitCost = 29.99F;
PurchaseOrderLineItem lineItem2 = new PurchaseOrderLineItem();
lineItem2.ProductId = "Red Widget";
lineItem2.Quantity = 890;
lineItem2.UnitCost = 45.89F;
po.orderLineItems = new PurchaseOrderLineItem[2];
po.orderLineItems[0] = lineItem1;
po.orderLineItems[1] = lineItem2;
//Create a transaction scope.
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
// Make a queued call to submit the purchase order
client.SubmitPurchaseOrder(po);
// Complete the transaction.
scope.Complete();
}
client.Close();
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
}
用戶端的組態會指定很短的期間讓訊息送達服務。 如果無法在指定期間內傳輸訊息,訊息就會過期,然後移入寄不出的信件佇列。
注意
用戶端可能會在指定的時間內將訊息傳遞到服務佇列。 為確保您看得到寄不出的信件服務的實際執行過程,您必須先執行用戶端,再啟動服務。 此訊息會逾時,然後傳遞到寄不出的信件服務。
應用程式必須定義要當做寄不出的信件佇列使用的佇列。 如果未指定佇列,就會使用預設全系統交易式寄不出的信件佇列,將無法傳遞的訊息加入佇列。 在這個範例中,用戶端應用程式會指定它自己的應用程式寄不出的信件佇列。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!-- use appSetting to configure MSMQ Dead Letter queue name -->
<add key="deadLetterQueueName" value=".\private$\ServiceModelSamplesOrdersAppDLQ"/>
</appSettings>
<system.serviceModel>
<client>
<!-- Define NetMsmqEndpoint -->
<endpoint name="OrderProcessorEndpoint"
address="net.msmq://localhost/private/ServiceModelSamplesDeadLetter"
binding="netMsmqBinding"
bindingConfiguration="PerAppDLQBinding"
contract="IOrderProcessor" />
</client>
<bindings>
<netMsmqBinding>
<binding name="PerAppDLQBinding"
deadLetterQueue="Custom"
customDeadLetterQueue="net.msmq://localhost/private/ServiceModelSamplesOrdersAppDLQ"
timeToLive="00:00:02"/>
</netMsmqBinding>
</bindings>
</system.serviceModel>
</configuration>
寄不出的信件訊息服務會從寄不出的信件佇列讀取訊息。 寄不出的信件訊息服務會實作 IOrderProcessor
合約。 然而,這個實作不是要用來處理訂單。 寄不出的信件訊息服務是用戶端服務,並沒有處理訂單的能力。
注意
寄不出的信件佇列是用戶端佇列,而且是用戶端佇列管理員本機上的佇列。
寄不出的信件訊息服務實作會檢查訊息傳遞失敗的原因,然後採取更正措施。 訊息失敗的原因可以擷取自兩個列舉:DeliveryFailure 和 DeliveryStatus。 您可以從 MsmqMessageProperty 擷取 OperationContext,如下列範例程式碼所示:
public void SubmitPurchaseOrder(PurchaseOrder po)
{
Console.WriteLine("Submitting purchase order did not succeed ", po);
MsmqMessageProperty mqProp =
OperationContext.Current.IncomingMessageProperties[
MsmqMessageProperty.Name] as MsmqMessageProperty;
Console.WriteLine("Message Delivery Status: {0} ",
mqProp.DeliveryStatus);
Console.WriteLine("Message Delivery Failure: {0}",
mqProp.DeliveryFailure);
Console.WriteLine();
…
}
寄不出的信件佇列中的訊息是針對處理訊息之服務所發出的訊息。 因此,當無效信件訊息服務從佇列讀取訊息時,Windows Communication Foundation (WCF) 通道層會發覺端點中有不相符的情況,從而不分派訊息。 在本例中,訊息是針對訂單處理服務發出的,但是會由寄不出的信件訊息服務接收。 為了接收針對不同端點發出的訊息,在 ServiceBehavior
中會指定用來比對所有位址的位址篩選條件。 若要順利處理從寄不出的信件佇列中讀取的訊息,就必須這麼做。
在這個範例中,如果失敗的原因是訊息逾時,寄不出的信件訊息服務將會重新傳送訊息。對於其他所有原因,則顯示傳遞失敗,如下列範例程式碼所示:
// Service class that implements the service contract.
// Added code to write output to the console window.
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Single, AddressFilterMode=AddressFilterMode.Any)]
public class PurchaseOrderDLQService : IOrderProcessor
{
OrderProcessorClient orderProcessorService;
public PurchaseOrderDLQService()
{
orderProcessorService = new OrderProcessorClient("OrderProcessorEndpoint");
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void SubmitPurchaseOrder(PurchaseOrder po)
{
Console.WriteLine("Submitting purchase order did not succeed ", po);
MsmqMessageProperty mqProp = OperationContext.Current.IncomingMessageProperties[MsmqMessageProperty.Name] as MsmqMessageProperty;
Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus);
Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure);
Console.WriteLine();
// resend the message if timed out
if (mqProp.DeliveryFailure == DeliveryFailure.ReachQueueTimeout ||
mqProp.DeliveryFailure == DeliveryFailure.ReceiveTimeout)
{
// re-send
Console.WriteLine("Purchase order Time To Live expired");
Console.WriteLine("Trying to resend the message");
// reuse the same transaction used to read the message from dlq to enqueue the message to app. queue
orderProcessorService.SubmitPurchaseOrder(po);
Console.WriteLine("Purchase order resent");
}
}
// Host the service within this EXE console application.
public static void Main()
{
// Create a ServiceHost for the PurchaseOrderDLQService type.
using (ServiceHost serviceHost = new ServiceHost(typeof(PurchaseOrderDLQService)))
{
// Open the ServiceHostBase to create listeners and start listening for messages.
serviceHost.Open();
// The service can now be accessed.
Console.WriteLine("The dead letter service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
}
}
}
下列範例示範寄不出的信件訊息的組態:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="Microsoft.ServiceModel.Samples.PurchaseOrderDLQService">
<!-- Define NetMsmqEndpoint in this case, DLQ end point to read messages-->
<endpoint address="net.msmq://localhost/private/ServiceModelSamplesOrdersAppDLQ"
binding="netMsmqBinding"
bindingConfiguration="DefaultBinding"
contract="Microsoft.ServiceModel.Samples.IOrderProcessor" />
</service>
</services>
<client>
<!-- Define NetMsmqEndpoint -->
<endpoint name="OrderProcessorEndpoint"
address="net.msmq://localhost/private/ServiceModelSamplesDeadLetter"
binding="netMsmqBinding"
bindingConfiguration="SystemDLQBinding"
contract="IOrderProcessor" />
</client>
<bindings>
<netMsmqBinding>
<binding name="DefaultBinding" />
<binding name="SystemDLQBinding"
deadLetterQueue="System"/>
</netMsmqBinding>
</bindings>
</system.serviceModel>
</configuration>
在執行的範例中,有 3 個可執行檔要執行,藉以觀察每個應用程式 (用戶端、服務和寄不出的信件服務) 運用寄不出的信件佇列的方式;每個應用程式都會從各自寄不出的信件佇列讀取訊息,然後重新傳送給服務。 這些都是會在主控台視窗中產生輸出的主控台應用程式。
注意
因為佇列正在使用中,所以用戶端與服務不需要同時啟動與執行。 您可以執行用戶端,關閉用戶端,然後再啟動服務,服務還是會收到訊息。 您應該啟動服務再加以關閉,這樣就可以建立佇列。
執行用戶端時,用戶端會顯示訊息:
Press <ENTER> to terminate client.
用戶端已嘗試傳送訊息,但是因為逾時期限很短,訊息已過期,且已加入至每個應用程式之寄不出的信件佇列中。
此時,請執行寄不出的信件服務,它會讀取訊息並顯示錯誤碼,然後將訊息重新傳回到服務。
The dead letter service is ready.
Press <ENTER> to terminate service.
Submitting purchase order did not succeed
Message Delivery Status: InDoubt
Message Delivery Failure: ReachQueueTimeout
Purchase order Time To Live expired
Trying to resend the message
Purchase order resent
服務會啟動,然後讀取重送的訊息,再加以處理。
The service is ready.
Press <ENTER> to terminate service.
Processing Purchase Order: 97897eff-f926-4057-a32b-af8fb11b9bf9
Customer: somecustomer.com
OrderDetails
Order LineItem: 54 of Blue Widget @unit price: $29.99
Order LineItem: 890 of Red Widget @unit price: $45.89
Total cost of this order: $42461.56
Order status: Pending
若要安裝、建置及執行範例
如果服務優先執行,它就會檢查以確定佇列存在。 如果佇列不存在,服務將建立一個佇列。 您可以先執行服務來建立佇列,也可以透過 MSMQ 佇列管理員建立佇列。 請依照下列步驟,在 Windows 2008 中建立佇列。
在 Visual Studio 2012 中開啟伺服器管理員。
展開 [功能] 索引標籤。
以滑鼠右鍵按一下 [私人訊息佇列],然後依序選取 [新增] 和 [私人佇列]。
核取 [可異動] 方塊。
輸入
ServiceModelSamplesTransacted
作為新佇列的名稱。
若要建置方案的 C# 或 Visual Basic .NET 版本,請遵循 Building the Windows Communication Foundation Samples中的指示。
若要在單一或跨電腦組態中執行本範例,請適當變更佇列名稱,並以完整的電腦名稱取代 localhost,然後遵循執行 Windows Communication Foundation 範例中的指示。
若要在加入至工作群組的電腦上執行範例
如果您的電腦不是網域的一部分,請將驗證模式和保護層級設定為
None
,以關閉傳輸安全性,如下面的範例組態所示:<bindings> <netMsmqBinding> <binding name="TransactedBinding"> <security mode="None"/> </binding> </netMsmqBinding> </bindings>
請透過設定端點的
bindingConfiguration
屬性,確定端點與繫結相關聯。請務必先變更 DeadLetterService、伺服器和用戶端上的組態,再執行範例。
注意
將
security mode
設定為None
,相當於將MsmqAuthenticationMode
、MsmqProtectionLevel
和Message
安全性設定為None
。
註解
根據預設,安全性會透過 netMsmqBinding
繫結傳輸啟用。 MsmqAuthenticationMode
和 MsmqProtectionLevel
這兩個屬性會共同決定傳輸安全性的類型。 根據預設,驗證模式會設定為 Windows
,保護層級則會設定為 Sign
。 若要 MSMQ 提供驗證和簽署功能,則 MSMQ 必須是網域的一部分。 如果您在不屬於網域的電腦上執行這個範例,就會收到下列錯誤:「使用者的內部訊息佇列憑證不存在」。