วันเสาร์ที่ 26 ธันวาคม พ.ศ. 2552

Exercise 1: Using Worker Role External Endpoints

During this exercise, you will implement the WCF chat service. This involves defining an external endpoint for the worker role, implementing the service contract and updating the worker role to host the service at the external endpoint.

Windows Azure allows you to create as many instances of a worker role as you require, which means that you are able to host the chat service in many different nodes simultaneously. However, in this initial implementation, each instance maintains its own list of sessions. This allows clients connected to the same worker role to communicate with each other but prevents them from exchanging messages with peers that have active sessions in other worker roles. In the next exercise, you will see how to communicate worker roles and exchange session information between them.

Figure 1 Clients communicating with peers connected to the same worker role

Task 1 – Exploring the AzureTalk Solution

In this task, you open the starting solution and become familiar with the code.

  1. Open Microsoft Visual Studio 2008 in elevated administrator mode, from Start | All Programs | Microsoft Visual Studio 2008 by right clicking the Microsoft Visual Studio 2008 shortcut and choosing Run as Administrator.
  2. In the File menu, choose Open and then Project/Solution. In the Open Project dialog, browse to Ex1-WorkerExternalEndPoints\Begin in the Source folder of the lab, select Begin.sln in the folder for the language of your preference (Visual C# or Visual Basic) and click Open.
  3. The solution contains the following projects:

    Figure 2 Solution Explorer showing the AzureTalk solution

    AzureTalk

    A standard cloud service project configured to support a single worker role namedAzureTalk.Service.

    AzureTalk.Client

    A WPF client application that can connect to the chat service to exchange messages with peers. It implements the service callback contract and can receive notifications from the service. In this hands-on lab, you will use this application to test the chat service.

    AzureTalk.Contract

    A class library project that contains the service and data contracts for the chat service and the callback contract implement by the client application. The client application and the service share this project.

    AzureTalk.Service

    The worker role hosts the chat service and listens over an external TCP endpoint.

Task 2 – Hosting a WCF Service in a Worker Role

In this task, you configure the worker role to define an external endpoint and then create a WCF service host to listen at this endpoint.

  1. Enable full trust for the worker role. To do this, in Solution Explorer, expand the Roles node in theAzureTalk cloud project, right-click the AzureTalk.Service role and choose Properties. In the roleProperties window, select the Configuration tab and choose the Full Trust option.

    Figure 3 Configuring the trust level of the worker role

  2. Define an external endpoint for the worker role. In the role Properties window, change to the Endpointstab and click Add Endpoint. Set the name of the new endpoint to “ChatService”, leave the Type as “Input” and the Protocol as “tcp”, and set the Port number to the value “3030”. The worker role will use this TCP endpoint to host the chat service.

    Figure 4 Defining the external endpoint for the worker role

  3. Press CTRL + S to save the changes to the worker role configuration.
  4. Open the WorkerRole.cs file in the AzureTalk.Service project. This file contains the entry point of the worker role.
  5. In the WorkerRole class, define a WCF ServiceHost member field.

    (Code Snippet – Windows Azure Worker Role Communication – Ex01 ServiceHost- CS)

    C#
    /// ServiceHost object for internal and external endpoints. private ServiceHost serviceHost; 
  6. Add the StartChatService method to the WorkerRole class. This method creates and configures the WCF service host instance for the chat service.

    (Code Snippet – Windows Azure Worker Role Communication – Ex01 StartChatService - CS)

    C#
    ///  /// Starts the service host object for the internal  /// and external endpoints of the chat service. ///  /// Specifies the number of retries to  /// start the service in case of failure. private void StartChatService(int retries) {   // recycle the role if host cannot be started    // after the specified number of retries   if (retries == 0)   {     RoleEnvironment.RequestRecycle();     return;   }    Trace.TraceInformation("Starting chat service host...");    this.serviceHost = new ServiceHost(typeof(ChatService));    // Recover the service in case of failure.    // Log the fault and attempt to restart the service host.    this.serviceHost.Faulted += (sender, e) =>   {     Trace.TraceError("Host fault occured. Aborting and restarting the host. Retry count: {0}", retries);     this.serviceHost.Abort();     this.StartChatService(--retries);   };    // use NetTcpBinding with no security   NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);    // define an external endpoint for client traffic   RoleInstanceEndpoint externalEndPoint =       RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["ChatService"];    this.serviceHost.AddServiceEndpoint(      typeof(IChatService),      binding,      String.Format("net.tcp://{0}/ChatService", externalEndPoint.IPEndpoint));    try   {     this.serviceHost.Open();     Trace.TraceInformation("Chat service host started successfully.");   }   catch (TimeoutException timeoutException)   {     Trace.TraceError("The service operation timed out. {0}",                      timeoutException.Message);   }   catch (CommunicationException communicationException)   {     Trace.TraceError("Could not start chat service host. {0}",                       communicationException.Message);   } } 
    Note:
    The StartChatService method creates a service host to configure and expose the chat service using the implementation provided by the ChatService class. The code configures a single endpoint for the contract defined by the IChatService interface in the AzureTalk.Contract project using a NetTcpBinding binding to enable TCP message delivery using a binary encoding. For this service, the binding configuration specifies no transport security. To determine the service endpoint address, the method uses the RoleEnvironment to obtain a reference the “ChatService” endpoint for the current instance, which you previously defined for the worker role. To provide a certain degree of fault tolerance, the method subscribes to the Faulted event of the service host to restart the service in case of failure and attempts its recovery by re-starting the host. After a number of failed retries, the worker role requests to be recycled.

  7. Next, update the Run method of the worker role to create and start the chat service. To do this, insert a call to the StartChatService method as shown (highlighted) in the code below.C#
    public override void Run() {   Trace.TraceInformation("Worker Process entry point called.");    this.StartChatService(3);    while (true)   {     Thread.Sleep(300000);     Trace.TraceInformation("Working...");   } } 
  8. Press CTRL + S to save the WorkerRole.cs file.

Task 3 – Implementing the Chat Service

In this task, you implement the chat service as specified in the contract defined by the IChatService interface of theAzureTalk.Contract project.

  1. Open the ChatService.cs file in the AzureTalk.Service project. This file contains a skeleton implementation of the chat service contract. In the next steps, you will implement the service operations.
  2. Locate the Register operation and replace its body with the following code.

    (Code Snippet – Windows Azure Worker Role CommunicationEx01 Register - CS)

    C#
    ///  /// Called by clients to announce they are connected at this chat endpoint. ///  /// The user name of the client. /// The ClientInformation object for the new session. public ClientInformation Register(string userName) {   // retrieve session information   string roleId = RoleEnvironment.CurrentRoleInstance.Id;   string sessionId = OperationContext.Current.SessionId;   IClientNotification callback = OperationContext.Current.GetCallbackChannel();    SessionInformation session;   if (SessionManager.CreateOrUpdateSession(sessionId, userName, roleId, callback, out session))   {     // ensure that the session is killed when channel is closed     OperationContext.Current.Channel.Closed += (sender, e) =>     {       SessionManager.RemoveSession(sessionId);       NotifyConnectedClients(session);       Trace.TraceInformation("Session '{0}' by user '{1}' has been closed in role '{2}'.", sessionId, userName, roleId);     };      Trace.TraceInformation("Session '{0}' by user '{1}' has been opened in role '{2}'.", sessionId, userName, roleId);   }    // Notify clients connected to this role   NotifyConnectedClients(session);    return new ClientInformation()   {     SessionId = sessionId,     UserName = userName,     RoleId = roleId   }; } 
    Note:
    The Register implementation obtains the ID of the current worker role and the ID for the session established by the WCF infrastructure. In addition, it retrieves the callback channel to the client instance that called the service, which it can use to call operations on the client through its IClientNotify callback contract. With this information, the service calls the Session Manager to either create a new session or, if the client has registered previously, return an existing session. For new sessions, the method attaches a handler to theClosed event of the channel to remove the session once the channel is closed. The code invokesNotifyConnectedClients, both in the main body and in the handler for the Closed event, to inform other clients connected to this worker role that a new session has started or ended.

  3. Next, replace the body of the SendMessage operation with the code shown below.

    (Code Snippet – Windows Azure Worker Role CommunicationEx01 SendMessage - CS)

    C#
    ///  /// Sends a message to a user. ///  /// The message to send. /// The recipient's session Id. public void SendMessage(string message, string sessionId) {   string fromSessionId = OperationContext.Current.SessionId;    this.DeliverMessage(message, fromSessionId, sessionId); } 
    Note:
    The SendMessage operation retrieves the current session ID from the execution context of the service method, which is the same as the one used by the Register operation to register the client with the Session Manager, and then calls the DeliverMessage method to send the message to its destination. You will implement the DeliverMessage method shortly.

  4. Finally, to complete the implementation of the contract, replace the body of the GetConnectedClientsoperation with the code shown below, which returns the list of active client sessions registered with the Session Manager.

    (Code Snippet – Windows Azure Worker Role CommunicationEx01 GetConnectedClients - CS)

    C#
    ///  /// Returns a list of connected clients. ///  /// The list of active sessions. public IEnumerable GetConnectedClients() {   return from session in SessionManager.GetActiveSessions()          select new ClientInformation()          {            SessionId = session.SessionId,            UserName = session.UserName,            RoleId = session.RoleId          }; } 
    Note:
    The code retrieves a list of active sessions from the Session Manager and returns a projection ofClientInformation objects. These objects, which are defined in the data contract of the service, contain a subset of the session information more suitable for transmission to clients.

  5. Add a DeliverMessage method to the ChatService class. This method sends messages to clients connected to the worker role.

    (Code Snippet – Windows Azure Worker Role CommunicationEx01 DeliverMessage - CS)

    C#
    ///  /// Delivers a message to a client in the current worker role. ///  /// The message to forward. /// The session ID of the message originator. /// The session ID of the message recipient. public void DeliverMessage(string message, string fromSessionId, string toSessionId) {   SessionInformation fromSession = SessionManager.GetSession(fromSessionId);   SessionInformation toSession = SessionManager.GetSession(toSessionId);   if ((fromSession != null) && (toSession != null))   {     // retrieve the callback channel to the client     IClientNotification callback = toSession.Callback;     if (callback != null)     {       callback.DeliverMessage(message, fromSessionId, toSessionId);       Trace.TraceInformation("Message '{0}' sent from '{1}' to '{2}'.",                         message, fromSession.UserName, toSession.UserName);     }   } } 
    Note:
    The code in this method retrieves the source and target sessions and then uses the callback channel to the recipient, which is stored in the target session, to deliver the message over the duplex channel. To do this, it invokes the DeliverMessage operation of the IClientNotification contract implemented by the client.

  6. As a final step, add the NotifyConnectedClients method to the class. This method notifies clients connected to this worker role about session activities, namely clients connecting and disconnecting.

    (Code Snippet – Windows Azure Worker Role CommunicationEx01 NotifyConnectedClients - CS)

    C#
    ///  /// Notifies clients connected to this worker role to update their active sessions list when a new client connects or disconnects. ///  /// The ClientInformation object for the client. private static void NotifyConnectedClients(ClientInformation clientInfo) {   foreach (SessionInformation client in SessionManager.GetActiveSessions())   {     if (client.Callback != null)     {       try       {         client.Callback.UpdateClientList(clientInfo);       }       catch (TimeoutException timeoutException)       {         Trace.TraceError("Unable to notify client '{0}'. The service operation timed out. {1}", client.UserName, timeoutException.Message);         ((ICommunicationObject)client).Abort();         client.Callback = null;       }       catch (CommunicationException communicationException)       {         Trace.TraceError("Unable to notify client '{0}'. There was a communication problem. {1} - {2}", client.UserName, communicationException.Message, communicationException.StackTrace);         ((ICommunicationObject)client).Abort();         client.Callback = null;       }     }   } }  
    Note:
    The NotifyConnectedClients method retrieves a list of active sessions from the Session Manager and then iterates over this list invoking the UpdateClientList operation of the callback contract to notify clients when a client connects or disconnects. Only clients connected to the current worker role have a valid Callbackmember in the SessionInformation object; for clients in other worker roles this member is null.

Verification

You are now ready to test the solution. First, you will run the verification against a single instance of the worker role. To test the service, you will start two instances of the client application and exchange messages between them to determine that messages and notifications flow between clients connected to the service in the worker role. Next, you will repeat the procedure after increasing the number of running instances of the worker role to two.

  1. Press F5 to launch the cloud project in the development fabric.
  2. Switch to the development fabric UI and ensure that the service has started successfully. Notice that only a single instance of the worker role is currently active.

    Figure 5 Chat service executing successfully in the development fabric

  3. In Solution Explorer, right-click the AzureTalk.Client project, point to Debug and select Start new instance.
  4. In the main window of the application, enter a user name for the first client and click Sign In. Note that at this point, this is the only client connected to the service and the Online Users list is predictably empty.

    Figure 6 Signing in to the chat service

    Note:
    The following instructions assume that you are deploying locally and testing against the service running in the development fabric. You may wish to test the service running in Windows Azure, in which case, after you have deployed the service package and before you sign in, you will need to enter the URL that points to your deployment in the client application Server URL field as shown below:net.tcp://.cloudapp.net:3030/ChatService where is the name you chose for your service deployment. For example,net.tcp://azureTalk.cloudapp.net:3030/ChatService

  5. In the development fabric UI, select the worker role instance to display its log and view the entry for the new session that started.

    Figure 7 Worker role log showing the new session

  6. Start a second instance of the client application and sign in using a different user name. Notice that the title bar of the application shows the name of the current user and the ID of the worker role where the client is connected. Both clients should display the same connected role ID because there is only one instance of the worker role currently running.

    Figure 8 Verifying the connected worker role ID of the client

  7. In the second client, notice how the list of online users includes the name of the user registered by the first client. Switch to the first instance of the client application and see how the server immediately alerted this instance about the new session started by the other client.
  8. In one of the instances of the client application, select a user from the list of online users, type a message and click Send. Switch to the other instance to see the message delivered.
  9. Optionally, start additional instances and exchange messages with different users. In the development fabric UI, view the messages that users exchange.

    Figure 9 Worker role log showing messages exchanged by clients

  10. Finally, click Sign out in one of the instances of the client application. Switch to the other instance and notice how the server immediately notified the client about the session that ended.
  11. In the development fabric, view the worker role log to see an event logged for the ending session.
  12. Press Shift + F5 to stop the application running in the development fabric.
  13. Next, you will configure the service to start a second instance of the worker role and repeat the previous steps.
  14. In Solution Explorer, expand the Roles node in the AzureTalk project, right-click the AzureTalk.Servicerole and select Properties. In the Properties window, select the Configuration tab and increase the value of the Instance count to 2.

    Figure 10 Configuring additional instances of the worker role

  15. Press F5 to launch the cloud project in the development fabric.
  16. In Solution Explorer, right-click the AzureTalk.Client project, point to Debug and select Start new instance.
  17. In the main window of the application, enter a user name for the first client and click Sign In.
  18. Start a second instance of the client application and sign in using a different user name. Ensure that the second client connects to a different worker role than the first client by verifying the connected role ID of each client in the title bar of the application.
    Note:
    The load balancer determines which instance of the worker role responds to a client's request. The development fabric typically assigns connections in round robin fashion, so the second client should normally start a session in a different worker role when it connects. If necessary, restart one of the client instances until both clients have sessions in different worker roles.

  19. Notice that the list of active users in either client does not show the other client so they are unable to exchange messages.

    Figure 11 Clients connected to different worker roles cannot communicate

  20. Start a third instance of the client and sign in. Notice that this time, the client connected to the same instance of the worker role as the new client is shown in the list of online users and vice versa.
    Note:
    In the current implementation, worker roles do not share session information and consequently, behave as independent servers. Clients connected to one role cannot exchange messages with clients in any other role. In the next exercise, you will remove this restriction by enabling worker roles to exchange messages and session information and allow all clients to communicate, regardless of the worker role where they establish their session.

ไม่มีความคิดเห็น:

แสดงความคิดเห็น