November 13th, 2007Remoting, Web Services y PHP

Introducción

Los servicios web es software accesible desde internet por otras aplicaciones:

  • Son accesibles desde internet ya que son identificadas mediante URI’s.
  • Pueden interacturar con otras aplicaciones ya que son descritas, definidas y descubiertas mediante artefactos XML, así pues, los mensajes entre aplicaciones están descritos mediante éste.

En la actualidad se están desarrollando muchos de estos servicios, los cuales podemos usar en nuestras propias aplicaciones. Durante este manual, veremos como crear servicios web mediante remoting, y como acceder mediante PHP a estos servicios usando las librerías nuSoap.

IMPORTANTE: Durante este tutorial, se hará uso de los conocimientos adquiridos en los tutoriales de Remoting. Además, usaremos un servidor de la Calculadora de los ejemplos anteriores… coger uno de los que el servidor recoge la configuración desde un archivo exterior. Si no tenéis tiempo o no queréis mirar los manuales, aquí está el proyecto que sigue este manual (con el servidor, la calculadora y el cliente en PHP).

Descargar el código fuente de este tutorial.

1. Creando servicios web con NET/Mono Remoting

1.1. SOAP (Simple Object Access Protocol)

Mediante los manuales de Remoting hemos creado aplicaciones distribuidas cliente-servidor. Uno de los canales que los que podíamos hacer uso era el canal HTTP el cual usa SOAP (Simple Object Access Protocol, protocolo basado en XML para hacer llamadas remotas a objetos) para la comunicación entre ellos. Además, como recordaremos, el cliente necesitaba una dirección URI para acceder al servicio remoto.

WebServices

En el dibujo, podemos comprobar como tanto el servidor como el cliente podrían formar parte de tecnologías diferentes. Lo único que habría que ajustar es el parser SOAP de cada uno de ellos.

En realidad, anteriormente ya habíamos creado servicios web con Remoting. De hecho, el servidor servía nuestra calculadora como un servicio web, y el cliente hacía uso de éste. Para comprobar esto, usaremos un sniffer y capturaremos los mensajes que se generan cuando el cliente llama a la operación Suma (el cliente en color azul, el servidor en color rojo):

POST /Calculadora.remota HTTP/1.1
User-Agent: Mono Remoting Client (Mono CLR 2.0.50727.42)
SOAPAction: “http://schemas.microsoft.com/clr/nsassem/Calculo.Calculadora/Calculo#Suma”
Content-Type: text/xml; charset=”utf-8″
Content-Length: 611
Expect: 100-continue
Connection: close
Host: localhost:8080

HTTP/1.0 100 Continue
Server: Mono Remoting, Mono CLR 2.0.50727.42
Content-Length: 0
X-Powered-By: Mono
Connection: close


<SOAP-ENV:Envelope xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
xmlns:SOAP-ENC=”http://schemas.xmlsoap.org/soap/encoding/”
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”
xmlns:clr=”http://schemas.microsoft.com/clr/”
SOAP-ENV:encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”>
<SOAP-ENV:Body>
<i2:Suma id=”ref-1″
xmlns:i2=”http://schemas.microsoft.com/clr/nsassem/Calculo.Calculadora/Calculo”>
<izq xsi:type=”xsd:double”>2</izq>
<dcha xsi:type=”xsd:double”>2</dcha>

</i2:Suma>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

HTTP/1.0 200 OK
Content-Type: text/xml; charset=”utf-8″
Server: Mono Remoting, Mono CLR 2.0.50727.42
Content-Length: 590
X-Powered-By: Mono
Connection: close
<SOAP-ENV:Envelope xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
xmlns:SOAP-ENC=”http://schemas.xmlsoap.org/soap/encoding/”
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”
xmlns:clr=”http://schemas.microsoft.com/clr/”
SOAP-ENV:encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”>
<SOAP-ENV:Body>
<i2:SumaResponse id=”ref-1″
xmlns:i2=”http://schemas.microsoft.com/clr/nsassem/Calculo.Calculadora/Calculo”>
<return xsi:type=”xsd:double”>4</return>
</i2:SumaResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Como podemos ver, el paso de mensajes está formado en SOAP. Las capturas las realicé con Wireshark (Ethereal).

1.2. WSDL (Web service description language)

Como el nombre de WSDL indica, es un lenguaje que describe los servicios web disponibles. Cuando necesitamos acceder a un servicio web, es posible que no sepamos el nombre de los métodos, los argumentos que debemos pasar a cada uno, los tipos de los que disponemos… Toda esta documentación puede ser descrita mediante WSDL y así acceder de manera correcta a los web services.

Podemos acceder al WSDL de nuestra calculadora, de los ejemplos de remoting, arrancando el servidor escribiendo la dirección seguida de “?wsdl” en un navegador, como por ejemplo:

http://localhost:8080/Calculadora.remota?wsdl

Y en nuestro navegador nos aparecerá la descripción de nuestra calculadora en WSDL, que cualquier cliente podría usar para documentarse:

<definitions name=“Calculadora” targetNamespace=“http://schemas.microsoft.com/clr/nsassem/Calculo/Calculo”>
<types/>
<message name=“Calculadora.get_LaBaseInput”/>


<message name=“Calculadora.get_LaBaseOutput”>
<part name=“return” type=“xsd:double”/>
</message>


<message name=“Calculadora.set_LaBaseInput”>
<part name=“value” type=“xsd:double”/>
</message>


<message name=“Calculadora.set_LaBaseOutput”/>


<message name=“Calculadora.SumaInput”>
<part name=“izq” type=“xsd:double”/>
<part name=“dcha” type=“xsd:double”/>
</message>


<message name=“Calculadora.SumaOutput”>
<part name=“return” type=“xsd:double”/>
</message>

Hemos cogido solo una parte del WSDL para describirlo ligeramente. En el ejemplo, aparecen las operaciones get y set para el atributo laBase y el método Suma. Como vemos, cada operación tiene dos partes: Input y Output que describen la entrada (los argumentos de entrada) y la salida (el resultado de la operación). La única nota a remarcar es no confundirnos con los nombres de los métodos: por ejemplo, la operación para Sumar es Suma y no SumaInput.

2. El servidor

