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

Exercise 2: Using Internal Endpoints for Inter-Role Communication

So far, even though multiple instances of the worker role can each host its own endpoint for the chat service, they all operate as independent servers. Clients connected to a given instance are unable to exchange messages with peers in other worker role instances because roles do not share session information.

In this exercise, you extend the worker role implementation to allow different instances to exchange session information and client notifications over an internal endpoint. To forward messages and session activation events, worker roles need to implement not only the incoming contract of the chat service but also the callback contract implemented by clients. This change allows roles to act as a bridge between peers active in different roles and changes the flow of information in the following manner.

Client Registration

During registration, worker roles notify every other worker role through their internal endpoints. Each alerted worker role then notifies its directly connected clients about the new session.

Figure 1 Roles exchanging session information via an internal endpoint

  1. Client 1 calls the Register operation to start a new session in Worker Role A.
  2. Worker Role A registers the session with its Session Manager and then calls UpdateClientList in each instance of the internal channel endpoint to notify other worker roles about the new session.
  3. Worker Role B receives the notification and then calls UpdateClientList in the callback channel to notify Client 2 that Client 1 has connected.

Sending Messages

When sending messages to peers with session in other worker roles, clients first send the message to their role, which forwards the message through the callback interface to the second role using an internal endpoint. The target role then delivers the message to the recipient using the callback channel to the client. In this case, only the worker roles for the origin and destination are involved in the exchange.

Figure 2 Users connected to different worker roles exchanging messages

  1. Client 1 calls SendMessage to send the message to Worker Role A indicating Client 2 as the recipient.
  2. Worker Role A queries the Session Manager, determines that Client 2 has a session in Worker Role B and then calls DeliverMessage over the internal channel endpoint to forward the message only to this role.
  3. Worker Role B receives the message and then calls DeliverMessage to deliver the message to Client 2 over the callback channel.

Task 1 – Creating an Inter-Role Communication Endpoint

