This sample demonstrates how to use the Windows Azure Service Bus and the Messaging Session functionality.
The sample shows simple senders and receivers communicating via a Service Bus queue. 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 two queues, one requiring sessions and one that does not, then creates one or more senders and receivers. The sender(s) send messages with a range of SessionId values simulating different orders into the queues, first to the sessionless queue then to the queue requiring sessions. The receivers read orders until the sessionless queue is empty, then receives messages by session.
When messages are being received from the sessionless queue, the receivers will process the messages in the order they appear, regardless of the SessionId. When messages are being received from the session queue, the receivers will only receive messages that have the same SessionId until there are no more remaining, then will begin receiving messages for the next available session. The receivers also use Session.State to keep track of how much time has been spent processing messages for that session. This state is persisted for the session, so even if a receiver goes down while processing messages in a session (which you can simulate by closing one of the Session Receiver windows) the state will be available to the next receiver that picks up the session.
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 ServiceBusNamespaceClient (namespaceClient). This entity holds the credentials and is used for all messaging management operations. The namespaceClient is used to create queues for communication between the sender(s) and receiver(s). The SampleManager then creates sender(s) and receiver(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 queues: Console.WriteLine("Creating Queues..."); Queue sessionlessQueue = CreateQueue(false); Console.WriteLine("Created {0}, Queue.RequiresSession = false", sessionlessQueue.Path); Queue sessionQueue = CreateQueue(true); Console.WriteLine("Created {0}, Queue.RequiresSession = true", sessionQueue.Path); ... } static void CreateNamespaceClient() { TransportClientCredentialBase credentials = TransportClientCredentialBase.CreateSharedSecretCredential( ServiceBusIssuerName, ServiceBusIssuerKey); Uri managementUri = ServiceBusEnvironment.CreateServiceUri("https", ServiceBusNamespace, string.Empty); namespaceClient = new ServiceBusNamespaceClient(managementUri, credentials); } static Queue CreateQueue(bool session) { QueueDescription queueDescription = new QueueDescription() { RequiresSession = session }; string queueName = (session ? sessionQueueName : sessionlessQueueName); // Delete the queue if already exists before creation. if (namespaceManager.QueueExists(queueName)) { namespaceManager.DeleteQueue(queueName); } return namespaceClient.CreateQueue(queueName, queueDescription); } |
The SampleManager then creates sender and receiver processes and starts them.
C# | |
---|---|
static void Main(string[] args) { ... // Start senders and receivers: Console.WriteLine("\nLaunching senders and receivers..."); StartSenders(); StartReceivers(); Console.WriteLine("\nPress [Enter] to exit."); Console.ReadLine(); ... } private static void StartSenders() { ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.FileName = "SessionMessagesSampleSender.exe"; startInfo.Arguments = CreateArgs(); for (int i = 0; i < numSenders; ++i) { Process process = Process.Start(startInfo); senderProcs.Add(process); } Thread.Sleep(500); ArrangeWindows(); } static void StartReceivers() { ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.FileName = "SessionMessagesSampleReceiver.exe"; startInfo.Arguments = CreateArgs(); for (int i = 0; i < numReceivers; ++i) { Process process = Process.Start(startInfo); receiverProcs.Add(process); } Thread.Sleep(500); ArrangeWindows(); } |
Finally, the SampleManager cleans up by deleting the queues and stopping the senders and receivers if necessary.
C# | |
---|---|
static void Main(string[] args) { ... // Cleanup: namespaceClient.DeleteQueue(sessionlessQueue.Path); namespaceClient.DeleteQueue(sessionQueue.Path); StopSenders(); StopReceivers(); } private static void StopSenders() { foreach (Process proc in senderProcs) { proc.CloseMainWindow(); } } static void StopReceivers() { foreach (Process proc in receiverProcs) { proc.CloseMainWindow(); } } Visual Basic |
Sender
The sender 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 client to send messages to the queues. The sender then creates a client to the sessionless queue and sends messages.
C# |
---|
static void Main(string[] args) { ParseArgs(args); Console.Title = "MessageSender"; // Send messages to queue which does not require session QueueClient queueClient = CreateQueueClient(SampleManager.SessionlessQueueName); Console.WriteLine("Preparing to send messages to {0}...", queueClient.Path); Thread.Sleep(3000); SendMessages(queueClient); ... } static void SendMessages(QueueClient queueClient) { // Send messages to queue: Console.WriteLine("Sending messages to queue {0}", queueClient.Path); using (MessageSender sender = queueClient.CreateSender()) { System.Random rand = new Random(); for (int i = 0; i < SampleManager.NumMessages; ++i) { string sessionName = rand.Next(SampleManager.NumSessions).ToString(); BrokeredMessage message = CreateSessionMessage(sessionName); sender.Send(message); SampleManager.OutputMessageInfo("SEND: ", message); Thread.Sleep(senderDelay); } } Console.WriteLine(); } // Create the runtime entities (queue client) static QueueClient CreateQueueClient(string queueName) { TransportClientCredentialBase credentials = TransportClientCredentialBase.CreateSharedSecretCredential( ServiceBusIssuerName, ServiceBusIssuerKey); Uri runtimeUri = ServiceBusEnvironment.CreateServiceUri("sb", ServiceBusNamespace, string.Empty); MessagingFactory messagingFactory = MessagingFactory.Create(runtimeUri, credentials); return messagingFactory.CreateQueueClient(queueName); } static BrokeredMessage CreateSessionMessage(string sessionId) { BrokeredMessage message = BrokeredMessage.CreateMessage(); message.SessionId = sessionId; message.MessageId = "Order_" + Guid.NewGuid().ToString().Substring(0,5); return message; } |
The sender then creates a client to the session queue and sends messages. Note that the messages are created with a SessionId regardless of which queue they are sent to.
C# |
---|
static void Main(string[] args) { ... // Send messages to queue requiring session queueClient = CreateQueueClient(SampleManager.SessionQueueName); Console.WriteLine("Preparing to send messages to {0}...", queueClient.Path); Console.WriteLine("Press [Enter] to begin sending messages."); Console.ReadLine(); SendMessages(queueClient); // All messages sent Console.WriteLine("\nSender complete."); Console.ReadLine(); } |
Receiver
The receiver 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 client to receive messages from the queues. The receiver then creates a client to the sessionless queue and starts receiving messages:
C# | |
---|---|
static void Main(string[] args) { ParseArgs(args); // Create MessageReceiver for queue which does not require session Console.Title = "MessageReceiver"; QueueClient queueClient = CreateQueueClient(SampleManager.SessionlessQueueName); Console.WriteLine("Ready to receive messages from {0}...", queueClient.Path); MessageReceiver receiver = queueClient.CreateReceiver(ReceiveMode.ReceiveAndDelete); lastReceive = DateTime.Now; ReceiveMessages(receiver); ... } static QueueClient CreateQueueClient(string queueName) { TransportClientCredentialBase credentials = TransportClientCredentialBase.CreateSharedSecretCredential( ServiceBusIssuerName, ServiceBusIssuerKey); Uri runtimeUri = ServiceBusEnvironment.CreateServiceUri("sb", ServiceBusNamespace, string.Empty); MessagingFactory messagingFactory = MessagingFactory.Create(runtimeUri, credentials); return messagingFactory.CreateQueueClient(queueName); } static void ReceiveMessages(MessageReceiver receiver) { // Read messages from queue until queue is empty: Console.WriteLine("Reading messages from queue {0}", receiver.Path); Console.WriteLine("Receiver Type: " + receiver.GetType().Name); BrokeredMessage receivedMessage; while (receiver.TryReceive(TimeSpan.FromSeconds(receiveMessageTimeout), out receivedMessage)) { ProcessMessage(receivedMessage); } receiver.Close(); } static void ProcessMessage(BrokeredMessage message, Session session = null) { DateTime startProcessingNewMessage = DateTime.Now; TimeSpan elapsed = startProcessingNewMessage - lastReceive; lastReceive = startProcessingNewMessage; // Using the Session State to track how much processing time was spent on a group. // This value will persist even if a receiver process is killed and the remaining // messages are picked up by another receiver. string readState = null; if (session != null) { TimeSpan totalElapsed = elapsed; readState = GetState(session); if (readState != null) { TimeSpan prevElapsed = TimeSpan.FromSeconds(Double.Parse(readState)); totalElapsed = elapsed + prevElapsed; } SetState(session, totalElapsed.TotalSeconds.ToString()); } SampleManager.OutputMessageInfo("RECV: ", message, "State: " + readState); Thread.Sleep(receiverDelay); } |
Once the sessionless queue is empty, the receiver creates a queue client for the session queue and calls AcceptSessionReceiver to begin receiving messages from the first available session. Once all messages from that session have been received, the receiver continues to call AcceptSessionReceiver until there are no sessions of messages remaining.
In ProcessMessage, the receiver makes use of the session state to add the processing time for the session each time a message is processed.
C# | |
---|---|
static void Main(string[] args) { ... // Create SessionReceiver for queue requiring session Console.Title = "SessionReceiver"; QueueClient sessionQueueClient = CreateQueueClient(SampleManager.SessionQueueName); Console.Clear(); Console.WriteLine("Ready to receive messages from {0}...", sessionQueueClient.Path); bool allSessionsAccepted = false; while (!allSessionsAccepted) { try { Console.WriteLine("Checking for session..."); SessionReceiver sessionReceiver = sessionQueueClient.AcceptSessionReceiver(ReceiveMode.ReceiveAndDelete, TimeSpan.FromSeconds(acceptSessionReceiverTimeout)); ReceiveSessionMessages(sessionReceiver); Console.WriteLine("All received on this session"); } catch (TimeoutException) { Console.WriteLine("Got TimeoutException, no more sessions available"); allSessionsAccepted = true; } } Console.WriteLine("\nReceiver complete."); Console.ReadLine(); } static void ReceiveSessionMessages(SessionReceiver receiver) { // Read messages from queue until queue is empty: Console.WriteLine("Reading messages from queue {0}", receiver.Path); Console.WriteLine("Receiver Type:" + receiver.GetType().Name); Console.WriteLine("Receiver.SessionId = " + receiver.SessionId); BrokeredMessage receivedMessage; while (receiver.TryReceive(TimeSpan.FromSeconds(receiveSessionMessageTimeout), out receivedMessage)) { Session session = receiver.Session; ProcessMessage(receivedMessage, session); } receiver.Close(); } |
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 Queues... Created OrderQueue_NoSession, Queue.RequiresSession = false Created OrderQueue_Session, Queue.RequiresSession = true Launching senders and receivers... Press [Enter] to exit. |
Expected Output - Sender
Preparing to send messages to OrderQueue_NoSession... Sending messages to queue OrderQueue_NoSession SEND: Order_5618c - Group 2. SEND: Order_44753 - Group 0. SEND: Order_de2f7 - Group 0. SEND: Order_d154e - Group 3. ... Preparing to send messages to OrderQueue_Session... Press [Enter] to begin sending messages. Sending messages to queue OrderQueue_Session SEND: Order_f2251 - Group 2. SEND: Order_444c9 - Group 0. SEND: Order_0e7f6 - Group 2. SEND: Order_77c74 - Group 2. ... Sender complete. |
Expected Output - Receiver
Ready to receive messages from OrderQueue_NoSession... Reading messages from queue OrderQueue_NoSession Receiver Type: SbmpMessageReceiver RECV: Order_5618c - Group 2. State: RECV: Order_44753 - Group 0. State: RECV: Order_de2f7 - Group 0. State: RECV: Order_d154e - Group 3. State: ... Ready to receive messages from OrderQueue_Session... Checking for session... Reading messages from queue ORDERQUEUE_SESSION Receiver Type:SessionReceiver Receiver.SessionId = 2 RECV: Order_f2251 - Group 2. State: RECV: Order_0e7f6 - Group 2. State: 10.3260325 RECV: Order_77c74 - Group 2. State: 10.5930267 RECV: Order_43a8a - Group 2. State: 10.8810288 ... RECV: Order_9d1f6 - Group 3. State: 17.7360284 RECV: Order_4d3ec - Group 3. State: 18.0000264 RECV: Order_501d2 - Group 3. State: 18.2650265 RECV: Order_5668b - Group 3. State: 18.5360271 All received on this session Checking for session... Got TimeoutException, no more sessions available Receiver complete. |