Revista MSDN Magazine Edição 09 - Sistemas Distribuídos com .NET Remoting

Este artigo mostra os principais conceitos do .NET Remoting e uma aplicação real utilizando estes conceitos.

Clique aqui para ler todos os artigos desta edição

 

Sistemas Distribuídos com .NET Remoting<

por Gustavo Nalle Fernandes

 

Atualmente grande parte das aplicações multicamada construídas utilizam o browser para fornecer a interface gráfica com o usuário, sob a justificativa de aproveitar a estrutura que a internet oferece e de que todos os sistemas operacionais modernos já incluem um browser. De certo modo isso faz sentido, mas a medida que a aplicação vai se tornando complexa, fica cada vez mais difícil encarar as limitações do browser, causando um impacto imenso no tempo de desenvolvimento e na qualidade do código.

Não fazer uso do browser não significa que seu sistema não possa fazer uso da internet. Ele pode ter um front-end criado com Windows Forms que além de oferecer uma experiência mais rica para o usuário pode perfeitamente se comunicar através da rede utilizando diversos protocolos, “conversando” com diversos sistemas diferentes executando tanto em plataformas .NET  como plataforma não .NET.

É neste cenário que entra o .NET Remoting, que oferece toda a infra-estrutura necessária para que seu sistema se torne distribuído. Este artigo mostra os principais conceitos do .NET Remoting e uma aplicação real utilizando estes conceitos.

 

Application Domains

Abrindo o gerenciador de tarefas do Windows, você certamente verá uma lista de processos que estão em execução no momento. Um processo permite que uma determinada aplicação possua seu próprio bloco de memória reservado, evitando que um processo mal comportado afete os demais.