In this task, you configure the worker role to define an internal endpoint. Next, you update the service host configuration to add a new WCF service endpoint using the callback channel contract and set it to listen at the address provided by the internal endpoint.

  1. If it is not already open, launch 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 Ex2-InterRoleCommunication\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. Alternatively, you may continue with the solution that you obtained after completing Exercise 1.
  3. Define an internal endpoint 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 role Propertieswindow, change to the Endpoints tab and click Add Endpoint. Set the name of the new endpoint to “NotificationService”, change the Type to “Internal” and leave the Protocol as “tcp”. The worker role will use this TCP endpoint to receive notifications from other worker roles.

    Figure 3 Defining an internal endpoint for the worker role

  4. Press CTRL + S to save the changes to the worker role configuration.
  5. Open the WorkerRole.cs file in the AzureTalk.Service project.
  6. In the WorkerRole class, define a WCF ChannelFactory member that the worker role will use to create the channel objects for communicating with other roles.

    (Code Snippet – Windows Azure Worker Role Communication – Ex02 ChannelFactory - CS)

    C#
    public class WorkerRole : RoleEntryPoint {   /// Channel factory for inter-role notifications.   private static ChannelFactory factory;    /// ServiceHost object for internal and external endpoints.   private ServiceHost serviceHost;   ... 
    Note:
    The worker role caches the channel factory, which is a thread-safe object, in a static member to avoid the cost of re-creating it each time it needs to communicate with other worker roles.

  7. Update the StartChatService method to configure a new internal endpoint for the service host and create the channel factory for inter-role communication. To do this, insert the following (highlighted) code immediately after the code that defines the external endpoint.

    (Code Snippet – Windows Azure Worker Role CommunicationEx02 InternalEndpoint - 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) {   ...   // 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));    // define an internal endpoint for inter-role traffic   RoleInstanceEndpoint internalEndPoint =       RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["NotificationService"];   this.serviceHost.AddServiceEndpoint(      typeof(IClientNotification),      binding,      String.Format("net.tcp://{0}/NotificationService", internalEndPoint.IPEndpoint));    // create channel factory for inter-role communication   WorkerRole.factory = new ChannelFactory(binding);    try   {     this.serviceHost.Open();     Trace.TraceInformation("Chat service host started successfully.");   ... }  
  8. Add a new NotifyAllNodes method to the WorkerRole class. The worker role uses this method to inform other roles whenever a client starts or ends a session in this role.

    (Code Snippet – Windows Azure Worker Role CommunicationEx02 NotifyAllNodes - CS)

    C#
    ///  /// Notifies all available worker roles to update their active sessions list  /// when a new client connects or disconnects. ///  /// The SessionInformation object for the client. internal static void NotifyAllNodes(SessionInformation session) {   // iterate over all instances of the internal endpoint except the current role - no need to notify itself   var current = RoleEnvironment.CurrentRoleInstance;   var endPoints = current.Role.Instances                   .Where(instance => instance != current)                   .Select(instance => instance.InstanceEndpoints["NotificationService"]);    foreach (var ep in endPoints)   {     EndpointAddress address =         new EndpointAddress(String.Format("net.tcp://{0}/NotificationService", ep.IPEndpoint));     IClientNotification client = WorkerRole.factory.CreateChannel(address);      try     {       client.UpdateClientList(session);       ((ICommunicationObject)client).Close();     }     catch (TimeoutException timeoutException)     {       Trace.TraceError("Unable to notify worker role instance '{0}'. The service operation timed out. {1}", ep.RoleInstance.Id, timeoutException.Message);       ((ICommunicationObject)client).Abort();     }     catch (CommunicationException communicationException)     {       Trace.TraceError("Unable to notify worker role instance '{0}'. There was a communication problem. {1} - {2}", ep.RoleInstance.Id, communicationException.Message, communicationException.StackTrace);       ((ICommunicationObject)client).Abort();     }   } } 
    Note:
    Internally, worker roles expose an endpoint that uses the IClientNotification contract. This is the same contract used by worker roles to communicate back with clients. NotifyAllNodes iterates over every instance of the worker role, except the current instance, since the worker role does not need to notify itself, retrieves its internal endpoint, and invokes the UpdateClientList operation to alert each worker role about session activity in the current instance.

  9. Next, add a new ForwardMessage method to the WorkerRole class. This method uses the internal channel to forward messages from clients in the current role to the role where the target session is active.

    (Code Snippet – Windows Azure Worker Role CommunicationEx02 ForwardMessage - CS)

    C#
    ///  /// Forwards a message from the current role to the role of the destination session. ///  /// The message to forward. /// The ID of the source session. /// The ID of the target session. public static void ForwardMessage(string message, string fromSessionId, string toSessionId) {   SessionInformation session = SessionManager.GetSession(toSessionId);   if (session == null)   {     return;   }    // retrieve the endpoint for the role instance where the target session is active   var targetRole = RoleEnvironment.CurrentRoleInstance.Role.Instances                    .Where(role => role.Id == session.RoleId).FirstOrDefault();   if (targetRole != null)   {     var ep = targetRole.InstanceEndpoints["NotificationService"];     if (ep != null)     {       EndpointAddress address =           new EndpointAddress(String.Format("net.tcp://{0}/NotificationService", ep.IPEndpoint));        IClientNotification client = WorkerRole.factory.CreateChannel(address);       try       {         client.DeliverMessage(message, fromSessionId, toSessionId);         ((ICommunicationObject)client).Close();       }       catch (TimeoutException timeoutException)       {         Trace.TraceError("Unable to forward message to instance '{0}'. The service operation timed out. {1}", ep.RoleInstance.Id, timeoutException.Message);         ((ICommunicationObject)client).Abort();       }       catch (CommunicationException communicationException)       {         Trace.TraceError("Unable to forward message to instance '{0}'. There was a communication problem. {1} - {2}", ep.RoleInstance.Id, communicationException.Message, communicationException.StackTrace);         ((ICommunicationObject)client).Abort();       }     }   } } 
    Note:
    The ForwardMessage method retrieves the target session information to determine its role ID, obtains a reference to the corresponding worker role and then retrieves its internal endpoint. Finally, it creates a new channel instance and calls the DeliverMessage operation to forward the message to the target worker role over the internal endpoint.

Task 2 – Receiving Notifications from Other Worker Roles

In order to allow communication between clients connected to different instances of the service, worker roles must act as proxies for remote clients. Clients send messages and notifications to their role, which in turn forwards them to the remote worker role using the same contract that the service uses to communicate back with clients.

In this task, you extend the chat service class to implement the client notification contract.

  1. Open the ChatService.cs file in the AzureTalk.Service project.
  2. Add the IClientNotification interface to the list of contracts implemented by the ChatService class.C#
    public class ChatService : IChatService, IClientNotification {    ... 
  3. Add the UpdateClientList method to the ChatService class. Worker role instances use this operation to notify their peers whenever a client starts or ends a session.

    (Code Snippet – Windows Azure Worker Role CommunicationEx02 UpdateClientList - CS)

    C#
    ///  /// Receives notifications when a new client connects or disconnects in another worker role. ///  /// The ClientInformation object for the client. public void UpdateClientList(ClientInformation clientInfo) {   if (clientInfo.IsActive)   {     SessionInformation session;     if (SessionManager.CreateOrUpdateSession(clientInfo.SessionId, clientInfo.UserName, clientInfo.RoleId, null, out session))     {       Trace.TraceInformation("Remote session '{0}' by user '{1}' has been opened in role '{2}'.", session.SessionId, session.UserName, session.RoleId);     }   }   else   {     SessionManager.RemoveSession(clientInfo.SessionId);     Trace.TraceInformation("Remote session '{0}' by user '{1}' has been closed in role '{2}'.", clientInfo.SessionId, clientInfo.UserName, clientInfo.RoleId);   }    NotifyConnectedClients(clientInfo); } 
    Note:
    The UpdateClientList method determines whether a session is active and then registers it with the Session Manager. If a session has ended, it calls the Session Manager to remove it. In both cases, it callsNotifyConnectedClients to inform every client connected to the worker role about the session activity.

  4. At this point, you may wish to review the DeliverMessage method, which you implemented in the previous exercise to send messages to the client. Here, you take advantage of this method to implement theDeliverMessage operation of the IClientNotification contract, hence, no additional code is required to fulfill the contract.

Task 3 – Sending Notifications to Other Worker Roles

In this task, you update the service to send notifications to other worker roles whenever a new client connects or disconnects and when clients in the current role send messages to peers in different roles.

  1. Open the ChatService.cs file in the AzureTalk.Service project.
  2. Locate the Register method and insert a call to NotifyAllNodes inside the embedded handler for the Closedevent of the channel and immediately following the call to NotifyConnectedClients. In addition, insert a second call to NotifyAllNodes after the call to NotifyAllNodes in the main body of the method. This ensures that worker roles are alerted both when clients open and when they close a session.C#
    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);       WorkerRole.NotifyAllNodes(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);    // Notify other worker roles   WorkerRole.NotifyAllNodes(session);    return new ClientInformation()   {     SessionId = sessionId,     UserName = userName,     RoleId = roleId   }; } 
  3. Next, replace the body of the SendMessage method with the code shown (highlighted) below. The changed code determines whether a client is local or remote and if necessary, forwards the message to the target worker role.

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

    C#
    public void SendMessage(string message, string sessionId) {   string fromSessionId = OperationContext.Current.SessionId;   SessionInformation toSession = SessionManager.GetSession(sessionId);    // if recipient is connected to this role, deliver the message to the    // recipient; otherwise, forward the message to the recipient's role   if (toSession != null)   {     if (toSession.RoleId == RoleEnvironment.CurrentRoleInstance.Id)     {       this.DeliverMessage(message, fromSessionId, sessionId);     }     else     {       WorkerRole.ForwardMessage(message, fromSessionId, sessionId);     }   } } 
    Note:
    The SendMessage operation retrieves the target session, obtains its role ID and compares it with the ID of the current worker role. If the IDs match, the message is sent directly to the client using DeliverMessage; otherwise, the message is forwarded to the target role using WorkerRole.ForwardMessage.

Verification

You will now test the updated solution using two instances of the worker role. To determine whether clients connected to different worker roles are able to communicate with each other, you will start two instances of the client application and connect each one to a different role. Finally, you will exchange messages between these two instances to establish that messages and notifications flow between worker roles.

  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 two instances of the worker role are currently active.
  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.
  5. 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. You can verify the connected role by checking the title bar of the application that shows the name of the current user and the ID of the worker role where the client is connected. Each client should connect to a different worker role.
    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.

    Figure 4 Verifying the connected worker role ID of the client

  6. In the second client, notice how the online users list includes the first client. Switch to the first instance of the client application to make sure that the service notified it about the new active user. This shows that notifications flow from the role where the second user connected to the first role and then back to the client application. Contrast this with the result obtained after you completed the first exercise, where clients in different roles were unaware of each other.
  7. Switch to the development fabric UI and examine the logs for each of the worker roles. View the entries for the sessions that just started and see how the log registers local and remote sessions.

    Figure 5 Local and remote sessions logged in the worker role log

  8. In the first instance of the client application, select the other active user from the list, type a message and clickSend. Switch to the second instance of the application to verify that the service delivered the message.
  9. In the second instance, type a reply and click Send once again. Notice that the service delivers messages across worker roles in both directions.
  10. Finally, click Sign out in one of the instances of the client application. Switch to the other instance and notice that the server immediately notifies the client attached to the role about the session ending in the other role.

Exercise 2: Using Internal Endpoints for Inter-Role Communication

So far, even though multiple instances of the worker role can each host its own endpoint for the chat service, they all operate as independent servers. Clients connected to a given instance are unable to exchange messages with peers in other worker role instances because roles do not share session information.

In this exercise, you extend the worker role implementation to allow different instances to exchange session information and client notifications over an internal endpoint. To forward messages and session activation events, worker roles need to implement not only the incoming contract of the chat service but also the callback contract implemented by clients. This change allows roles to act as a bridge between peers active in different roles and changes the flow of information in the following manner.

Client Registration

During registration, worker roles notify every other worker role through their internal endpoints. Each alerted worker role then notifies its directly connected clients about the new session.

Figure 1 Roles exchanging session information via an internal endpoint

  1. Client 1 calls the Register operation to start a new session in Worker Role A.
  2. Worker Role A registers the session with its Session Manager and then calls UpdateClientList in each instance of the internal channel endpoint to notify other worker roles about the new session.
  3. Worker Role B receives the notification and then calls UpdateClientList in the callback channel to notify Client 2 that Client 1 has connected.

Sending Messages

When sending messages to peers with session in other worker roles, clients first send the message to their role, which forwards the message through the callback interface to the second role using an internal endpoint. The target role then delivers the message to the recipient using the callback channel to the client. In this case, only the worker roles for the origin and destination are involved in the exchange.

Figure 2 Users connected to different worker roles exchanging messages

  1. Client 1 calls SendMessage to send the message to Worker Role A indicating Client 2 as the recipient.
  2. Worker Role A queries the Session Manager, determines that Client 2 has a session in Worker Role B and then calls DeliverMessage over the internal channel endpoint to forward the message only to this role.
  3. Worker Role B receives the message and then calls DeliverMessage to deliver the message to Client 2 over the callback channel.

Task 1 – Creating an Inter-Role Communication Endpoint

In this task, you configure the worker role to define an internal endpoint. Next, you update the service host configuration to add a new WCF service endpoint using the callback channel contract and set it to listen at the address provided by the internal endpoint.

  1. If it is not already open, launch 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 Ex2-InterRoleCommunication\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. Alternatively, you may continue with the solution that you obtained after completing Exercise 1.
  3. Define an internal endpoint 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 role Propertieswindow, change to the Endpoints tab and click Add Endpoint. Set the name of the new endpoint to “NotificationService”, change the Type to “Internal” and leave the Protocol as “tcp”. The worker role will use this TCP endpoint to receive notifications from other worker roles.

    Figure 3 Defining an internal endpoint for the worker role

  4. Press CTRL + S to save the changes to the worker role configuration.
  5. Open the WorkerRole.cs file in the AzureTalk.Service project.
  6. In the WorkerRole class, define a WCF ChannelFactory member that the worker role will use to create the channel objects for communicating with other roles.

    (Code Snippet – Windows Azure Worker Role Communication – Ex02 ChannelFactory - CS)

    C#
    public class WorkerRole : RoleEntryPoint {   /// Channel factory for inter-role notifications.   private static ChannelFactory factory;    /// ServiceHost object for internal and external endpoints.   private ServiceHost serviceHost;   ... 
    Note:
    The worker role caches the channel factory, which is a thread-safe object, in a static member to avoid the cost of re-creating it each time it needs to communicate with other worker roles.

  7. Update the StartChatService method to configure a new internal endpoint for the service host and create the channel factory for inter-role communication. To do this, insert the following (highlighted) code immediately after the code that defines the external endpoint.

    (Code Snippet – Windows Azure Worker Role CommunicationEx02 InternalEndpoint - 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) {   ...   // 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));    // define an internal endpoint for inter-role traffic   RoleInstanceEndpoint internalEndPoint =       RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["NotificationService"];   this.serviceHost.AddServiceEndpoint(      typeof(IClientNotification),      binding,      String.Format("net.tcp://{0}/NotificationService", internalEndPoint.IPEndpoint));    // create channel factory for inter-role communication   WorkerRole.factory = new ChannelFactory(binding);    try   {     this.serviceHost.Open();     Trace.TraceInformation("Chat service host started successfully.");   ... }  
  8. Add a new NotifyAllNodes method to the WorkerRole class. The worker role uses this method to inform other roles whenever a client starts or ends a session in this role.

    (Code Snippet – Windows Azure Worker Role CommunicationEx02 NotifyAllNodes - CS)

    C#
    ///  /// Notifies all available worker roles to update their active sessions list  /// when a new client connects or disconnects. ///  /// The SessionInformation object for the client. internal static void NotifyAllNodes(SessionInformation session) {   // iterate over all instances of the internal endpoint except the current role - no need to notify itself   var current = RoleEnvironment.CurrentRoleInstance;   var endPoints = current.Role.Instances                   .Where(instance => instance != current)                   .Select(instance => instance.InstanceEndpoints["NotificationService"]);    foreach (var ep in endPoints)   {     EndpointAddress address =         new EndpointAddress(String.Format("net.tcp://{0}/NotificationService", ep.IPEndpoint));     IClientNotification client = WorkerRole.factory.CreateChannel(address);      try     {       client.UpdateClientList(session);       ((ICommunicationObject)client).Close();     }     catch (TimeoutException timeoutException)     {       Trace.TraceError("Unable to notify worker role instance '{0}'. The service operation timed out. {1}", ep.RoleInstance.Id, timeoutException.Message);       ((ICommunicationObject)client).Abort();     }     catch (CommunicationException communicationException)     {       Trace.TraceError("Unable to notify worker role instance '{0}'. There was a communication problem. {1} - {2}", ep.RoleInstance.Id, communicationException.Message, communicationException.StackTrace);       ((ICommunicationObject)client).Abort();     }   } } 
    Note:
    Internally, worker roles expose an endpoint that uses the IClientNotification contract. This is the same contract used by worker roles to communicate back with clients. NotifyAllNodes iterates over every instance of the worker role, except the current instance, since the worker role does not need to notify itself, retrieves its internal endpoint, and invokes the UpdateClientList operation to alert each worker role about session activity in the current instance.

  9. Next, add a new ForwardMessage method to the WorkerRole class. This method uses the internal channel to forward messages from clients in the current role to the role where the target session is active.

    (Code Snippet – Windows Azure Worker Role CommunicationEx02 ForwardMessage - CS)

    C#
    ///  /// Forwards a message from the current role to the role of the destination session. ///  /// The message to forward. /// The ID of the source session. /// The ID of the target session. public static void ForwardMessage(string message, string fromSessionId, string toSessionId) {   SessionInformation session = SessionManager.GetSession(toSessionId);   if (session == null)   {     return;   }    // retrieve the endpoint for the role instance where the target session is active   var targetRole = RoleEnvironment.CurrentRoleInstance.Role.Instances                    .Where(role => role.Id == session.RoleId).FirstOrDefault();   if (targetRole != null)   {     var ep = targetRole.InstanceEndpoints["NotificationService"];     if (ep != null)     {       EndpointAddress address =           new EndpointAddress(String.Format("net.tcp://{0}/NotificationService", ep.IPEndpoint));        IClientNotification client = WorkerRole.factory.CreateChannel(address);       try       {         client.DeliverMessage(message, fromSessionId, toSessionId);         ((ICommunicationObject)client).Close();       }       catch (TimeoutException timeoutException)       {         Trace.TraceError("Unable to forward message to instance '{0}'. The service operation timed out. {1}", ep.RoleInstance.Id, timeoutException.Message);         ((ICommunicationObject)client).Abort();       }       catch (CommunicationException communicationException)       {         Trace.TraceError("Unable to forward message to instance '{0}'. There was a communication problem. {1} - {2}", ep.RoleInstance.Id, communicationException.Message, communicationException.StackTrace);         ((ICommunicationObject)client).Abort();       }     }   } } 
    Note:
    The ForwardMessage method retrieves the target session information to determine its role ID, obtains a reference to the corresponding worker role and then retrieves its internal endpoint. Finally, it creates a new channel instance and calls the DeliverMessage operation to forward the message to the target worker role over the internal endpoint.

Task 2 – Receiving Notifications from Other Worker Roles

In order to allow communication between clients connected to different instances of the service, worker roles must act as proxies for remote clients. Clients send messages and notifications to their role, which in turn forwards them to the remote worker role using the same contract that the service uses to communicate back with clients.

In this task, you extend the chat service class to implement the client notification contract.

  1. Open the ChatService.cs file in the AzureTalk.Service project.
  2. Add the IClientNotification interface to the list of contracts implemented by the ChatService class.C#
    public class ChatService : IChatService, IClientNotification {    ... 
  3. Add the UpdateClientList method to the ChatService class. Worker role instances use this operation to notify their peers whenever a client starts or ends a session.

    (Code Snippet – Windows Azure Worker Role CommunicationEx02 UpdateClientList - CS)

    C#
    ///  /// Receives notifications when a new client connects or disconnects in another worker role. ///  /// The ClientInformation object for the client. public void UpdateClientList(ClientInformation clientInfo) {   if (clientInfo.IsActive)   {     SessionInformation session;     if (SessionManager.CreateOrUpdateSession(clientInfo.SessionId, clientInfo.UserName, clientInfo.RoleId, null, out session))     {       Trace.TraceInformation("Remote session '{0}' by user '{1}' has been opened in role '{2}'.", session.SessionId, session.UserName, session.RoleId);     }   }   else   {     SessionManager.RemoveSession(clientInfo.SessionId);     Trace.TraceInformation("Remote session '{0}' by user '{1}' has been closed in role '{2}'.", clientInfo.SessionId, clientInfo.UserName, clientInfo.RoleId);   }    NotifyConnectedClients(clientInfo); } 
    Note:
    The UpdateClientList method determines whether a session is active and then registers it with the Session Manager. If a session has ended, it calls the Session Manager to remove it. In both cases, it callsNotifyConnectedClients to inform every client connected to the worker role about the session activity.

  4. At this point, you may wish to review the DeliverMessage method, which you implemented in the previous exercise to send messages to the client. Here, you take advantage of this method to implement theDeliverMessage operation of the IClientNotification contract, hence, no additional code is required to fulfill the contract.

Task 3 – Sending Notifications to Other Worker Roles

In this task, you update the service to send notifications to other worker roles whenever a new client connects or disconnects and when clients in the current role send messages to peers in different roles.

  1. Open the ChatService.cs file in the AzureTalk.Service project.
  2. Locate the Register method and insert a call to NotifyAllNodes inside the embedded handler for the Closedevent of the channel and immediately following the call to NotifyConnectedClients. In addition, insert a second call to NotifyAllNodes after the call to NotifyAllNodes in the main body of the method. This ensures that worker roles are alerted both when clients open and when they close a session.C#
    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);       WorkerRole.NotifyAllNodes(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);    // Notify other worker roles   WorkerRole.NotifyAllNodes(session);    return new ClientInformation()   {     SessionId = sessionId,     UserName = userName,     RoleId = roleId   }; } 
  3. Next, replace the body of the SendMessage method with the code shown (highlighted) below. The changed code determines whether a client is local or remote and if necessary, forwards the message to the target worker role.

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

    C#
    public void SendMessage(string message, string sessionId) {   string fromSessionId = OperationContext.Current.SessionId;   SessionInformation toSession = SessionManager.GetSession(sessionId);    // if recipient is connected to this role, deliver the message to the    // recipient; otherwise, forward the message to the recipient's role   if (toSession != null)   {     if (toSession.RoleId == RoleEnvironment.CurrentRoleInstance.Id)     {       this.DeliverMessage(message, fromSessionId, sessionId);     }     else     {       WorkerRole.ForwardMessage(message, fromSessionId, sessionId);     }   } } 
    Note:
    The SendMessage operation retrieves the target session, obtains its role ID and compares it with the ID of the current worker role. If the IDs match, the message is sent directly to the client using DeliverMessage; otherwise, the message is forwarded to the target role using WorkerRole.ForwardMessage.

Verification

You will now test the updated solution using two instances of the worker role. To determine whether clients connected to different worker roles are able to communicate with each other, you will start two instances of the client application and connect each one to a different role. Finally, you will exchange messages between these two instances to establish that messages and notifications flow between worker roles.

  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 two instances of the worker role are currently active.
  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.
  5. 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. You can verify the connected role by checking the title bar of the application that shows the name of the current user and the ID of the worker role where the client is connected. Each client should connect to a different worker role.
    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.

    Figure 4 Verifying the connected worker role ID of the client

  6. In the second client, notice how the online users list includes the first client. Switch to the first instance of the client application to make sure that the service notified it about the new active user. This shows that notifications flow from the role where the second user connected to the first role and then back to the client application. Contrast this with the result obtained after you completed the first exercise, where clients in different roles were unaware of each other.
  7. Switch to the development fabric UI and examine the logs for each of the worker roles. View the entries for the sessions that just started and see how the log registers local and remote sessions.

    Figure 5 Local and remote sessions logged in the worker role log

  8. In the first instance of the client application, select the other active user from the list, type a message and clickSend. Switch to the second instance of the application to verify that the service delivered the message.
  9. In the second instance, type a reply and click Send once again. Notice that the service delivers messages across worker roles in both directions.
  10. Finally, click Sign out in one of the instances of the client application. Switch to the other instance and notice that the server immediately notifies the client attached to the role about the session ending in the other role.