Para el manual usaremos nuestra calculadora y el servidor recogerá la siguiente configuración:

<configuration>
<system.runtime.remoting>
<application>
 
<channels>
<channel ref=“http” port=“8080″ />
</channels>
 
<service>
<wellknown mode=“SingleCall”
type=“Calculo.Calculadora, Calculo”
objectUri=“Calculadora.soap” />
</service>
 
</application>
</system.runtime.remoting>
</configuration>

 

A sido tan fácil crear un servidor que comparta servicios web, que no hemos tocado nada nuevo.

3. Cliente en PHP

Ya que el servidor únicamente necesita mensajes en SOAP, podemos usar cualquier aplicación que pueda mandarle este tipo de mensajes y así poder recibir la respuesta en SOAP. Como ejemplo, en este tutorial, aprenderemos a usar PHP con una librería llamada nuSoap, que nos ayuda en las peticiones al servidor. Descargaremos las librerías de nuSoap y crearemos la siguiente estructura de archivos (para que todos lo hagamos igual y funcione):

  • www (carpeta que usa Apache para las páginas web)
    • lib (la carpeta lib de nuSoap)
      • Cliente.php

NOTA: para que el cliente en PHP funcione, tendremos que haber instalado un servidor Apache para que funcione con PHP. Hay un manual de como hacerlo (bajo [X]Ubuntu) aquí.

En el archivo Cliente.php escribiremos lo siguiente:

<html>
  <head>
    <title>Web services con C# y PHP</title>
  </head>
  <body>
 

<?
// Ejemplo de nuSoap del archivo /samples/client1.php modificado para
// nuestra calculadora. Tambien esta traducido al castellano y anyadido algunos
// comentarios de mas :P
// Librerias de nuSoap
require_once(‘lib/nusoap.php’);
 
// Configuracion inicial
//$servicioRemoto = ‘http://localhost:8080/Calculadora.soap’;
$servicioRemotoWSDL = ‘http://localhost:8080/Calculadora.soap?wsdl’;
$proxyhost = isset($_POST['proxyhost']) ? $_POST['proxyhost'] : ;
$proxyport = isset($_POST['proxyport']) ? $_POST['proxyport'] : ;
$proxyusername = isset($_POST['proxyusername']) ? $_POST['proxyusername'] : ;
$proxypassword = isset($_POST['proxypassword']) ? $_POST['proxypassword'] : ;
// $cliente = new nusoap_client(
// $servicioRemoto, false,
// $proxyhost, $proxyport, $proxyusername, $proxypassword);
 
// Con WSDL
$cliente = new nusoap_client(
$servicioRemotoWSDL, ‘wsdl’,
$proxyhost, $proxyport, $proxyusername, $proxypassword);
 
//Comprobamos errores
$err = $cliente->getError();
if ($err)
{
  echo ‘<h2>Error en el constructor</h2><pre>’ . $err . ‘</pre>’;
  echo ‘<h2>Debug</h2><pre>’.
  htmlspecialchars($client->getDebug(), ENT_QUOTES) . ‘</pre>’;
  exit();
}
 
// Llamaremos al metodo Sumar, el cual tiene los parametros a y b
// Creamos un array que representa los parametros
$parametros = array(
‘izq’ => ’8′,
‘dcha’ => ’9′
);
 
// Hacemos una llamada al metodo y recogemos el resultado
$metodo = ‘Suma’;
$resultado = $cliente->call($metodo, $parametros,,);
 
// Comprobamos los errores y mostramos el resultado
if ($cliente->fault)
{
  echo ‘<h2>Error: La peticion contiene un contenido SOAP invalido</h2>’.
  ‘<pre>’; print_r($resultado); echo ‘</pre>’;
}
else
{
  $err = $cliente->getError();
  if ($err)
  {
    echo ‘<h2>Error</h2><pre>’ . $err . ‘</pre>’;
  }
  else
  {
    echo ‘<h2>Resultado</h2><pre>’; print_r($resultado); echo ‘</pre>’;
  }
}
?>
 

</body>
</html>

Como vemos, el cliente en PHP necesita varias cosas:

  • Inicializar el cliente, pudiendo especificar un proxy web intermedio. Se puede usar tanto la dirección del WSDL como la del servicio. En el código he puesto las dos, pero he comentado una (podéis cambiarlo para ver que el resultado es el mismo).
  • Construir un array con los argumentos que necesita el método.
  • Hacer una llamada al método remoto que necesitamos y recoger la respuesta.
  • Mostrar el resultado o el posible error (por no poder establecer la conexión, por un error en los argumentos, porque el método no exista, …)

Ahora iniciaremos el servidor y accederemos mediante nuestro navegador a nuestra página en PHP para ver los resultados. Si hemos añadido trazas en nuestra calculadora, podremos ver que se ejecutan en la consola del servidor cuando el cliente en PHP hace las consultas:

./Servidor.exe
Constructor
Metodo: Suma

Salida en el navegador web.

Array
{
    [return] => 17
    [!id] => ref-1
}

Con esta introducción, podemos descubrir por nosotros mismos la funcionalidad de nuSoap, que seguro que se os han llenado la cabeza de ideas… aunque todavía nos quedan algunas cosas que tratar:

  • ¿Funcionan igual las formas de compartir singleCall, singleton y CAO con PHP y nuSoap?
  • Operaciones que tienen argumentos de tipo out y ref.
  • ¿Como recoger otro tipo de datos que no sean los nativos?
  • Servicios web que nos ofrecen otras compañías (como Google).

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. 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.

Descargar el código fuente de este tutorial.

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:

InfraestructuraRemoting

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

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. Uso de interfaces para separar el código entre cliente y servidor.  «  [Leyendo]

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.  «  [Leyendo]
  4. Uso de interfaces para separar el código entre cliente y servidor.

Introducción

Ya que tenemos una buena base, e incluso ya podríamos hacer nuestros programillas, ahora estudiaremos algunas cosas que nos pueden ser interesantes y de gran ayuda. Aprenderemos dos cosas:

  • Compartiremos los objetos mediante el canal TCP, en lugar de HTTP como hemos ido haciendo anteriormente.
  • Guardaremos la configuración de como comparte el servidor los objetos en un archivo, ya que, de esta forma, no tendremos que compilar el servidor o el cliente) cada vez que decidamos si queremos un SingleCall, un Singleton o un CAO; y también la dirección, el puerto, etc.

