Remoting – Entendendo a sua arquitetura – Parte I

 

Neste artigo veremos como deve ser a arquitetura de um projeto que utiliza Remoting. Veremos qual a função de cada parte do sistema ( client, server, host ), como elas se comunicam, como é feito a troca de mensagens nos dois sentidos ( client -> server e server->client), quais partes formam a solução por completo ( dlls, aplicações clients, aplicação host) e quais as dicas de como implementar um projeto do gênero.

 

O projeto de exemplo será um chat, onde neste artigo veremos apenas como funciona a arquitetura de um projeto utilizando Remoting, e nos próximos faremos a implementação de um chat de exemplo. As aplicações clients (WindowsForms) irão se conectar a um servidor, o qual será responsável pela troca de mensagens entre os usuários.

 

A arquitetura

Um projeto Remoting ( falando de uma maneira básica) normalmente é composto de 4 partes: Client, Server, Host, InterfaceLayer.

 

Atenção: este nome InterfaceLayer foi criado por mim apenas para ilustrar minha explicação, não é de forma alguma nenhum nome utilizado comumente na literatura.

 

A figura a seguir mostra cada uma dessas partes, e logo após existe uma pequena explicação de cada uma delas:

 

gbreap1fig01.jpg 

 

Client: Aplicação Windows Forms utilizada pelos usuários.

 

Server: Aplicação servidora que fará o gerenciamento dos usuários e da troca de mensagens entre eles. Está aplicação será uma dll, pois isto nos dá mais liberdade de implantação, permitindo-nos hospidá-lo no IIS ou numa aplicação Console Aplication, por exemplo.

 

Host: Como dito anteriormente, nosso Server será uma dll e obviamente precisamos de uma aplicação para hospedá-lo. Normalmente utilizariamos o IIS ou um Serviço qualquer para isso, mas neste caso utilizaremos um Console Aplication pela simplicidade.

 

InterfaceLayer: Esta dll conterá todas as interfaces do Server, as quais as aplicações clientes utilizarão para se comunicar com o Server, e também todas as interfaces dos Clients, as quais o Server utilizará para se comunicar com os Clients.

