October 2nd, 2007Programación con C# y .NET Remoting/Mono Remoting [7/7]
Indice general de los manuales
- Introducción, instalación en Ubuntu y creación de una calculadora.
- Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
- Canales: TCP y HTTP. Configuración en archivo XML.
- Uso de interfaces para separar el código entre cliente y servidor. « [Leyendo]
Introducción
El objetivo en esta sección será separar el código del servidor y el del cliente. Durante estas prácticas hemos estado usando la biblioteca Calculo.dll tanto en el cliente como en el servidor, pero sería interesante que el cliente no tuviera la implementación de ésta. Nos pueden surgir varias ideas de por qué separar el código:
- Aunque el cliente tuviera el código de la biblioteca, en realidad está usando la clase remota que le proporciona el servidor, y no la suya.
- Puede que no queramos enseñar la implementación de la biblioteca, solo proporcionar el servicio a un cliente.
- Al no tener la implementación de la biblioteca, el cliente ocupa menos.
Así pues, aprenderemos tres mecanismos para separar el código:
Y también aprenderemos, ligeramente, el por qué de todo esto y que hace internamente .NET/Mono Remoting.
El código fuente de este tutorial te lo puedes bajar desde aquí.
1. Utilizando una interfaz
Un mecanismo muy utilizado en la POO es el uso de interfaces. Éstas nos sirven para obtener la parte pública de una clase sin necesidad de su implementación y después adjudicarle una clase que herede o extienda de esta interfaz.
Conociendo nuestra calculadora, podríamos definir la siguiente interfaz:
- // Interfaz de una calculadora
- public interface ICalculadora
- {
- // Properties de “laBase”
- double LaBase
- {
- get;
- set;
- }
- // POST: Suma dos números
- double Suma(double izq, double dcha);
- // POST: Divide dos números
- // EXCEPTION: si el denominador es 0
- double Divide(double numerador, double denominador);
- // POST: Eleva nuestra base al número que le pasamos en
- // el argumento
- double Potencia(double valor);
- }
Y ahora implementaremos nuestra clase calculadora para que extienda de esta interfaz, la cual tiene prácticamente el mismo código de la que hemos estado usando en las anteriores secciones:
- // Clase Calculadora que puede ser compartida de forma remota
- public class Calculadora : MarshalByRefObject, ICalculo.ICalculadora
- {
- // Atributo para usarlo en el metodo Potencia
- private double laBase;
- // Properties de “laBase”
- public double LaBase
- {
- get { return this.laBase; }
- set { this.laBase = value; }
- }
- /* bueno, y el resto del código que hemos usado anteriormente */
- }
Debemos prestar atención a que también herede de MarshalByRefObject para que el servidor la pueda compartir remotamente.
El código del servidor será igual que en las demás prácticas, prestando atención a que nuestra calculadora implementada que comparte el servidor tiene el espacio de nombres cambiado a CalculoConImplementacion y deberemos cambiarlo tanto si la compartimos usando código como con el archivo en XML (la manera que hayas elegido).
El código del cliente cambiará ligeramente y aprenderemos una nueva función llamada Activator.GetObject ya que no podemos crear un objeto con una interfaz.
- #region Bibliotecas
- // Biblioteca estandar
- using System;
- // Bibliotecas remoting
- using System.Runtime.Remoting;
- // Calculadora
- using Icalculo;
- #endregion
- public class Cliente
- {
- public static void Main (string [] args)
- {
- // Recogiendo el archivo de configuración del cliente.
- RemotingConfiguration.Configure(“Cliente.exe.config”, false);
- // Recogemos del archivo de configuración en XML, el
- // campo “RemotingUrl” que contiene la dirección del servidor
- System.Configuration.AppSettingsReader configurationAppSettings =
- new System.Configuration.AppSettingsReader();
- String url = ((string)(configurationAppSettings.GetValue(
- “RemotingUrl”,
- typeof(string))));
- // Creando el objeto
- Console.WriteLine(“Creando la calculadora con el codigo de la interfaz.”);
- ICalculadora calc = (ICalculadora)Activator.GetObject(
- typeof(ICalculo.ICalculadora),
- url);
- // Usamos sus metodos
- Console.WriteLine(“Suma 2+2 = “ + calc.Suma(2, 2));
- // Paramos
- Console.ReadLine();
- }
- }
2. Utilizando una clase abstracta
En realidad, una interfaz es muy parecida a una clase abstracta, solo se diferencian en: Una interfaz solo puede declarar constantes y métodos públicos. La clase abstracta puede implementar constantes y atributos públicos y métodos implementados o no. Una clase, en C#, solo puede derivar de otra, pero puede extender de varias interfaces.
Centrándonos en la segunda característica, nos daremos cuenta que anteriormente la clase implementada derivaba de MarshalByRefObject y extendía de ICalculadora, pero ahora nuestra calculadora implementada tendrá que derivar de MarshalByRefObject y de una clase abstracta (que llamaremos AbstractCalculadora). Al no poder derivar de más de una clase, haremos que nuestra clase abstracta derive de MarshalByRefObject y la implementada de la clase abstracta AbstractCalculadora.
La clase abstracta quedará de la siguiente forma:
- // Clase abstracta para una calculadora
- public abstract class AbstractCalculadora : MarshalByRefObject
- {
- // Properties de “laBase”
- public abstract double LaBase
- {
- get;
- set;
- }
- // POST: Suma dos numeros
- public abstract double Suma(double izq, double dcha);
- // POST: Divide dos numeros
- // EXCEPTION: si el denominador es 0
- public abstract double Divide(double numerador, double denominador);
- // POST: Eleva nuestra base al numero que le pasamos en
- // el argumento
- public abstract double Potencia(double valor);
- }
Y la clase implementada quedará así:
- public class Calculadora : AbstractCalculo.AbstractCalculadora
- {
- // Atributo para usarlo en el metodo Potencia
- private double laBase;
- // Properties de “laBase”
- public override double LaBase
- {
- get { return this.laBase; }
- set { this.laBase = value; }
- }
- /* … bueno, y el resto del código que hemos usado anteriormente … */
- }
Tanto el cliente como el servidor quedarán de la misma forma que en la sección anterior (utilizando una interfaz), ya que, como tampoco podemos crear un objeto de una clase abstracta, deberemos llamar a la función Activator.GetObject como hicimos anteriormente.
3. Utilizando una clase “vacía”
Este método será muy parecido a usar una clase abstracta, la única diferencia será en dejar los métodos no implementados. C# nos ofrece una excepción, para añadirla a métodos que no queremos implementar por el momento, llamada NotImplementExcepcion (que currado el nombre). Y que utilizaremos en nuestra clase vacía de la siguiente forma:
- // Clase vacia para una calculadora
- public class CalculadoraVacia : MarshalByRefObject
- {
- // Properties de “laBase”
- public virtual double LaBase
- {
- get { throw new NotImplementedException(); }
- set { throw new NotImplementedException(); }
- }
- // POST: Suma dos numeros
- public virtual double Suma(double izq, double dcha)
- {
- throw new NotImplementedException();
- }
- // POST: Divide dos numeros
- // EXCEPTION: si el denominador es 0
- public virtual double Divide(double numerador, double denominador)
- {
- throw new NotImplementedException();
- }
- // POST: Eleva nuestra base al numero que le pasamos en
- // el argumento
- public virtual double Potencia(double valor)
- {
- throw new NotImplementedException();
- }
- }
Como podemos observar, tenemos el mismo problema de herencia múltiple del caso de la clase abstracta, así que esta clase derivará de MarshalByRefObject y la siguiente derivará de CalculadoraVacia:
- public class Calculadora : CalculoVacio.CalculadoraVacia
- {
- // Atributo para usarlo en el metodo Potencia
- private double laBase;
- // Properties de “laBase”
- public override double LaBase
- {
- get { return this.laBase; }
- set { this.laBase = value; }
- }
- /* … bueno, y el resto del código que hemos usado anteriormente … */
- }
Ya tenemos nuestra clase para el cliente y para el servidor, pero, a diferencia de los otros mecanismos, en este sí que podemos instanciar un objeto de la clase vacía en el cliente (aunque no sirva de mucho, ya que usará la clase remota):
- public class Cliente
- {
- public static void Main (string [] args)
- {
- // Recogiendo el archivo de configuración del cliente.
- RemotingConfiguration.Configure(“Cliente.exe.config”, false);
- // Creando el objeto
- Console.WriteLine(“Creando la calculadora con el codigo de una clase abstracta.”);
- CalculadoraVacia calc = new CalculadoraVacia();
- // Usamos sus metodos
- Console.WriteLine(“Suma 2+2 = “ + calc.Suma(2, 2));
- // Paramos
- Console.ReadLine();
- }
- }
4. ¿Qué se hace internamente?
4.1. Clientes que se comunican con objetos remotos vía proxies.
Para no perdernos mientras leemos este apartado, tendremos el siguiente dibujo que nos ayudará en la comprensión:

Como hemos dicho varias veces durante esta sección y las anteriores, el cliente llama remotamente al objeto remoto. En realidad lo que hace el cliente es tener un representante (proxy) del objeto, conteniendo los métodos y atributos públicos que puede utilizar. Esta representación es el llamado TransparentProxy. Cuando, desde el cliente, se llama a un método del objeto, el TransparentProxy convierte la llamada en un objeto-mensaje que la describe, utilizando la clase Imessage, y la envía al llamado RealProxy. El RealProxy se encarga de reenviar el mensaje remotamente y así, el objeto remoto, utilizará esta información para ejecutar la acción.
Los objetos TransparentProxy y RealProxy se crean al instanciar un objeto que hace referencia a un objeto remoto y, tanto los mensajes como el RealProxy, se pueden ampliar y personalizar.
Esto ha sido un súper resumen sobre este funcionamiento, ideal para tener una visión general. Pero podemos consultar información más ampliada en la página del MSDN en la que explican esto con más detalle: http://msdn.microsoft.com/library/spa/default.asp?url=/library/SPA/dntaloc/html/hawkremoting.asp
4.2. Objetos sensibles al contexto
La siguiente pregunta a hacerse es sobre el operador new: ¿por qué a veces crea clases locales y otras remotas? En C# no es posible redefinir el operador new, pero la aplicación si puede obtener la información de como usar este operador dependiendo de la configuración que le proporcionemos. Esto es lo que diferencia a los objetos habituales (llamados ágiles) de los objetos sensibles al contexto (ContextBound). Los objetos sensibles al contexto son capaces de, utilizando clases intermedias, captar las llamadas que se producen a este objeto. Cuando decimos que un objeto es MarshalByRefObject le estamos diciendo que es un objeto sensible al contexto y tanto por el lado del cliente, como del servidor se crearán clases intermedias que administrarán la clase remota según en el ámbito en el que se encuentren:
- En el cliente: se crean los proxies y con estos se crean los mensajes, etc.
- En el servidor: se captan los mensajes y se llama adecuadamente al objeto compartido, etc.
Indice general de los manuales
- Introducción, instalación en Ubuntu y creación de una calculadora.
- Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
- Canales: TCP y HTTP. Configuración en archivo XML.
- Uso de interfaces para separar el código entre cliente y servidor. « [Leyendo]
