jueves, 14 de junio de 2012

Aplicación de 3 capas multihilo con DBX4 y Delphi XE - multi-threaded application with Delphi XE and DataSnap DBX4


Fuente Original: Trabajando con Delphi XE Un punto de reunión para programadores en Delphi 




APLICACIÓN SERVIDOR


Paso 1 Servidor
Abrimos Delphi y vamos a File New Other .



Paso 2 Servidor
Ahora vamoa a New Item elegir "Delphi Projects" - "DataSnap Server" y hacer clic "DataSnap Server" y "OK"



Paso 3: Servidor
Tipo de Proyecto
Seleccionar:
VCL Forms Application: Si se quiere que el servidor tenga una o más ventanas (es nuestro ejemplo)
Console Applicaton: El servidor aparecerá como una consola de comandos.
Service application: Si queremos que nuestro servidor se ejecute como un servicio en Windows.

Elegimos y pulsamos NEXT




Paso 4: Servidor

Características del Servidor
Protocols: TCP/IP para una aplicación estandar y HTTP sobre todo para aplicaciones web.
- Autenticación: Si queremos que en nuestro servidor se utilice algún tipo de identificación. No es la autenticación de una base de datos, es un nuevo sistema para proteger el código. No la marcamos
Server Methods Class. Nos genera una unidad con dos ejemplos de como usar funciones para compartir con los clientes.
En nuestro ejemplo lo dejaremos todo con las opciones por defecto y pulsamos NEXT.



Paso 5: Servidor
Número de Puerto
Nos dejará elegir el número de puerto que usará nuestra aplicación.
Nos permitirá chequear si el puerto está en uso e incluso buscar uno libre.




Paso 6: Servidor
Clase de métodos de servidor
TComponent: Cuando queremos que el servidor sólo exponga funciones, sin bases de datos.
TDataModule: Si queremos que nuestro servidor exponga bases de datos a través de los TDataSetProviders
TDSServerModule: Es la versión más completa para bases de datos ya que nos implementa el interface IAppServer
Nosotros escogemos TDSServerModule y hacemos clic en Finish





Ya tenemos nuestra versión básica de Servidor
Ahora nos aparece el proyecto con las distintas forms creadas
ServerContainerUnit1: unidad para las características del servidor
ServerMethodsUnit1: almacena los métodos que exportará el servidor, es decir, los que la aplicación cliente podrá usar.
Unit1: nuestro form principal



Ahora retocamos algo nuestro servidor:
Vamos a ServerContainerUnit1.pas en la pestaña de "Diseño"

Al componente DSServer1 le ponemos la propiedad "AutoStart" a False, para así poder iniciar/parar el servidor de forma manual desde nuestro form principal.

La propiedad HideDsAdmin es útil cuando la aplicación está totalmente terminada porque si la ponemos a TRUE no serán visibles los métodos que el servidor publique. así desde otra aplicación cliente de otra persona no se podrían usar nuestros métodos. Pero en tiempo de desarrollo hay que dejarla a FALSE.


Luego en el componente DSServerClass, en el evento "On Get Class" vemos que el asistente ha creado el siguiente código:
PersistentClass := ServerMethodsUnit1.TServerMethods1;
Es la manera de decir dónde están las cosas que el servidor va a compartir.


MUY IMPORTANTE: anotar ese TServerMethods1 ya que lo vamos a necesitar en el lado del cliente a la hora de configurarlo.

El DSTCPServerTransport1 es donde configuramos el máximo hilos, el puerto, y una cosa muy importante: los filtros para el transporte de datos. De esto último hay un ejemplo de comprimir las transmisiones en este blog.




Luego en el componente DSServerClass1 ponemos la propiedad LifeCycle session
Los tipos posibles son
Server: Un servidor para todos los clientes. El segundo que intenta acceder a un dato tiene que esperar.


Sesión. Cada cliente crea su servidor. Nadie espera salvo uno mismo si es multihilo. Guarda la memoria hasta que cierre sesión. Para iniciarse es mejor esta opción porque la Invocation puede hacer cosas que no esperamos.


Invocación. Mayor coste de proceso. No gasta memoria. No hay esperas. El problema que puede aparecer y que requiere de más atención es que como el TSQLConnection se cierra cuando no hay ningún acceso abierto, puede darse el caso de que las consultas nos devuelvan valores inesperados, como si acabásemos de entrar de nuevo.




La unidad ServerMethodUnit1 


En la unidad ServerMethodsUnit1 ponemos todo lo que queremos que el servidor publique para los clientes: Funciones, TQuery, TTable, ...

Si son function o procedure y las creamos de forma automática con SHIFT-CTRL-C recodar que se habrán creado en la zona Private y entonces no serán publicadas en el servidor.
Recordar declarar  function o procedure en el en Public.

Si en el asistente de servidor hemos marcado la casilla de "Sample Methods" veremos que estarán creadas dos funciones de ejemplo:


function TServerMethods1.EchoString(Value: string): string;
begin
  Result := Value;
end;


function TServerMethods1.ReverseString(Value: string): string;
begin
  Result := StrUtils.ReverseString(Value);
end;

Vamos a añadir nuestro ejemplo simple pero algo más útil: saber la hora que tiene el servidor.
Podría permitirnos sincronizar ordenadores o grabar en un registro la hora real del servidor y no la del pc desde donde lo ejecuta el cliente.