Além das interfaces, esta dll conterá os DTOs ( data transfer objets ), e as Exceptions ( aquelas que serão transportadas de um lado para o outro ( Client->Server e Server->Client).

 

Entendendo o problema

Antes de qualquer coisa vamos entender nosso problema e qual a solução que o Remoting trás consigo. Nossas aplicações estarão rodando numa rede local ou na internet, e por isso estarão em máquinas diferentes. Evidentemente, uma aplicação Client numa máquina não terá qualquer referência ao Server, o qual estará numa outra máquina qualquer. Com isto como nossa aplicação Client fará chamadas ao Server, sem qualquer tipo de referência a este?

 

E é claro que temos o mesmo problema em relação as chamadas aos Clients feitas pelo Server.

 

Entendendo a solução

Vamos agora observar como funciona a solução do Remoting. Uma dll ( aqui chamada de InterfaceLayer) conterá, entre outras coisas, diversas interfaces. Essas interfaces são divididas basicamente em dois grupos ( Interfaces-Server e Interfaces-Client ).

 

 - Interfaces-Server: Estas interfaces possuem todos os métodos do Server, os quais as aplicações Clients poderão chamar. As classes do Server irão implementar essas interfaces.

 

A aplicação Client conterá uma referência a esta dll (InterfaceLayer), logo poderá utilizar estas Interfaces-Server.

 

 Por exemplo: Quando a aplicação Client deseja se conectar, ela deve chamar o método “Logar” da classe “Login” do Server, conforme a figura abaixo:

 

gbreap1fig02.jpg 

 

 Entretanto o Client não possui qualquer referência a esta classe. Por sua vez, a classe “Login” herda da interface “ILogin”, localizada na dll InterfaceLayer, e está dll o Client possui referência. Logo o que a aplicação Client fará é chamar o método “logar” da interface “ILogin", e o Remoting fará com que essa chamada seja “transportada” até o Server, e essa chamada ( do método “login” ) será feita em um objeto da classe “Login” no Server. Veja figura abaixo:

 

gbreap1fig03.jpg 

 

O mesmo vale para o outro grupo de interfaces (Interfaces-Client):

 

- Interfaces-Client: Estas interfaces possuem todos os métodos dos Clients, os quais o Server pode fazer chamadas. As classes do Client então irão herdar dessas interfaces.

 

O Server  conterá uma referência a esta dll (InterfaceLayer), logo poderá utilizar estas Interfaces-Client.

 

Por exemplo: Quando o Server deseja enviar uma mensagem a um usuário, ela deve chamar o método “SendMessage” da classe “Communication” do Client. Entretanto o Server não possui qualquer referência a esta classe. Por sua vez, a classe “Communication” herda da interface “ICommunication”, localizada na dll InterfaceLayer, e está dll o Server possui referência. Logo o que o Server fará é chamar o método “SendMessage” da interface “ICommunication ", e o Remoting fará com que essa chamada seja “transportada” até o Client. Essa chamada ( do método “SendMessage” ) será feita em um objeto da classe “Communication” no Client.

(Conforme a figura abaixo)

 

gbreap1fig04.jpg 

 

Perceba que tanto o Client quanto o Server possui referência para as dlls InterfaceLayer, e por isso é essencial que ambas as dlls sejam da mesma versão.                                 

 

Como tudo acontece

Server

 

O Server deixará uma porta disponível para que as aplicações Clients possam se conectar. Além disso, o Server define uma URL para CADA CLASSE que as aplicações Clients poderão fazer chamadas. Então sempre que uma aplicação Client desejar fazer uma chamada a classe, utilizará a URL definida pelo Server.

 

Client

A aplicação Client deverá conhecer o endereço do Server, além da porta que o mesmo estará escutando. Antes de fazer a primeira chamada a qualquer classe, a aplicação deve configurar o Remoting, podendo ser através de código ou por Xml. O Remoting  instanciará um objeto no Server para que o Client possa fazer chamadas (utilizando para isso as interfaces localizadas na dll InterfaceLayer).


Troca de Mensagens

Toda a troca de informações entre as aplicações será feita através de objetos serializáveis, objetos estes definidos na dll InterfaceLayer e/ou no próprio .Net framework.

 

Alguns problemas comuns

 

1. Problema da Comunicação assíncrona Server->Client

 

Como foi dito a pouco, o Server disponibiliza uma porta para que a aplicação Client possa se conectar. Entretanto o contrário não é verdade, pois o Client não possui uma porta a qual ficará escutando.

 

Existem duas soluções comuns para isso:

 

a.    Está solução é a mais óbvia, porém a mais trabalhosa. Trata-se de transformar o Client também em um pequeno servidor. Do mesmo jeito que o Server disponibiliza uma porta para os Clients, o Client pode fazer o mesmo. Assim sempre que o Server quiser se comunicar basta utilizar a porta do Client disponível. Entretanto isto é muito trabalhoso, pois todo Client agora será um pequeno servidor. Um dos inúmeros problemas é em relação a configuração, pois o Server nesse caso precisará saber o endereço e porta de todas as aplicações Clients.

b.    A segunda solução é a seguinte: Passar uma instância da classe do Client, como parâmetro pro Server.

 

Exemplo:

Como mostrado a pouco, no nosso exemplo sempre que o Server quiser se comunicar com o Client deverá utilizar a classe Communication, utilizando para isso a sua interface ICommunication. Como esta classe deverá ser Serializável, o Client passará uma instância dessa classe por parâmetro para o Server na primeira vez que se comunicar com este ( método Login, por exemplo ). O Server então guardará essa instancia num hash, utilizando o username da aplicação Client como chave. Desta forma sempre que o Server quiser fazer uma chamada ao Client basta recuperar do hash a instância da classe do Client, e fazer chamadas utilizando a sua interface.

 

2. Problema da Captura de Exceptions

Um problema muito comum no Remoting é acontecer do Server lançar uma Exception, porém a aplicação Client só receber uma Exception genérica, a qual não possui nenhuma informação a respeito do problema que a gerou.

 

Isso acontece pelo fato das Exceptions não serem Serializaveis por default, logo o Remoting não consegue fazer o transporte de uma aplicação para a outra.

 

Além disso, temos o seguinte fato: se a classe Exception estiver definida no Server, a aplicação Client não a conhecerá, e caso ela esteja definida no Client, o Server não a conhecerá da mesma forma.

 

Logo a solução consiste em tornar as Exceptions Serializaveis e defini-las na dll InterfaceLayer, assim tanto o Server quanto o Client as conhecerão.

 

3. Problema da Aplicações Windows Forms MultiThreading

Existem duas coisas básicas que NÃO SE DEVE fazer em aplicações WindowsForms:

 

·         Nunca invoque qualquer método ou propriedade de um controle UI utilizando uma nova thread. Controles UI são Thread-preferenciáveis, ou seja, você só deve alterá-los utilizando a thread que os criou. Caso você utilize um thread diferente corre o risco de travar sua aplicação.

·         Nunca execute um trecho longo de código em uma Thread da UI, pois isto irá congelar a aplicação durante este tempo.

 

Mas o que isso tem haver com o Remoting?

 

Problema em relação ao item 1:

Quando o Server faz uma chamada ao Client, uma nova thread é utilizada, e não a thread original da UI. Logo se você tentar alterar diretamente qualquer controle da UI utilizando a chamada que o Server fez, você provavelmente irá travar a aplicação em algum momento.

 

Sendo assim, sempre que a aplicação Client receber uma chamada do Server e quiser alterar um controle da UI, deverá utilizar o método BeginInvoke, o qual fará uma chamada a thread da UI para que a mesma invoque o método desejado.

 

Problema em relação ao item 2:

Imagine que uma aplicação Client envie uma mensagem para todos os usuários, chamando para isso um método do Server evidentemente. Se existirem 1000 usuários logados, o método que a aplicação Client chamou poderá retornar apenas quando o Server terminar de enviar a mensagem aos mil usuários. Pior ainda, se você fizer essa chamada utilizando a Thread da UI, irá congelar a aplicação durante todo este tempo.

 

Sendo assim, sempre que for fazer uma chamada ao Server, utilize um Thread a parte.


Concluindo

Neste artigo vimos apenas alguns poucos tópicos sobre a construção de um projeto utilizando Remoting, com o objetivo de evitar erros comuns já conhecidos. Existe é claro muito mais coisas a serem ditas, mas que as abordaremos a medida que formos construindo nosso pequeno chat.

 

Até a próxima

 

Gustavo Barros