1. Usando el canal TCP

Usar el canal TCP es tan fácil como usar HTTP. De hecho, si echamos un vistazo a códigos anteriores, podríamos intuir cómo usarlo.

Cogeremos el código del servidor de alguno de los ejemplos anteriores para ver el cambio:

// Archivo: Servidor.cs
// Descripcion: programa servidor que comparte
// la clase Calculadora remotamente.
 
// Biblioteca estandar y remoting
using System;
using System.Runtime.Remoting;
 
// Clases de Remoting
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
 
namespace Servidor
{
  public class Servidor
  {
    // arg[0] representa el puerto del servidor
    public static void Main(string[] args)
    {
      // Creamos un nuevo objeto para el canal HTTP
      HttpChannel chnl = new HttpChannel(int.Parse(args[0]));
 
      // Le decimos a la máquina que nos reserve ese canal
      ChannelServices.RegisterChannel(chnl);
 
      // Opciones para la comparticion de la clase
      RemotingConfiguration.RegisterWellKnownServiceType(
        typeof(Calculo.Calculadora),
        “Calculadora.remota”,
        WellKnownObjectMode.SingleCall);
 
      // Mensajes de espera en la pantalla
      Console.WriteLine(“Atendiendo las peticiones…”);
      Console.WriteLine(“Pulse Enter para salir…”);
      Console.ReadLine();
    }
  }
}

Como vemos en el ejemplo, estamos usando siempre HTTP. El código para usar el canal TCP consistirá en cambiar las apariciones de “Http” por “Tcp”, así de simple. Podemos verlo en el siguiente ejemplo del código anterior modificado:

// Archivo: Servidor.cs
// Descripcion: programa servidor que comparte
// la clase Calculadora remotamente.
 
// Biblioteca estandar y remoting
using System;
using System.Runtime.Remoting;
 
// Clases de Remoting
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
 
namespace Servidor
{
  public class Servidor
  {
    // arg[0] representa el puerto del servidor
    public static void Main(string[] args)
    {
      // Creamos un nuevo objeto para el canal TCP (antes era HTTP)
      TcpChannel chnl = new TcpChannel(int.Parse(args[0]));
 
      // Le decimos a la máquina que nos reserve ese canal
      ChannelServices.RegisterChannel(chnl);
 
      // Opciones para la comparticion de la clase
      RemotingConfiguration.RegisterWellKnownServiceType(
        typeof(Calculo.Calculadora),
        “Calculadora.remota”,
        WellKnownObjectMode.SingleCall);
 
      // Mensajes de espera en la pantalla
      Console.WriteLine(“Atendiendo las peticiones…”);
      Console.WriteLine(“Pulse Enter para salir…”);
      Console.ReadLine();
    }
  }
}

1.1. ¿En qué se diferencian los dos canales?

Canal HTTP: Los mensajes que recibimos y envía el objeto remoto usarán el protocolo SOAP, es decir, estos datos se convertirán en formato SOAP (Simple Object Access Protocol) para comunicarse entre sí. El protocolo SOAP es una representación de la comunicación entre dos objetos en XML. Así pues, cuando los programas se comunicaban entre sí, lo hacían enviándose mensajes en XML que podían interpretar.

Canal TCP: es más simple, los mensajes que se usan para la comunicación se envían en binario.

1.2. ¿Cuál usar?

Mirando esta tabla, podremos escoger nuestro canal según nuestra necesidad:

Causa HTTP TCP
Rapidez Es más lento, ya que tiene que enviar mucha información en XML. Es más rápido, ya que usa binario y manda la información justamente necesaria.
Seguridad Un cortafuegos podría dejar pasar información en XML. Un cortafuegos podría denegar la entrada a información en binario.
Herramientas Se puede usar WSDL para describir los servicios web que nos ofrece el servidor (se verá más adelante).  

2. Archivos de configuración

En los ejemplos anteriores, hemos visto como la configuración del servidor y del cliente la escribíamos dentro del programa (el modo, la dirección, el puerto). Ahora veremos una forma de tener nuestra configuración en un fichero separado.

Los archivos de configuración están escritos en XML. Ahora daremos los ejemplos de configuración del servidor y del cliente que podrían sernos útiles en los ejemplos anteriores.

2.1. Archivo para el servidor

El archivo que deberá recoger el servidor debe tener una estructura similar a esta:

Archivo: Servidor.exe.config

<configuration>
  <system.runtime.remoting>
   <application>
 
     <!– Clase que vamos a compartir –>
     <service>
       <wellknown
          mode=“Singleton”
          type=“Calculo.Calculadora, Calculo”
          objectUri=“Calculadora.remota” />
     </service>
 
     <!– Canales que vamos a utilizar –>
     <channels>
       <channel ref=“http” port=“1234″ />
       <!– tambien podia haber sido tcp –>
     </channels>
 
   </application>
 </system.runtime.remoting>
</configuration>

Como vemos, es muy simple e intuitivo. Los elementos que más nos interesan son wellknown y channel. Veamos los valores posibles en los atributos del elemento wellknown:

  • mode: <Singleton> | <SingleCall> Con mode le indicamos la forma de compartir nuestra clase.
  • type: <[namespace].[clase], [fichero]> Con type le indicamos es espacio de nombre, la clase que queremos compartir y en que archivo está.
  • objectUri: <nombreQueLeDamosAlServicio> Con objectUri le ponemos un nombre al servicio.

Nota: el uso de los corchetes es para indicar que en ese lugar la escritura es libre, es decir, no tiene valores predeterminados.

Los atributos de los elementos channel podrán tener los siguientes valores:

  • ref: <http> | <tcp> Con ref elegimos el canal.
  • port: <numeroDelPuerto> Con port elegimos el puerto.

2.2. Archivo para el cliente

El archivo del cliente es aún más fácil:

Archivo: Cliente.exe.config

<configuration>
 <system.runtime.remoting>
   <application>
     <client>
       <wellknown
         type=“Calculo.Calculadora, Calculo”
         url=“http://localhost:1234/Calculadora.remota” />
     </client>
   </application>
 </system.runtime.remoting>
</configuration>