Quando você executa uma aplicação gerenciada (um executável C#, por exemplo), o sistema operacional carrega o Common Language Runtime (CLR) em um novo processo e o CLR cria um Application Domain (domínio de aplicação) dentro desse processo, onde a aplicação gerenciada é executada. Um Application Domain é uma segmentação lógica de um processo de modo a permitir isolamento mesmo dentro de um mesmo processo.

Um processo pode ser divido em vários Application Domains, cada um executando uma aplicação diferente. O isolamento oferecido pelos Application Domains permite carregar e descarregar uma aplicação dentro de um processo e definir políticas de segurança diferentes para cara aplicação. Um exemplo desse isolamento é o ASP.NET: dentro de um mesmo processo existem vários sites web. Cada site web possui sua própria configuração de segurança e pode ser criado ou destruído independentemente das demais.

 

Contextos

Objetos que são criados dentro de um Application Domain podem estar ou não associados a um contexto. Um contexto é um agrupamento de objetos que possuem o mesmo comportamento e compartilham os mesmos parâmetros. Contextos são importantes porque permitem que chamadas à objetos dentro dele  sejam interceptadas antes de chegar ao destino. Como exemplo, considere o requisito de gerar uma entrada num arquivo de log cada vez que um método de um objeto é chamado. Uma solução é criar um contexto que agrupará todos os objetos que terão seus métodos logados. A cada chamada realizada por métodos desses objetos, o CLR interceptará essas chamadas, onde você poderá escrever no arquivo de log informações sobre o método e somente depois a chamada alcançará o objeto de destino. Desse modo, nenhum objeto dentro do contexto conterá código de log, o que é extremamente vantajoso. Contextos na plataforma .NET viabilizam o uso de AOP (Aspect Oriented Programming), paradigma no qual é possível isolar código como log, tratamento de erros, verificação de segurança (ou seja, códigos não relacionados diretamente com a função primária do objeto) em um local centralizado, tornando mais fácil a manutenção. Não faz parte do escopo desse artigo o detalhamento sobre contextos, basta saber que os contextos são mecanismos de segmentação de um Application Domain.

 

Definição de .NET Remoting

Um sistema .NET é considerado distribuído e utiliza a infra-estrura do .NET Remoting se ocorrer chamadas entre objetos pertencentes a diferentes contextos ou Application Domains. A Figura 1 mostra diversas situações em que ocorrem chamadas entre objetos de diferentes Application Domains e contextos. Note que mesmo dentro de um mesmo Application Domain ocorrem chamadas que se utilizam da infra-estrutura do .NET Remoting (chamadas remotas). Um dos objetivos do .NET Remoting é permitir que você acesse objetos de outro processo do mesmo modo que objetos locais. Quem está acostumado a trabalhar com computação distribuída usando DCOM, perceberá que o .NET Remoting é mais flexível e poderoso. Ao invés de evoluir a tecnologia DCOM, o .NET remoting optou por uma redefinição completa. No restante desse artigo, chamarei de objeto remoto (ou servidor) o objeto que reside em outro Application Domain, e de cliente o objeto que deseja chamar um método do objeto remoto.

 



Figura 1. Segmentação lógica dos processos e tipos de objeto no .NET Remoting:

 

Trafegando entre processos

Para que dois objetos pertencentes à diferentes Application Domains possam se comunicar, é necessário que ambos sejam do tipo Remotable. Na Figura 1, note que chamadas entre contextos e Application Domains sempre envolvem somente objetos do tipo Remotable. É possível tornar um objeto qualquer em um objeto Remotable de duas maneiras: usando Marshal-By-Value ou Marshal-By-Ref.

 

Marshal-By-Value são objetos capazes de trafegar de um Application Domain para outro. Quando você lida com comunicação com objetos remotos certamente precisará passar objetos como parâmetros de métodos e receber objetos em retorno. Esses parâmetros e objetos de retorno precisam trafegar de um Application Domain para outro, e para que isso seja possível, o .NET utiliza um processo chamado serialização, responsável por transformar um objeto em uma seqüência de bytes para que ele possa ser transportado pela rede. Deserialização é o processo inverso.

O .NET framework serializa e deserializa automaticamente os objetos que direta ou indiretamente participam de uma comunicação remota desde que os mesmos utilizem o atributo Serializable. O atributo NonSerialized pode ser usado para marcar campos dentro da classe que não devem ser serializados. Veja na Listagem 1 um exemplo de um objeto serializável, sendo que um dos campos (curriculum) não será serializado, ou seja, seu valor não estará no conjunto de bytes gerado.

Você deve usar com cautela essa modalidade de Remotable, visto que o .NET irá serializar toda a hierarquia de objetos contidos dentro do objeto marcado como serializável (exceto os campos marcados como não serializável). Dependendo do tamanho dessa hierarquia, a quantidade de dados resultante da serialização pode ser proibitiva. Outro cuidado é que se dentro dessa hierarquia existir um objeto que não possa ser serializável, o processo de serialização é interrompido e uma excessão é gerada.

 

Listagem 1: Objeto serializável com um campo não serializável

[Serializable()]     

public class Pessoa  {

  public int idade;

  public string nome;

  public string endereco;

   

  [NonSerialized()]public string curriculum;

     

  public Pessoa () {

  }

}

 

Marshal-By-Ref são objetos que ao contrário do Marshal-By-Value, não trafegam entre Application Domains. Ao invés disso, o .NET Remoting cria um Proxy para o objeto remoto no mesmo Application Domain do objeto que o chama. Proxy em inglês significa “procurador”, e é exatamente isso que acontece quando você chama um método de um objeto Marshal-By-Ref, pois você não lida diretamente com o objeto remoto, mas sim com um representante. O Proxy atua como intermediário entre o seu objeto e o objeto remoto, de tal forma que tudo se passa como se você tivesse lidando diretamente com o objeto remoto. Para que um objeto seja do tipo Marshal-By-Ref, basta fazê-lo herdeiro da classe MarshalByRefObject do namespace System, conforme mostra a Listagem 2. Perceba que o objeto contém apenas um método que retorna um objeto do tipo pessoa, mostrado na Listagem 1. Todos os parâmetros e valores de retorno desse método devem obrigatoriamente ser Remotable para que a chamada remota funcione, caso contrário uma exceção é gerada.

 

Listagem 2: Objeto Remotable do tipo Marshal-By-Ref

 class MinhaClasse : MarshalByRefObject {

   

  public Pessoa getPessoa(int id) {

    // Implementação...

  }

}

 

Ativando um Objeto remoto

Antes de utilizar um objeto Remotable, é necessário criar uma instância dele, ou em outras palavras, ativá-lo. Os objetos Marshal-By-Ref suportam dois tipos de ativação: cliente e servidor, sendo que a ativação pelo servidor pode ser Singleton ou Single Call. A ativação pelo servidor é feita justamente no Application Domain do objeto remoto, que disponibiliza um URI e um nome para que os objetos clientes possam acessar o objeto remoto. O controle do tempo em que o objeto está ativo e do número de instâncias ativas é feito remotamente.

No modo Singleton, uma única instância do objeto remoto existe para servir todos os clientes. No modo single call, o objeto servidor é criado somente para servir um método chamado pelo cliente. A ativação pelo cliente é feita pelo objeto que chama o objeto remoto. Nessa modalidade, cada cliente que acessa o objeto remoto cria uma instância do mesmo, estabelecendo uma relação uma para um. O objeto remoto somente existe para servir um único cliente e pode criar uma sessão com o cliente, de modo a armazenar variáveis entre chamadas de métodos. A Tabela 1 mostra um comparativo entre os tipos de ativação apresentados.

 

 

 

Tabela 1. Comparativo entre os tipos de ativação

 

Tipo de Ativação

Cliente

Server – Single Call

Server – Singleton

Vantagens

Permite ao cliente controlar o tempo de vida do objeto remoto;

Permite  manter estado entre as chamadas. Útil quando se deseja alto grau de conversação entre cliente e servidor

Um objeto para cada cliente sem manter estado; objeto é destruído ao fim da chamada do método. Útil quando não é preciso manter estado e deseja-se maior economia de recursos no lado servidor

Um único objeto serve todos os clientes, permitindo operações conjuntas envolvendo todos os clientes, como por exemplo callbacks e log de conexões

Controle do tempo de vida

cliente

Servidor

Servidor

Estado entre chamadas

sim

não

sim, o servidor é o mesmo para todos os clientes

Multiplicidade cliente-servidor

Um objeto servidor para cada cliente

Um objeto servidor para cada cliente

Um objeto servidor para todos os clientes

 

 

Publicando um objeto para acesso remoto

Application Domains se comunicam através de Channels (canais). Os channels são usado pelo .NET Remoting para transportar objetos serialializáveis e proxys. O .NET Framework suporta dois tipos de Channels: HTTP e TCP, mas é possível criar seu próprio channel. Para cada tipo de channel, você pode definir o payload das mensagens que trafegam na rede: SOAP ou binário. Desse modo, um channel TCP com payload binário é o mecanismo mais eficiente, enquanto que um channel HTTP com payload SOAP é o menos eficiente em termos de largura de banda. Por outro lado, um channel HTTP com payload SOAP permite interação com clientes não .NET, por se tratar de protocolos amplamente suportados.

De modo a tornar seu objeto pronto para o acesso remoto, você precisa realizar as seguintes tarefas:

- Tornar seu objeto e os objetos dependentes (parâmetros e valores de retorno) do tipo Remotable;

- Registrar seu objeto especificando uma URI e o tipo de ativação desejado;

- Criar um channel que fica esperando conexões em determinada porta.

 

No exemplo a seguir, você verá como criar uma aplicação distribuída contemplando todos esses passos.

 

Exemplo: Chat

Mostrarei aqui como criar uma aplicação real usando .NET Remoting, de modo a consolidar todos os conceitos mostrados nesse artigo. O exemplo é uma aplicação simples de chat, onde uma aplicação Console C# será o servidor e clientes criados com Windows Forms se conectarão para entrar numa sala de bate-papo. O servidor gerará um evento cada vez que um cliente se conectar, desconectar ou enviar mensagens, e os clientes receberão esses eventos de modo a atualizar sua interface gráfica, retirando usuários da lista caso os mesmos sejam desconectados, adicionando novos usuários caso novas conexões surjam e exibindo mensagem sempre que algum usuário escrever algo. O mecanismo de notificação é feito usando-se eventos e delegates.

Os clientes irão interagir com o servidor através de uma interface (Listagem 3), de modo a reduzir o acoplamento entre os clientes e a implementação do servidor. A interface contém três métodos, um para o cliente se conectar, outro para se desconectar e um para enviar mensagem. A interface também declara um evento, para que os clientes se inscrevam para receber notificações do servidor.

 

Listagem 3: IChatServer.cs - interface do servidor de chat

using System;

using System.Collections;

namespace MSDNMagazine

{

  namespace Remoting

  {

            namespace Servidor

            {

               public delegate void ChatEventHandler(

                                   object sender,ChatEventArgs args);

               public interface IChatServer

               {

                         event ChatEventHandler chatEvent;

                         void sendMessage(String apelido, String mensagem);   

                         ArrayList connect(String apelido);

                         void disconnect(String apelido);

               }

            }

  }

}

Uma classe chamada ChatEventArgs é responsável por fornecer detalhes do evento que o servidor gera. A Listagem 4 mostra a implementação dessa classe. A classe ChatEventArgs é bem simples, declarando um enumeration contendo o tipo de evento  (CONEXAO, MENSAGEM ou DESCONEXAO), além de armazenar o usuário que gerou a mensagem e a mensagem enviada. Perceba que como a classe trafega do servidor para o cliente, e ambos se encontram em Application Domains diferentes, a classe ChatEventArgs é marcada com o atributo [Serializable].

 

Listagem 4. ChatEventArgs.cs

using System;

 

namespace MSDNMagazine

{

 namespace Remoting

 {

   namespace Servidor

   {

              [Serializable]

               public class ChatEventArgs:EventArgs

               {

                         public  enum EventType { CONEXAO, MENSAGEM, DESCONEXAO};

                         private String v_usuario;

                         private String v_mensagem;

                         private EventType v_tipo;

 

                         public ChatEventArgs(String usuario,String mensagem,EventType tipo_evento)

                         {

                                   v_usuario = usuario;

                                   v_mensagem = mensagem;

                                   v_tipo = tipo_evento;

                         }

 

                         public string usuario

                         {

                                    get

                                    {

                                               return v_usuario;

                                    }

                                    set

                                    {

                                               v_usuario = value;

                                    }

                          }

 

                         public string mensagem

                          {

                                    get

                                    {

                                               return v_mensagem;

                                    }

                                    set

                                    {

                                               v_mensagem = value;

                                    }

                           }

 

                         public EventType tipo_evento

                           {

                                    get

                                    {

                                                return v_tipo;

                                    }

                                    set

                                    {

                                                v_tipo = value;

                                    }

                           }

               }

   }

 }

}

 

A Listagem 5 mostra a classe servidora que  implementa a interface IChatServer.

 

Listagem 5 – Servidor.cs - implementação do servidor de chat

using System;

using System.Collections;

using System.Reflection;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels.Http;

using System.Runtime.Remoting.Channels;

using System.Runtime.Serialization.Formatters;

 

namespace MSDNMagazine

{

  namespace Remoting

  {

    namespace Servidor

            {

               public class ChatServer: MarshalByRefObject, IChatServer

               {

                         private ArrayList clientes = new ArrayList();

                         public event ChatEventHandler chatEvent;

                         private void notifyClients(ChatEventArgs args)

                         {

                                   System.Delegate[] assinantes = chatEvent.GetInvocationList();

                                   IEnumerator lista = assinantes.GetEnumerator();

                                   while(lista.MoveNext())

                                   {

                                               ChatEventHandler handler = (ChatEventHandler)lista.Current;

                                               try

                                               {

                                                  IAsyncResult ar = handler.BeginInvoke(this, args, null, null);

                                               }

                                               catch(System.Exception)

                                               {

                                                           chatEvent=chatEvent-handler;

                                               }

                                    }

                        }

 

                         public void sendMessage(String apelido, String mensagem)

                         {

                                   ChatEventArgs args = new ChatEventArgs(

                                               apelido, mensagem,ChatEventArgs.EventType.MENSAGEM);

                                   notifyClients(args);

                         }

 

                         public ArrayList connect(String apelido)

                         {

                                    ChatEventArgs args = new ChatEventArgs(

                                                apelido,null,ChatEventArgs.EventType.CONEXAO);

                                    clientes.Add(apelido);

                                    notifyClients(args);

                                    return clientes;

                         }

                       

                         public void disconnect(String apelido)

                         {

                                    ChatEventArgs args = new ChatEventArgs(

                                                apelido,null,ChatEventArgs.EventType.DESCONEXAO);

                                    notifyClients(args);

                                    clientes.Remove(apelido);

                         }

 

                         public override object InitializeLifetimeService()

                         {

                                    return null;

                         }

 

                         static void Main(string[] args)

                         {                                                                   

                                    RemotingConfiguration.Configure( @"server.exe.config" );

                                    Console.WriteLine("Server ready");

                                    Console.ReadLine();

                         }

               }

            }

  }

}

 

Como o servidor de Chat será acessado por classes de diferentes Application Domains, ele extende a classe

MarshalByRefObject. Internamente a classe Servidora declara duas variáveis: um ArrayList chamado clientes e um evento chamado chatEvent. Cada vez que um cliente chama o método connect, passando como parâmetro o apelido desejado, o servidor insere o cliente no ArrayList, cria uma instância de ChatEventArgs contendo o cliente que se conectou e notifica todos os clientes conectados que uma nova conexão foi estabelecida. Quando o cliente chama o método disconnect, o servidor retira o cliente do ArrayList e notifica todos os clientes do evento. E quando o método sendMessage é chamado por algum cliente, o servidor cria uma instância de ChatEventArgs contendo a mensagem enviada e o usuário que enviou a mensagem, e a seguir notifica todos os clientes.

Toda notificação é feita usando o método privado notifyClients, que é o responsável por chamar todos os delegates dos clientes inscritos em receber eventos do servidor.

O método Main da classe ChatServer  utiliza a classe RemotingConfiguration do namespace  System.Runtime.Remoting para configurar a infra-estrutura Remoting, cujas propriedades estão no arquivo Server.exe.config que deve estar no mesmo diretório do assembly do servidor. A Listagem 6 mostra o arquivo Server.exe.config.

 

 

Basicamente, o arquivo Server.exe.config declara um objeto Marshal-by-ref (presença do elemento wellknown) cuja ativação é feita pelo servidor e é do tipo Singleton (atributo mode). Os clientes se conectarão ao servidor de Chat através do nome “chatserver” (atributo objectUri). Um Application Domain pode expor vários objetos remotos, e a diferenciação entre eles é feita justamente pelo nome. O Application Domain no qual o servidor será executado ficará esperando por conexões na porta 80 (atributo port do elemento channel) e o channel é do tipo HTTP, ou seja, as comunicações realizadas pelo .NET remoting serão feitas usando requisições e respostas HTTP (atributo ref do elemento channel). O formato das mensagens internas do .NET Remoting trocadas entre cliente e servidor será do tipo SOAP (atrinuto ref do elemento formatter).

O uso de um arquivo externo para configurar propriedades da infra-estrutura do .NET remoting permite por exemplo alterar o tipo de channel utilizado sem precisar recompilar o código e também permite retirar do código detalhes de comunicação remota. É possível obter o mesmo efeito que os arquivos de configuração de modo programático, mas esse procedimento não será abordado nesse artigo.

 

Na Listagem 7 você encontra um fragmento do código do cliente do chat, sendo que o completo está disponível para download

 

Listagem 7. ClienteChat.cs – Classe cliente baseada em windows forms

 

using System;

using System.Drawing;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Net;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels.Http;

using System.Runtime.Remoting.Channels;

using MSDNMagazine.Remoting.Servidor;

using System.Runtime.Serialization.Formatters;

 

namespace MSDNMagazine

{

  namespace Remoting

  {

    namespace Cliente

            {

              public class ClienteChat : System.Windows.Forms.Form

              {

                         // Referência ao servidor

                         private IChatServer servidor;

 

                         // Obtém referência do servidor de chat.

                         public IChatServer getChatServer()

                         {                                                       

                                   if(servidor==null)

                                   {

                                     // Obtém uma lista de todos os objetos marshal-by-ref

                                     // registrado no client. Essa informação vem do

                                     // arquivo client.exe.config

                                     WellKnownClientTypeEntry[] ClientEntries =

                                                 RemotingConfiguration.GetRegisteredWellKnownClientTypes();

 

                                     // Usa a classe Activator, que tem a capacidade de criar

                                     // instâncias locais e remotas de um objeto. Neste caso, é

                                     // passado ao método GetObject o tipo de classe desejada

                                     // a a URL (obtida do método GetRegisteredWellKnownClientTypes)

                                     servidor = (IChatServer)Activator.GetObject(typeof(IChatServer),

                                                           ClientEntries[0].ObjectUrl );

                                     }

                                     return servidor;

                         }

 

                         // Conecta so servidor

                         public void connect()

                         {

                                    IChatServer servidor = getChatServer();

                                    // Cria instância do delegate que será chamado

                                    // pelo servidor quando um evento ocorrer

                                    ChatEventHandler handler = new ChatEventHandler(handleMessageEvent);

                                    // Registra o delegate no servidor para ser notificado

                                    // quando um evento ocorrer

                                    servidor.chatEvent+=handler;

                                    // Conecta o servidor e recebe a lista de usuários conectados

                                    ArrayList list = servidor.connect(apelido);

                                    // Chama método interno para preencher o controle

                                    // com a lista de usuários

                                    preencheLista(list);

                         }

 

                         // Preenche lista de usuários

                         private void preencheLista(ArrayList lista)

                         {

                                     IEnumerator num = lista.GetEnumerator();

                                     while(num.MoveNext())

                                     {

                                                if (!num.Current.Equals(apelido))

                                                  lstUsers.Items.Add(num.Current);                                                                  

                                      }

                         }

 

                         // Método interno que adiciona um único usuário

                         // á lstUsers, além de escrever um texto na área de mensagens

                         // alertando que o usuário entrou no chat

                         private void adicionalNovoUsuario(string usuario)

                         {

                                   textBox1.Text=textBox1.Text + "***"+usuario+" entrou no chat\r\n";

                                   lstUsers.Items.Add(usuario);                                                                           

                         }

 

                         // Método interno que remove um usuário

                         // de lstUsers, além de escrever um texto na área de mensagens

                         // alertando que o usuário deixou o chat

                         private void removeUsuario(string usuario)

                         {

                                   textBox1.Text=textBox1.Text + "***"+usuario+" saiu do chat\r\n";

                                   lstUsers.Items.Remove(usuario);

                         }

 

                         // Método interno que escreve uma mensagem na área de mensagens,

                         // precedida pelo apelido do usuário que a enviou

                         private void adicionaNovaMensagem(string usuario,string mensagem) {

                                    textBox1.Text=textBox1.Text + "<"+usuario+"> " + mensagem+"\r\n";

                         }

 

                         // Método de callback chamado pelo servidor quando algum evento

                         // de conexão, desconexão ou envio de mensagens ocorre

                         public void handleMessageEvent(object sender, ChatEventArgs e)

                         {

                                   switch(e.tipo_evento)

                                   {

                                               case(ChatEventArgs.EventType.CONEXAO):

                                                 // Se o evento for do tipo CONEXAO, chama o método

                                                 // interno para atualizar o controle que exibe a

                                                 // lista de usuários

                                                 adicionalNovoUsuario(e.usuario);

                                                 break;

                                               case(ChatEventArgs.EventType.MENSAGEM):

                                                 // Se o evento for do tipo MENSAGEM, chama  método interno

                                            // que atualiza o controle que exibe mensagens

                                                 adicionaNovaMensagem(e.usuario,e.mensagem);

                                                 break;

                                               case(ChatEventArgs.EventType.DESCONEXAO):

                                                 // Se o evento for do tipo DESCONEXAO, chama o método

                                                 // interno para atualizar o controle que exibe a

                                                 // lista de usuários

                                                 removeUsuario(e.usuario);

                                                 break;

                                   }

                         }

                                              

                         // Apelido passado via linha de comando

                         private string nome;             

                         // Controle que contém espaço destinado a conter as mensagens

                         // recebidas

                         private System.Windows.Forms.TextBox textBox1;

                         // Controle que contém a lista de usuários conectadas ao chat

                         private System.Windows.Forms.ListBox lstUsers;

                         // Controle que contém espaço destinado a escrever mensagens

                         private System.Windows.Forms.TextBox textBox2;                  

                         // Botão usado para enviar mensagem

                         private System.Windows.Forms.Button button1;                      

                         //Construtor. Carrega configurações do arquivo cliente.exe.config

                         // que contém configurações da infra estrutura do .NET Remoting

                         public ClienteChat()

                         {

                                   InitializeComponent();

                                   RemotingConfiguration.Configure( @"client.exe.config" );

                                   servidor = getChatServer();

                         }

                       

                         // Obtém o apelido passado via linha de comando, e exibe

                         // a interface grafica do cliente do chat

                         public static void Main(string[] args)

                         {

                                   ClienteChat chatCli = new ClienteChat();

                                   if(args.Length < 1)

                                   {

                                               Console.WriteLine("Uso:\n");

                                               Console.WriteLine("Cliente <APELIDO>\n");

                                   }

                                   else

                                   {

                                               try

                                               {

                                                           chatCli.apelido = args[0];

                                                           chatCli.connect();

                                                           chatCli.Text = chatCli.Text + " - "+ chatCli.apelido;

                                                           Application.Run(chatCli);

                                               }

                                               catch (FormatException)

                                               {

                                                           Console.WriteLine("Endereço IP inválido!");

                                               }                                            

                                   }

                }

 

                         public override object InitializeLifetimeService()

                         {

                                   return null;

                         }

                                                

                         private void button1_Click(object sender, System.EventArgs e)

                         {

                                    servidor.sendMessage(apelido,textBox2.Text);

                                    textBox2.Text = "";     

                         }

 

                         // Ao fechar a janela, chama o método do servidor para avisar

                         // que este cliente foi desconectado

                         private void ClienteChat_Closed(object sender, System.EventArgs e)

                         {

                                    servidor.disconnect(apelido);

                         }

 

                         public string apelido

                         {

                                   get

                                   {

                                               return nome;

                                   }

                                   set

                                   {

                                               nome = value;

                                   }

                         }

              }

            }

  }

}

 

Assim como o servidor, o cliente utiliza um arquivo externo de propriedades contendo informações da infra-estrutura remoting. Esse arquivo está exibido na Listagem 8.

 

Listagem 8. Cliente.exe.config - Configuração das propriedades remoting do cliente

<configuration>

  <system.runtime.remoting>

     <application>

        <channels>

           <channel ref="http" port="0">

              <clientProviders>

                <formatter ref="soap"/>

              clientProviders>

              <serverProviders>

                 <formatter ref="soap" typeFilterLevel="Full"/>

              serverProviders>

           channel>

        channels>

        <client>

           <wellknown

               type="MSDNMagazine.Remoting.Servidor.IChatServer, ChatLib"

               url="http://localhost:80/chatserver"/>

        client>

     application>

  system.runtime.remoting>

configuration>

 

O arquivo cliente.exe.config declara um channel capaz de trocar mensagens HTTP (atributo ref do elemento channel). O formato das mensagens será SOAP (atributo ref do elemento formatter). O channel  escutará por conexões em uma porta determinada pelo .NET Remoting, já que foi especificado “0” no atributo port do elemento channel. O objeto ClienteChat (Listagem 7) precisa funcionar como servidor também, já que receberá callbacks do servidor, daí a necessidade de especifiar uma porta que fica aguardando conexões. Como o objeto cliente também será chamado de outro Application Domain, ele necessita ser Remotable. Perceba que não fiz a classe que implementa o client herdeira de MarshalByRefObject, porque a classe System.Windows.Forms.Form da qual ela deriva já é herdeira de MarshalByRefObject.

O elemento <CLIENT> do arquivo cliente.exe.config é necessário para especificar os objetos remotos que serão utilizados. O tipo e o assembly contendo o objeto remoto devem estar especificados no atributo type do elemento wellKnown. Perceba agora o porque do uso de uma interface ao invés de referenciar diretamente a classe do servidor: se fosse usada a classe ChatServer, seria necessário que o cliente tivesse acesso ao assembly contendo a implementação do servidor. Sempre que o servidor sofressse alteração, você teria que enviá-lo a todos os clientes!

Por fim, a url do objeto remoto, contendo protocolo, porta e o nome do objeto remoto, deve estar especificada no atributo url do elemento wellknown.

 

Assemblies  e Execução

O sistema de Chat possui no total três assemblies, conforme mostra a Figura 2. Tanto na máquina cliente como na máquina servidora o assembly ChatLib.dll está presente, e contém as classes ChatEventArgs e IChatServer, usadas tanto pelo cliente como pelo servidor. Perceba que no servidor o assembly cliente está presente também: isso é necessário porque o servidor envia callbacks para o cliente, e para tal precisa ter acesso ao assembly do cliente. O assembly Server.exe contém somente a classe ChatServer, o assembly Client.exe contém somente a classe ClienteChat.

 

 

Figura 2 – Diagrama de deployment do sistema de Chat

 

O servidor é executado simplesmente  chamando o arquivo Server.exe em um console; o cliente é executado também via console chamando o arquivo client.exe passando como parâmetro o apelido que o usuário deseja entrar no chat. Lembre-se de configurar corretamente a URL do objeto do servidor no arquivo cliente.exe.config A Figura 3 mostra o sistema em execução.

 

Figura 3: Cliente de chat

 

Conclusão

O .NET remoting é uma maneira fácil e poderosa de se construir sistemas distribuídos. Com uma arquitetura altamente modular e plugável, torna-se simples estender o framework de modo a incorporar novos protocolos e novos formatos de mensagens.

Artigos relacionados