Function TServerMethods2.HoraEnServidor: TDateTime;
begin
  result := now;
end;

En esta unidad en donde podríamos, por ejmplo, poner los TSQLQuery con sus TDAtaSetProvider para suministrar datos de nuestra base de datos a los clientes.


Ahora abrimos la unit1 y le ponemos un TButton con el caption "iniciar"


















Añadimos al
uses la unidad: ServerContainerUnit1;


Y en el evento "on click" del TButton ponemos:

procedure TForm1.Button1Click(Sender: TObject);
begin  if ServerContainer1.DSServer1.started then
     begin
     ServerContainer1.DSServer1.Stop;
     Button1.Caption := 'Iniciar';
     end
  else
     begin
     ServerContainer1.DSServer1.Start;
     Button1.Caption := 'Parar';
     end;
end;






Ahora grabamos todo el proyecto en una carpeta, mejor separando al servidor del cliente:
Proyecto\Servidor
Y grabamos el proyecto con el nombre "servidor"

MUY IMPORTANTE: ejecutamos el servidor en modo "sin depuración" haciendo clic con el botón secundario del ratón sobre el proyecto Servidor y eligiendo "Run Without Debugging", porque a la hora de configurar y ejecutar el cliente lo vamos a necesitar

Dado que hemos creado un servidor con inicio manual, recordar una vez ejecutado el servidor hacer clic sobre el botón iniciar del servidor para que esté totalmente operativo.






APLICACIÓN CLIENTE
Sobre el proyecto pulsamos el botón derecho del ratón y elegimos "Add New Project"


Hacemos clic sobre "Delphi Projects" y seleccionamos "VCL Form Application" y "OK"




RECORDAR:
 - que el servidor tienen que estar ejecutándose e iniciado.
- El proyecto activo en Delphi (el que está en negrita a la derecha) sea el del cliente, porque de lo contrario lo que vamos a hacer a continuación lo pondrá en el proyecto del servidor y habremos hecho un lío.

Ahora vamos al menú "File", "new","Other", elegimos "Delphi Projects", luego "DataSnap Server" y "Data Snap Client Module"


Nos pedirá el tipo de servidor: Local o Remoto. La diferencia es que en el remoto pide nombre o IP del host y en el local no nos lo pide ya que pone "localhost"


Ahora nos pide el tipo de servidor: en nuestro caso será sólo con DataSnap stand alone server



Ahora nos pregunta que tipo de protocolo usaremos: en nuestro caso TCP/IP



Y ahora nos pide el puerto que le hemos puesto al servidor.
Es importante que el servidor se esté ejecutando, ya que de lo contrario no conectará y no nos dejará continuar.

Si lo hemos hecho bien, y pulsamos el botón "Test Connection" nos mostrará un mensaje de "test connection succeeded"

Y entonces podremos pulsar el botón "finish"

Ahora grabamos el nuevo proyecto como "cliente", quedándonos algo como lo siguiente:





Ahora en la unit ClientModuleUnit1 nos aparecerá un SQLConnection1. No debemos modificar nada de ese componente.

Añadimos DSProviderConnection y cambiamos lo siguiente:
propiedad Sqlconnection apuntando a SQLConnection1 (si no la hemos renombrado)
y lo más IMPORTANTE: la propiedad ServerClassName con TServerMethods1

IMPORTANTE: No es una propiedad con una lista desplegable ni nada parecido. Hay que escribirlo y te deja poner cualquier cosa, eso sí, si no está correcto no funcionará nada.

Es algo que expliqué en la parte del servidor y lo repito de nuevo:

En el componente DSServerClass de la unidad ServerContainerUnit1;, en el evento "On Get Class" vemos el siguiente código que se genera automáticamente con el asistente:
PersistentClass := ServerMethodsUnit1.TServerMethods1;




Luego ponemos un TSQLServerMethod y cambiamos
propiedad Sqlconnection apuntando a SQLConnection1


Ahora en propiedad ServerMethodName hacemos clic y si lo hemos hecho todo correcto y el servidor está funcionando, nos saldrá una lista con todas las funciones que el servidor está publicando.
En nuestro caso elegimos TServerMethods1.HoraEnServidor


Ahora hacemos doble clic en su propiedad Params y nos mostrará los parámetros de entrada salida del método elegido. En nuestro caso sólo tiene de salida por si lo queremos renombrar.


Y ya sólo nos queda dar utilidad a nuestro cliente, pidiendo la hora a nuestro servidor y mostrándola.

Ahora abrimos la unidad unit3
En la clausula uses añadimos: ClientModuleUnit1;

Ponemos un TButton

En el evento onclick del TButton ponemos

procedure TForm3.Button1Click(Sender: TObject);
begin
//  Así sería si tuviese parámetros de entrada, lo pongo para que veais el funcionamiento de las llamadas.
//  ClientModule1.SqlServerMethod1.ParamByName('LoQueSea').Text := 'ValorEnviado';
  
  ClientModule1.SqlServerMethod1.ExecuteMethod;
  Showmessage (FormatDateTime 
        ('dd/mm/yy  hh:nn',ClientModule1.SqlServerMethod1.ParamByName('ReturnParameter').Value));
end;

Y ya tenemos una aplicación cliente servidor.
Si están interesados también podemos hacer lo mismo pero con bases de datos que es algo más complejo pero más apasionante.