Como vemos, en el único elemento que nos debemos fijar es en wellknown, en el que sus atributos pueden coger los siguientes valores:

  • type: <[namespace].[clase], [fichero]> Al igual que antes, la clase que queremos compartir.
  • url: (<http> | <tcp>)://<host>:<puerto>/<nombreQueLeHanDadoAlServicio> La dirección dónde se está compartiendo la clase.

2.3. Los códigos del servidor y del cliente.

El código del servidor se nos reducirá considerablemente:

using System;
using System.Runtime.Remoting;
 
using Calculo;
 
public class Servidor
{
 public static void Main(string [] args)
 {
   // Recogiendo el archivo de configuración del servidor.
   RemotingConfiguration.Configure(“Servidor.exe.config”);
 
   Console.WriteLine(“Atendiendo a las peticiones.”);
   Console.WriteLine(“Pulse ENTER para salir.”);
 
   Console.ReadLine();
 }
}

El código del cliente es igual de fácil que el del servidor (de hecho usan la misma llamada).

using System;
using System.Runtime.Remoting;
using Calculo;
 
public class Cliente
{
  public static void Main (string [] args)
  {
   // Recogiendo el archivo de configuración del cliente.
   RemotingConfiguration.Configure(“Cliente.exe.config”);
 
   // Creando el objeto
   Console.WriteLine(“Creando la calculadora”);
   Calculadora calc = new Calculadora();
 
   // Hacer uso de la calculadora
   /* ….. */
  }
}

Ahora os estaréis preguntando el por qué de no haber dicho esto antes y tener que haber estado modificando el código del cliente y del servidor todo el rato… bueno, hay que aprender todos los métodos.

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.  «  [Leyendo]
  4. Uso de interfaces para separar el código entre cliente y servidor.

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.  «  [Leyendo]
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. Uso de interfaces para separar el código entre cliente y servidor.
Descargar el código fuente de este tutorial.

CAO

La última de las formas de compartir los objetos desde el servidor es CAO: Client Activated Object. De esta forma, cada cliente que acceda al servidor, podrá crear un objeto en el y usarlo de forma independiente. En nuestro ejemplo, cada cliente tendrá su propia calculadora.

cao

Como vemos, cada cliente tiene su propia calculadora, y cada calculadora guarda su estado.

Aunque el nuevo código del servidor es muy parecido a los anteriores, pongo el cuerpo de Main entero:

// Archivo: Servidor.cs
// Descripcion: programa servidor que comparte
// la clase Calculadora remotamente.
 
// Biblioteca estandar y remoting
using System;
using System.Runtime.Remoting;
 
// Bibliotecas para usar un canal HTTP
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Calculo;
 
public class Servidor
{
  // arg[0] representa el puerto del servidor
  public static void Main (string[] args)
  {
    // Creamos un nuevo objeto para el canal HTTP
    HttpChannel chnl = new HttpChannel(int.Parse(args[0]));
 
    // Le decimos a la máquina que nos reserve ese canal
    ChannelServices.RegisterChannel(chnl);
    

    // Opciones para la comparticion de la clase
    RemotingConfiguration.ApplicationName =
       “ServidorDeCalculadoras”;
    RemotingConfiguration.RegisterActivatedServiceType(
      typeof(Calculo.Calculadora));
 
    // Mensajes de espera en la pantalla
    Console.WriteLine(“Atendiendo las peticiones…”);
    Console.WriteLine(“Pulse Enter para salir…”);
    Console.ReadLine();
  }
}

La configuración del servidor la hemos dividido en dos partes, le hemos adjudicado un nombre que será el del servidor, no el de una calculadora; y después hemos compartido la calculadora.

Vamos a modificar el cliente por completo, para que no tengamos que hacer dos clientes diferentes para ver el funcionamiento de esta forma de compartir.

// Archivo: Cliente.cs
// Version: 2.0
// Descripcion: cliente que usara la clase remota Calculadora
 
// Biblioteca estandar y remoting
using System;
using System.Runtime.Remoting;
 
// Bibliotecas para usar un canal HTTP
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Calculo;
 
public class Cliente
{
  // arg[0] representa el host del servidor
  // arg[1] representa el puerto del servidor
  // arg[2] representa el numero que guardamos en LaBase
  public static void Main (string[] args)
  {
    // Creamos un nuevo objeto para el canal HTTP
    HttpChannel chnl = new HttpChannel();
 
    // Le decimos a la máquina que nos reserve ese canal
    ChannelServices.RegisterChannel(chnl);
 
    // Opciones para la utilizacion remota de la clase
    RemotingConfiguration.RegisterActivatedClientType(
      typeof(Calculo.Calculadora),
      “http://” + args[0] + “:” + args[1]);
 
    // Creamos un objeto
    Calculadora calc = new Calculadora();
 
    // Practicamos
    calc.LaBase = int.Parse(args[2]);
    Console.WriteLine(“Cambiado a: “ + cacl.LaBase);
 
    Console.WriteLine(“Pulse Enter para ver el estado…”);
    Console.ReadLine();
 
    Console.WriteLine(“Hemos cambiado la base a “ + calc.LaBase);
  }
}

Podemos ver varios cambios en este nuevo cliente:

  • A diferencia de los anteriores, solo necesitamos saber el host y el puerto del servidor, no nos hace falta saber el nombre con el que el servidor se ha autodenominado.
  • Hemos añadido un nuevo argumento al ejecutar el programa, que será el número que queremos adjudicar a LaBase, y así poder hacer varias pruebas para cambiar el estado desde el cliente sin tener que crear dos.
  • Para comprobar que cada cliente tiene su propia calculadora, hemos interrumpido el programa a la mitad (pulsando Enter) para cambiar el estado desde dos clientes diferentes que se ejecutan a la misma vez y comprobar después que cada uno tiene una calculadora con su propio estado.

Ahora solo nos queda compilar los archivos:

mcs -target:library Calculo.cs
mcs -r:Calculo.dll -r:System.Runtime.Remoting.dll Servidor.cs
mcs -r:Calculo.dll -r:System.Runtime.Remoting.dll Cliente.cs

Ejecutaremos los archivos en diferentes consolas:

Ejecutar el servidor:

./Servidor.exe 9999

El cliente 1:

./Cliente.exe localhost 9999 5

Y su resultado:

Cambiado a: 5
Pulse Enter para ver el estado…

Y el cliente 2:

./Cliente2.exe localhost 9999 8

Y su resultado:

Cambiado a: 8
Pulse Enter para ver el estado…

Una vez que tenemos los dos clientes cargados, pulsamos Enter en cada uno de ellos y vemos que conservan su estado:

El Cliente 1:

Hemos cambiado la base a 5

El Cliente 2:

Hemos cambiado la base a 8

En el servidor solo veremos como cada cliente crea un nuevo objeto.

Atenciando las peticiones…
Pulse Enter para salir…
Contructor.
Constructor.

Ya que cada cliente puede crearse su propia calculadora, podríamos replantearnos dejar que cada objeto “muera” pasado un tiempo (el tiempo de vida que vimos en el capítulo anterior) o controlar todas las instancias que se hacen desde los servidores con un atributo estático que vaya aumentando por cada instancia. Deberíamos pensar esto ya que puede que alguien malintencionado haga miles de clientes que llamen a nuestro servidor, creando miles de instancias y bloqueando el servidor.

Aunque ya está escrita la parte más importante del manual, en los siguientes capítulos veremos trucos y formas de usar la teoría que hemos dado hasta ahora.

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.  «  [Leyendo]
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. Uso de interfaces para separar el código entre cliente y servidor.

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.  «  [Leyendo]
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. Uso de interfaces para separar el código entre cliente y servidor.

Singleton

Ésta es otra de las formas que vimos en las que el servidor puede compartir un objeto. De esta forma el servidor creará un objeto único para todos los clientes y podrá guardar el estado. Así, los clientes podrán compartir información de ese objeto entre ellos.

singleton

Como podemos ver, los clientes comparten la misma calculadora. El cliente1 cambia uno de los atributos del objeto (cambia el estado) y el cliente2 se percata de ello.

El código del servidor es igual que el del tutorial anterior, aunque solo habrá que decirle que comparta la calculadora en modo Singleton. Modificaremos la siguiente línea:

// Archivo: Servidor.cs
// Descripcion: programa servidor que comparte
// la clase Calculadora remotamente.
 
// Biblioteca estandar y remoting
using System;
using System.Runtime.Remoting;
 
// Bibliotecas para usar un canal HTTP
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Calculo;
 
public class Servidor
{
  // arg[0] representa el puerto del servidor
  public static void Main (string[] args)
  {
    // Creamos un nuevo objeto para el canal HTTP
    HttpChannel chnl = new HttpChannel(int.Parse(args[0]));
 
    // Le decimos a la máquina que nos reserve ese canal
    ChannelServices.RegisterChannel(chnl);
 
    // Opciones para la comparticion de la clase
    RemotingConfiguration.RegisterWellKnownServiceType(
      typeof(Calculo.Calculadora),
      “Calculadora.remota”,
      WellKnownObjectMode.Singleton);
 
    // Mensajes de espera en la pantalla
    Console.WriteLine(“Atendiendo las peticiones…”);
    Console.WriteLine(“Pulse Enter para salir…”);
    Console.ReadLine();
  }
}

Y el cliente lo dejaremos igual, ya que en el cliente no especificábamos como era la forma en la que compartía el servidor. Aunque vamos a crear otro nuevo servidor para ver como se comparte el objeto entre los dos. Solo escribiré la parte en que se hace uso de la calculadora, el resto del código (crear canal HTTP, reservar el canal, forma en la que se comparte) será igual:

// Archivo: Cliente2.cs
// Descripcion: cliente que usara la clase remota Calculadora
 
// Biblioteca estandar y remoting
using System;
using System.Runtime.Remoting;
 
// Bibliotecas para usar un canal HTTP
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Calculo;
 
public class Cliente2
{
  // arg[0] representa el host del servidor
  // arg[1] representa el puerto del servidor
  public static void Main (string[] args)
  {
    // Creamos un nuevo objeto para el canal HTTP
    HttpChannel chnl = new HttpChannel();
 
    // Le decimos a la máquina que nos reserve ese canal
    ChannelServices.RegisterChannel(chnl);
 
    // Opciones para la utilizacion remota de la clase
    RemotingConfiguration.RegisterWellKnownClientType(
      typeof(Calculo.Calculadora),
      “http://” + args[0] + “:” + args[1] + “/Calculadora.remota”);
 
    // Ahora, usamos la calculadora como si la clase
    // la tuvieramos en nuestro ordenador
    Calculadora calc = new Calculadora();
    Console.WriteLine(“LaBase la hemos cambiado desde el primer cliente…”);
    Console.WriteLine(“Asi que vemos que la base desde el segundo es: “ + calc.LaBase);
   }
}

El código del primer cliente lo podemos conseguir desde el tutorial anterior: Programación con C# y .NET Remoting/Mono Remoting [3/7]

Ahora solo nos queda compilar los archivos:

mcs -target:library Calculo.cs
mcs -r:Calculo.dll -r:System.Runtime.Remoting.dll Servidor.cs
mcs -r:Calculo.dll -r:System.Runtime.Remoting.dll Cliente.cs
mcs -r:Calculo.dll -r:System.Runtime.Remoting.dll Cliente2.cs

Ejecutaremos los archivos en diferentes consolas y con el siguiente orden.

Ejecutar el servidor:

./Servidor.exe 9999

El cliente 1:

./Cliente.exe localhost 9999

Y el cliente 2:

./Cliente2.exe localhost 9999

Observamos el resultado del servidor:

Atendiendo las peticiones…
Pulse Enter para salir…
Constructor.
Metodo: Suma.
Metodo: Potencia.
Metodo: Divide.
Metodo: Divide.

Como podemos ver, solo se llama una vez al constructor, es decir, solo se crea un objeto el cual usarán todos los clientes.

La salida del cliente1:

Suma 2+2 = 4
Potencia 10^5 = 100000
Hemos cambiado la base a 5.0
Pero la base es: 5
Division 8/2 = 4
System.Exception: Division por 0

A diferencia que en SingleCall, una vez que hemos modificado el atributo laBase, este se puede consultar y obtener el último cambio, es decir, se guarda el estado.

Y la salida del cliente2:

LaBase la hemos cambiado desde el primer cliente
Asi que vemos que la base desde el segundo es: 5

Ya que desde el primer cliente cambiamos el atributo laBase, al hacer una consulta desde el segundo, vemos que recogemos el último estado de la calculadora aunque se haya cambiado desde otro cliente.

Vida del objeto

¿Que pasa si dejamos el servidor ejecutándose y pasan 5 minutos (aproximadamente) desde la última llamada de un cliente? Pues que el servidor destruye el objeto. .NET Remoting usa el concepto de vida del objeto para poder destruirlo pasados unos minutos. Prueba a ejecutar el servidor, luego el cliente 1, esperar 5 minutos y ejecutar el cliente 2:

./Servidor.exe 9999

./Cliente.exe localhost 9999

[5 minutos después...]

./Cliente2.exe localhost 9999
LaBase la hemos cambiado desde el primer cliente
Asi que vemos que la base desde el segundo es: 10

El seguimiento de este programa sería:

  1. El servidor crea un nuevo objeto calculadora con el atributo laBase = 10.
  2. El Cliente 1 cambia laBase = 5.
  3. Pasan más o menos cinco minutos, así que el servidor destruye la calculadora.
  4. El Cliente 2 necesita la calculadora, así que el servidor crea un nuevo objeto calculadora con el atributo laBase = 10.
  5. El Cliente 2 usa la calculadora, pero no obtiene el estado que había estado usando el Cliente 1.

Como podemos ver, al pasar un tiempo, el servidor destruye el objeto y el Cliente 2 debe usar un nuevo objeto Calculadora.

Esta forma de destruir los objetos si ningún cliente los utiliza durante un tiempo puede que nos sea de ayuda por temas de la seguridad o de liberación de memoria. Pero si no queremos que el objeto se autodestruya pasado un tiempo, habrá que añadir el siguiente método al código de la clase Calculadora:

public override object InitializeLifetimeService()
{
  // Un truco para que el objeto viva para
  // siempre.
  return null;
}

Sobre la vida de los objetos pasaremos a hablar en otro capítulo, aunque necesitaba remarcarlo por si alguien lo necesita.

Puede que haya ido un poco rápido, esto es así ya que si comprendisteis bien la forma SingleCall, esta forma no os supondría mucha más dificultad. En caso contrario, comentarme vuestras dudas.

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.  «  [Leyendo]
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. Uso de interfaces para separar el código entre cliente y servidor.

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.  «  [Leyendo]
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. Uso de interfaces para separar el código entre cliente y servidor.
Descargar el código fuente de este tutorial.

SingleCall

Como se explicó en el anterior capítulo, SingleCall es una de las posibles formas en la que el servidor comparte una clase. De esta forma, los clientes podrán hacer instancias a los métodos de una clase remota sin guardar el estado. Esto es así ya que una vez que al servidor se le hace la petición de invocar ese método, éste crea una instancia del objeto de esa clase, ejecuta el método, y vuelve a destruir el objeto creado.

singlecall

Como podemos ver en el ejemplo, los objetos se crean y se destruyen una vez que el cliente ha obtenido una respuesta del método que ha llamado.

Por fin, vamos a pasar a la acción y a crear un cliente y un servidor. El servidor usará la forma de compartir SingleCall, y sacaremos algunas conclusiones de la ejecución:

El código del servidor será:

// Archivo: Servidor.cs
// Descripcion: programa servidor que comparte
// la clase Calculadora remotamente.
 
// Biblioteca estandar y remoting
using System;
using System.Runtime.Remoting;
 
// Bibliotecas para usar un canal HTTP
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Calculo;
 
public class Servidor
{
  // arg[0] representa el puerto del servidor
  public static void Main (string[] args)
  {
    // Creamos un nuevo objeto para el canal HTTP
    HttpChannel chnl = new HttpChannel(int.Parse(args[0]));
 
    // Le decimos a la máquina que nos reserve ese canal
    ChannelServices.RegisterChannel(chnl);
 
    // Opciones para la comparticion de la clase
    RemotingConfiguration.RegisterWellKnownServiceType(
      typeof(Calculo.Calculadora),
      “Calculadora.remota”,
      WellKnownObjectMode.SingleCall);
 
    // Mensajes de espera en la pantalla
    Console.WriteLine(“Atendiendo las peticiones…”);
    Console.WriteLine(“Pulse Enter para salir…”);
    Console.ReadLine();
  }
}

Como podemos ver, el código del servidor es realmente simple. Creamos un canal por el que vamos a escuchar cuando nos hagan peticiones para usar la clase. La única parte que no hemos comentado en el código es las opciones de la compartición. Para compartir una clase, primero deberemos decir que clase queremos compartir, así pues, recogemos la clase Calculadora que tenemos en el espacio de nombres Calculo “typeof(Calculo.Calculadora)”. Después asignamos un nombre con el que los clientes deben referirse a nuestra clase (como veremos en el código del cliente), que hemos llamado “Calculadora.remota”, pero que puede ser otro cualquiera. Y la parte más importante es la forma en la que compartimos la clase: “SingleCall”. Una vez que el servidor está compartiendo la clase, “pararemos” el programa con la espera de una lectura por teclado.

El código del cliente es igual de simple y parecido:

// Archivo: Cliente.cs
// Descripcion: cliente que usara la clase remota Calculadora
 
// Biblioteca estandar y remoting
using System;
using System.Runtime.Remoting;
 
// Bibliotecas para usar un canal HTTP
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Calculo;
 
public class Cliente
{
  // arg[0] representa el host del servidor
  // arg[1] representa el puerto del servidor
  public static void Main (string[] args)
  {
    // Creamos un nuevo objeto para el canal HTTP
    HttpChannel chnl = new HttpChannel();
 
    // Le decimos a la máquina que nos reserve ese canal
    ChannelServices.RegisterChannel(chnl);
 
    // Opciones para la utilizacion remota de la clase
    RemotingConfiguration.RegisterWellKnownClientType(
      typeof(Calculo.Calculadora),
      “http://” + args[0] + “:” + args[1] + “/Calculadora.remota”);
 
    // Ahora, usamos la calculadora como si la clase
    // la tuvieramos en nuestro ordenador
    Calculadora calc = new Calculadora();
    Console.WriteLine(“Suma 2+2 = “ + calc.Suma(2.0, 2.0));
    Console.WriteLine(“Potencia 10^5 = “ + calc.Potencia(5.0));
 
    // Vamos a comprobar como no se guarda el estado
    calc.LaBase = 5.0;
    Console.WriteLine(“Hemos cambiado la base a 5.0″);
    Console.WriteLine(“Pero la base es: “ + calc.LaBase);
 
    try
    {
      Console.WriteLine(“Division 8/2 = “ + calc.Divide(8.0,2.0));
      Console.WriteLine(“Division 5/0 = “ + calc.Divide(5.0,0.0));
    }
    catch (Exception e)
    {
      Console.WriteLine(e);
    }
  }
}

El cliente, al igual que el servidor, necesita registrar el canal por el cual va a hacer las peticiones. Después necesitará recoger el tipo de la clase que se comparte remotamente, y también deberá indicar la dirección en la que se proporciona el servicio. Como vemos, al cliente no hay que especificarle que el servidor está compartiendo su clase de forma SingleCall.

Ahora compilaremos los tres archivos para hacer las pruebas:

mcs -target:library Calculo.cs
mcs -r:Calculo.dll -r:System.Runtime.Remoting.dll Servidor.cs
mcs -r:Calculo.dll -r:System.Runtime.Remoting.dll Cliente.cs

Ejecutaremos en una consola el servidor:

./Servidor.exe 9999

Y en otra consola el cliente:

./Cliente.exe localhost 9999

Observamos el resultado del servidor:

Atendiendo las peticiones…
Pulse Enter para salir…
Constructor.
Metodo: Suma.
Constructor.
Metodo: Potencia.
Constructor.
Constructor.
Constructor.
Metodo: Divide.
Constructor.
Metodo: Divide.

Al meter una traza en el constructor de la calculadora, podemos observar que cada vez que hacemos una llamada a un método, se llama al constructor, es decir, el servidor crea una nueva instancia de la calculadora.

Y ahora la salida del cliente:

Suma 2+2 = 4
Potencia 10^5 = 100000
Hemos cambiado la base a 5.0
Pero la base es: 10
Division 8/2 = 4
System.Exception: Division por 0

Podemos ver dos cosas interesantes:

