This sample demonstrates how to implement the request/response pattern using Windows Azure Service Bus Topics and Subscriptions.
The sample shows simple clients and servers communicating via a Service Bus Topic. The SampleManager first prompts for service namespace credentials. These are used to authenticate with the Access Control service, and acquire an access token that proves to the Service Bus infrastructure that the client is authorized to access the queue. The SampleManager creates a Topic and two Subscriptions one for requests and one for responses. It then creates one or more clients and servers. Each client sends request messages to the topic and asynchronously receives a message on the response subscription. The servers receives a message from the request subscription and sends a response message to the topic.
Many application scenarios involve two-way communications in which a sender would like to receive and correlate responses for the messages that it sends. This is supported in Service Bus through the use of the properties SessionId and ReplyToSessionId on a message. Consider a pattern involving several clients that are sending messages and expect responses for these messages to be directed back to them. To enable this pattern, a given client, say client “ABC”, would set ReplyToSessionId=”ABC” on any messages it sends and also use MessageSession on a reply Topic/Subscription or Queue to listen for messages where SessionId=ABC. To complete the pattern, any processor of the message would set SessionId=ReplyToSessionId once a message has been processed.
Prerequisites
If you haven't already done so, please read the release notes document that explains how to sign up for a Windows Azure account and how to configure your environment.
SampleManager
The SampleManager gets user credentials and creates a NamespaceManager (namespaceManager). This entity holds the credentials and is used for all messaging management operations. The namespaceClient is used to create queues for communication between the client(s) and server(s). The SampleManager then creates client(s) and server(s), passing the user credentials to each.
The following code prompts for the issuer credentials and then constructs the
listening URI using that information. The static
ServiceBusEnvironment.CreateServiceUri
function is provided to help
construct the URI with the correct format and domain name. It is strongly
recommended that you use this function instead of building the URI from scratch
because the URI construction logic and format might change in future releases.
C# | |
---|---|
static void Main(string[] args) { // Setup: ParseArgs(args); GetUserCredentials(); CreateNamespaceClient(); // Create topic Console.WriteLine("\nCreating Topic..."); TopicDescription description = CreateTopic(topicPath); Console.WriteLine( "Created {0}", description.Path); // Create request subscription Console.WriteLine("\nCreating Subscriptions..."); SubscriptionDescription requestSub = CreateSubscription(description.Path, requestSubName, false); Console.WriteLine( "Created {0}/{1}, RequiresSession = {2}", requestSub.TopicPath, requestSub.Name, requestSub.RequiresSession); SubscriptionDescription responseSub = CreateSubscription(description.Path, responseSubName, true); Console.WriteLine( "Created {0}/{1}, RequiresSession = {2}", responseSub.TopicPath, responseSub.Name, responseSub.RequiresSession); ... } // Create the management entities (queue) static void CreateNamespaceClient() { TokenProvider tokenProvider = TokenProvider.CreateSharedSecretTokenProvider( ServiceBusIssuerName, ServiceBusIssuerKey); Uri uri = ServiceBusEnvironment.CreateServiceUri("sb", ServiceBusNamespace, string.Empty); namespaceManager = new NamespaceManager(uri, tokenProvider); } static TopicDescription CreateTopic(string path) { TopicDescription description = new TopicDescription { Path = path }; // Delete the topic if it already exists if (namespaceManager.TopicExists(path)) { namespaceManager.DeleteTopic(path); } return namespaceManager.CreateTopic(description); } static SubscriptionDescription CreateSubscription(string path, string name, bool sessions) { SubscriptionDescription description = new SubscriptionDescription { TopicPath = path, Name = name, RequiresSession = sessions }; // Delete the subscription if it already exists if (namespaceManager.SubscriptionExists(description.TopicPath, description.Name)) { namespaceManager.DeleteSubscription(description.TopicPath, description.Name); } return namespaceManager.CreateSubscription(description, new CorrelationFilterExpression(name)); } |
The SampleManager then creates client and server processes and starts them.
C# | |
---|---|
static void Main(string[] args) { ... // Start clients and servers: Console.WriteLine("\nLaunching clients and servers..."); StartClients(); StartServers(); Console.WriteLine("\nPress [Enter] to exit."); Console.ReadLine(); ... } static void StartClients() { ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.FileName = "RequestResponseSampleClient.exe"; for (int i = 0; i < numClients; ++i) { startInfo.Arguments = CreateArgs(i.ToString()); Process process = Process.Start(startInfo); clientProcs.Add(process); } Thread.Sleep(500); ArrangeWindows(); } static void StartServers() { ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.FileName = "RequestResponseSampleServer.exe"; startInfo.Arguments = CreateArgs(); for (int i = 0; i < numServers; ++i) { Process process = Process.Start(startInfo); serverProcs.Add(process); } Thread.Sleep(500); ArrangeWindows(); } |
Finally, the SampleManager cleans up by deleting the topics and subscriptions, as well as stopping the clients and servers if necessary.
C# | |
---|---|
static void Main(string[] args) { ... // Cleanup: namespaceManager.DeleteSubscription(requestSub.TopicPath, requestSub.Name); namespaceManager.DeleteSubscription(responseSub.TopicPath, responseSub.Name); namespaceManager.DeleteTopic(description.Path); StopClients(); StopServers(); } static void StopClients() { foreach (Process proc in clientProcs) { proc.CloseMainWindow(); } } static void StopServers() { foreach (Process proc in serverProcs) { proc.CloseMainWindow(); } } |
Client
The Client gets user credentials as parameters from SampleManager and creates a
MessagingFactory (messagingFactory). This entity holds the credentials and is
used for all messaging runtime operations, such as opening a TopicClient to send
messages to the topic. The Client then creates a SubscriptionClient to the request
subscription. Messages are sent to the server on the topic and
responses are processed asynchonously by calling responseClient.BeginReceive()
.
C# |
---|
static void Main(string[] args) { ParseArgs(args); Console.Title = "Client"; // Send request messages to request queue TopicClient topicClient = CreateTopicClient(SampleManager.TopicPath); SubscriptionClient responseClient = CreateSubscriptionClient( SampleManager.TopicPath, SampleManager.ResponseSubName); Console.WriteLine("Preparing to send request messages to {0}...", topicClient.Path); SendMessages(topicClient, responseClient); ... } static void SendMessages(TopicClient topicClient, SubscriptionClient responseClient) { // Send request messages to queue: Console.WriteLine("Sending request messages to topic {0}", topicClient.Path); Console.WriteLine("Receiving response messages on subscription {0}", responseClient.Name); MessageSession session = responseClient.AcceptMessageSession(ClientId); for (int i = 0; i < SampleManager.NumMessages; ++i) { // send request message BrokeredMessage message = new BrokeredMessage { ReplyToSessionId = ClientId, MessageId = i.ToString(), CorrelationId = SampleManager.RequestSubName }; topicClient.Send(message); SampleManager.OutputMessageInfo("REQUEST: ", message); // start asynchronous receive operation session.BeginReceive(TimeSpan.FromSeconds(ResponseMessageTimeout), ProcessResponse, session); } Console.WriteLine(); } public static TopicClient CreateTopicClient(string topicPath) { TokenProvider tokenProvider = TokenProvider.CreateSharedSecretTokenProvider( ServiceBusIssuerName, ServiceBusIssuerKey); Uri uri = ServiceBusEnvironment.CreateServiceUri("sb", ServiceBusNamespace, string.Empty); MessagingFactory messagingFactory = MessagingFactory.Create(uri, tokenProvider); return messagingFactory.CreateTopicClient(topicPath); } public static SubscriptionClient CreateSubscriptionClient(string topicPath, string subName) { TokenProvider tokenProvider = TokenProvider.CreateSharedSecretTokenProvider( ServiceBusIssuerName, ServiceBusIssuerKey); Uri uri = ServiceBusEnvironment.CreateServiceUri("sb", ServiceBusNamespace, string.Empty); MessagingFactory messagingFactory = MessagingFactory.Create(uri, tokenProvider); return messagingFactory.CreateSubscriptionClient(topicPath, subName, ReceiveMode.ReceiveAndDelete); } static void ProcessResponse(IAsyncResult result) { MessageSession session = result.AsyncState as MessageSession; BrokeredMessage message = session.EndReceive(result); if (message == null) { Console.WriteLine("ERROR: Message Receive Timeout."); } else { SampleManager.OutputMessageInfo("RESPONSE: ", message); } } |
Server
The Server also gets user credentials as parameters from SampleManager and creates a MessagingFactory (messagingFactory). This entity holds the credentials and is used for all messaging runtime operations, such as opening a TopicClient to receive messages from the topic. The Server then creates a SubscriptionClient to the request subscription and starts receiving and responding to request messages:
C# | |
---|---|
static void Main(string[] args) { ParseArgs(args); // Receive request messages from request queue Console.Title = "Server"; TopicClient topicClient = CreateTopicClient(SampleManager.TopicPath); SubscriptionClient requestClient = CreateSubscriptionClient( SampleManager.TopicPath, SampleManager.RequestSubName); Console.WriteLine("Ready to receive messages from {0}/{1}...", requestClient.TopicPath, requestClient.Name); ReceiveMessages(topicClient, requestClient); ... } static void ReceiveMessages(TopicClient topicClient, SubscriptionClient requestClient) { // Read all the messages from subscription: BrokeredMessage request; while ((request = requestClient.Receive(TimeSpan.FromSeconds(ReceiveMessageTimeout))) != null) { SampleManager.OutputMessageInfo("REQUEST: ", request); BrokeredMessage response = new BrokeredMessage { SessionId = request.ReplyToSessionId, MessageId = request.MessageId, CorrelationId = SampleManager.ResponseSubName }; topicClient.Send(response); SampleManager.OutputMessageInfo("RESPONSE: ", response); } } public static TopicClient CreateTopicClient(string topicPath) { TokenProvider tokenProvider = TokenProvider.CreateSharedSecretTokenProvider( ServiceBusIssuerName, ServiceBusIssuerKey); Uri uri = ServiceBusEnvironment.CreateServiceUri("sb", ServiceBusNamespace, string.Empty); MessagingFactory messagingFactory = MessagingFactory.Create(uri, tokenProvider); return messagingFactory.CreateTopicClient(topicPath); } public static SubscriptionClient CreateSubscriptionClient(string topicPath, string subName) { TokenProvider tokenProvider = TokenProvider.CreateSharedSecretTokenProvider( ServiceBusIssuerName, ServiceBusIssuerKey); Uri uri = ServiceBusEnvironment.CreateServiceUri("sb", ServiceBusNamespace, string.Empty); MessagingFactory messagingFactory = MessagingFactory.Create(uri, tokenProvider); return messagingFactory.CreateSubscriptionClient(topicPath, subName, ReceiveMode.ReceiveAndDelete); } |
Running the Sample
To run the sample, build the solution in Visual Studio or from the command line, then run the resulting SampleManager.exe executable file. The program will prompt for your service namespace and the issuer credentials. For the issuer secret, be sure to use the "Default Issuer Key" value (typically "owner") from the portal, rather than one of the management keys.
Expected Output - SampleManager
Please provide the namespace to use: <service namespace> Please provide the Issuer name to use: <issuer name> Please provide the Issuer key to use: <issuer key> Creating Topic... Created RequestResponseTopic Creating Subscriptions... Created RequestResponseTopic/Request, RequiresSession = False Created RequestResponseTopic/Response, RequiresSession = True Launching clients and servers... Press [Enter] to exit. |
Expected Output - Client
Preparing to send request messages to RequestQueue... Sending request messages to queue RequestQueue Receiving response messages on queue ResponseQueue REQUEST: 0 - Client 0. REQUEST: 1 - Client 0. REQUEST: 2 - Client 0. REQUEST: 3 - Client 0. REQUEST: 4 - Client 0. REQUEST: 5 - Client 0. REQUEST: 6 - Client 0. REQUEST: 7 - Client 0. REQUEST: 8 - Client 0. REQUEST: 9 - Client 0. Client finished sending requests. RESPONSE: 0 - Client 0. RESPONSE: 1 - Client 0. RESPONSE: 2 - Client 0. RESPONSE: 3 - Client 0. RESPONSE: 4 - Client 0. RESPONSE: 5 - Client 0. RESPONSE: 6 - Client 0. RESPONSE: 7 - Client 0. RESPONSE: 8 - Client 0. RESPONSE: 9 - Client 0. |
Expected Output - Server
Ready to receive messages from RequestQueue... Reading messages from queue RequestQueue REQUEST: 0 - Client 2. RESPONSE: 0 - Client 2. REQUEST: 0 - Client 0. RESPONSE: 0 - Client 0. REQUEST: 0 - Client 1. RESPONSE: 0 - Client 1. REQUEST: 0 - Client 3. RESPONSE: 0 - Client 3. REQUEST: 1 - Client 2. RESPONSE: 1 - Client 2. REQUEST: 1 - Client 0. RESPONSE: 1 - Client 0. REQUEST: 1 - Client 1. RESPONSE: 1 - Client 1. REQUEST: 1 - Client 3. RESPONSE: 1 - Client 3. REQUEST: 2 - Client 2. RESPONSE: 2 - Client 2. REQUEST: 2 - Client 3. RESPONSE: 2 - Client 3. REQUEST: 2 - Client 1. RESPONSE: 2 - Client 1. REQUEST: 2 - Client 0. RESPONSE: 2 - Client 0. ... REQUEST: 8 - Client 0. RESPONSE: 8 - Client 0. REQUEST: 8 - Client 1. RESPONSE: 8 - Client 1. REQUEST: 8 - Client 2. RESPONSE: 8 - Client 2. REQUEST: 8 - Client 3. RESPONSE: 8 - Client 3. REQUEST: 9 - Client 0. RESPONSE: 9 - Client 0. REQUEST: 9 - Client 1. RESPONSE: 9 - Client 1. REQUEST: 9 - Client 3. RESPONSE: 9 - Client 3. REQUEST: 9 - Client 2. RESPONSE: 9 - Client 2. Server complete. |