  1. Como recordaremos, el atributo laBase se inicializa a 10 cada vez que se llama al constructor de la calculadora. Aunque en una instrucción hayamos cambiado LaBase a 5.0, al volverla a recoger en una instrucción posterior, será 10. Podemos observar que el objeto no ha guardado el estado, o mejor dicho, en realidad eran dos instancias de la clase calculadora.
  2. La segunda cosa interesante que podemos ver es que las excepciones las recoge el cliente, así el servidor será seguro y no se interrumpirá.

Todavía nos quedan muchas cosas por ver: elegir un canal TCP en lugar de HTTP, controlar la vida de los objetos, como compilar el cliente sin la clase implementada Calculadora, etc. Aunque lo dejamos aquí para que podáis ir probando y contándome vuestras dudas.

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.  «  [Leyendo]
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. Uso de interfaces para separar el código entre cliente y servidor.

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.  «  [Leyendo]
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. Uso de interfaces para separar el código entre cliente y servidor.

Introducción

El objetivo de esta lección es conocer la forma en la que un servidor puede compartir su calculadora y también modificar ligeramente nuestra calculadora para que sea una clase referenciada remotamente. No es objetivo aprenderse estos conceptos de memoria ni comprenderlos completamente, ya que en las siguientes lecciones haremos un programa por cada una de estas formas.

Descargar el código fuente de este tutorial.

1. Formas de compartir nuestra calculadora

Hay varias formas de compartir remotamente una clase desde un servidor.

SingleCall

Los clientes solo podrán hacer una invocación al método de una clase y obtener una respuesta, pero el objeto del servidor no podrá guardar el estado, ya que el servidor creará un nuevo objeto por petición del cliente, efectuará la operación solicitada y destruirá la instanciación de ese objeto.

singlecall

En el ejemplo ilustramos como después de la primera llamada (estrella 1), el servidor crea un objeto de la calculadora, hace la petición de la suma, después le devuelve el resultado al cliente y a continuación borra el objeto del servidor. La segunda llamada (estrella 2) que hace el cliente a la calculadora, el servidor crear el objeto y hasta que no le responda al cliente, el objeto no lo borrará. Podemos comprobar como nos sería imposible guardar el estado de un objeto, ya que el servidor borra el objeto al dar una respuesta al cliente.

Singleton

Con este modo, todos los clientes comparten un mismo objeto en el servidor. De este modo, el servidor si que guarda una única instancia del objeto y así será posible guardar su estado.

singleton

Al contrario que en SingleCall, esta vez podemos cambiar el estado del objeto del servidor, ya que no se destruirá en ningún momento, y además podrán compartir el mismo objeto varios clientes.

Objetos activados por el cliente (CAO)

Son iguales a los SingleCall pero con la diferencia de que los objetos guardan el estado en el servidor, es decir, el servidor guarda todos los objetos de los clientes.

cao

Como podemos ver, cada cliente tiene su propio objeto en el servidor y cada uno conserva su propio estado.

2. Hacer que nuestra clase sea remota

Para que una clase se pueda compartir de forma remota, únicamente hará falta añadir la interfaz System.MarshalByRefObject a esa clase. Este nombre es muy intuitivo, significa que la clase se puede serializar (aplanar) por una referencia remota. Con referencia remota, queremos decir que en lugar de almacenar el objeto en memoria, se guardarán datos como la dirección, el puerto y otros para poder llegar hasta ese objeto que está en otra máquina. Así, si tenemos una referencia a esta clase, podremos usar sus servicios. Añadiendo esta interfaz, el código de nuestra calculadora debería quedar de la siguiente forma:

// el código del anterior tutorial…
public class Calculadora: MarshalByRefObject
{
  // el código del anterior tutorial…
}
// el código del anterior tutorial…

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.  «  [Leyendo]
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. Uso de interfaces para separar el código entre cliente y servidor.

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.  «  [Leyendo]
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. Uso de interfaces para separar el código entre cliente y servidor.

Introducción

C# es un lenguaje de programación orientado a objetos, con una sintaxis muy similar a C++. Se diseñó con la intención de derivar las mejoras de otros lenguajes (Java, Delphi…) mezclando la posibilidad de programar en bajo nivel, y la flexibilidad de los lenguajes orientados a objetos. Entre todas la ventajas de este lenguaje, vease en wikipedia, destacamos el uso del modelo de objetos .NET (o Mono) y la portabilidad del código.

.NET Remoting (Mono Remoting) es un entorno (framework) para la creación de aplicaciones distribuidas. Como cualquier otro framework, Remoting nos ofrece una serie de servicios y mecanismos para el paso de mensajes entre objetos, creación y gestión de vida de los objetos.

Descargar el código fuente de este tutorial.

0. Instalación de los compiladores y el framework bajo [X]Ubuntu

Para instalar tanto el compilador de C# como el framework de Mono, abriremos una consola y escribiremos:

sudo aptitude install monodevelop mono-gmcs

Con estas instrucciones no solo instalaremos el compilador y el framework, sino también un editor gráfico para programar sobre C# y Mono.

1. Creación de una calculadora

El objetivo de esta serie de tutoriales, será ofrecer remotamente el servicio de una calculadora de diferentes modos, que veremos más adelante. Para empezar, crearemos un calculadora simple que sume, divida y haga la potencia sobre una base, y así podremos familiarizarnos con el lenguaje C#.

// Archivo: Calculo.cs
// Descripcion: Clase que simula algunas operaciones de una calculadora
// Biblioteca estandar
using System;
 
// Un espacio de nombres puede contener varias clases
// En este caso, tendra una llamada Calculadora
namespace Calculo
{
  // Clase Calculadora
  public class Calculadora
  {
    // Atributo para usarlo en el metodo Potencia
    private double laBase;
 
    // Properties: son el GET y el POST, en C#, de un atributo
    // y asi poderlo usarlo de esta forma: obj.Base
    // Nos evita, como en otro lenguajes, el uso de obj.getBase()
    // y obj.setBase()
    public double LaBase
    {
      get { return this.laBase; }
      set { this.laBase = value; }
    }
 
    // Constructor
    public Calculadora()
    {
      Console.WriteLine(“Constructor.”);
      this.laBase = 10.0;
    }
 
    // POST: Suma dos numeros
    public double Suma(double izq, double dcha)
    {
      Console.WriteLine(“Metodo: Suma.”);
      return izq + dcha;
    }
 
    // POST: Divide dos numeros
    // EXCEPTION: si el denominador es 0
    public double Divide(double numerador, double denominador)
    {
      Console.WriteLine(“Metodo: Divide.”);
      if (denominador == 0)
      {
        throw new Exception(“Division por 0″);
      }
      else
      {
        return (numerador / denominador);
      }
    }
 
    // POST: Eleva nuestra base al numero que le pasamos en
    // el argumento
    public double Potencia(double valor)
    {
      Console.WriteLine(“Metodo: Potencia.”);
      return Math.Pow(this.laBase, valor);
    }
  }
}

Podéis ver que he añadido algunos comentarios para seguir fácilmente el código y que he añadido trazas en el constructor y los métodos. Con estas trazas podremos comprobar en que máquina se ejecuta el código cuando hagamos la aplicación distribuida.

Ahora necesitamos el programa principal que haga uso de la calculadora:

// Archivo: Principal.cs
// Descripcion: programa principal que usa la calculadora
// Bibliotecas a usar:
using System;
using Calculo;
 
public class Principal
{
  public static void Main (String[] args)
  {
    Calculadora calc = new Calculadora();
    Console.WriteLine(“Suma 2+2 = “ + calc.Suma(2.0, 2.0));
    Console.WriteLine(“Potencia 10^5 = “ + calc.Potencia(5.0));
 
    try
    {
      // La division puede darnos una excepcion, por eso
      // la metemos en este bloque de captura de excepciones.
 
      Console.WriteLine(“Division 8/2 = “ + calc.Divide(8.0,2.0));
      Console.WriteLine(“Division 5/0 = “ + calc.Divide(5.0,0.0));
    }
    catch (Exception e)
    {
      Console.WriteLine(e);
    }
  }
}

Una vez que tenemos los dos archivos creados, “Calculo.cs” y “Principal.cs”, vamos a compilarlos. Para ello, abriremos una consola. Primero vamos a compilar el archivo “Calculo.cs” y decirle que es un archivo de biblioteca:

mcs -target:library Calculo.cs

Esto nos generará un archivo llamado “Calculo.dll”, que contendrá todas las clases que estén en el espacio de nombres “Calculo”, en nuestro caso la clase “Calculadora”. Ahora pasamos a compilar el programa principal junto con la biblioteca, para que pueda hacer uso de esta:

mcs -r:Calculo.dll Principal.cs

Y esto nos generará el archivo ejecutable “Principal.exe” (todo esto de archivos “dll” y “exe” suena un poco a Windows… aunque lo estemos haciendo en Linux). Para ejecutar el programa, simplemente lo escribimos en consola:

./Principal.exe

Y obtendremos:

Constructor.
Metodo: Suma.
Suma 2+2 = 4
Metodo: Potencia.
Potencia 10^5 = 100000
Metodo: Divide.
Division 8/2 = 4
Metodo: Divide.
System.Exception: Division por 0
at Calculo.Calculadora.Divide (Double numerador, Double denominador) [0x00000]
at Principal.Main (System.String[] args) [0x00000]

Si pruebas a ejecutar el programa bajo Windows (con las bibliotecas .NET instaladas) nos daremos cuenta de que… también funciona!!

Con esto, terminamos este primer acercamiento a C#. Ahora viene lo interesante: como hacer la aplicación distribuida.

Indice general de los manuales

  1. Introducción, instalación en Ubuntu y creación de una calculadora.  «  [Leyendo]
  2. Modos de compartición de objetos. Creando nuestra primera calculadora distribuida.
    1. Modos de compartición de objetos: Singlecall.
    2. Modos de compartición de objetos: Singleton.
    3. Modos de compartición de objetos: CAO.
  3. Canales: TCP y HTTP. Configuración en archivo XML.
  4. Uso de interfaces para separar el código entre cliente y servidor.

© 2007-2010 El blog de ERiDeM. Free wordpress themes.