05 marzo 2010

La librería Synapse (1)

Buscando componentes de comunicaciones alternativos a los indocumentados Indy, me encontré con esta librería que soporta diversos protocolos TCP/IP sin tener que instalar ningún componente en Delphi. Además es gratuita y viene con todo el código fuente abierto.

La última versión es la release nº 39 con fecha 9/10/2009 y se encuentra en esta página web:

http://www.synapse.ararat.cz/doku.php

Nos vamos al apartado Download y nos bajamos la última versión estable:

Donde tengamos el directorio de los otros componentes, creamos la carpeta Synapse y descomprimimos dentro este zip:

Luego solo tenemos que preocuparnos de enlazar la carpeta \Synapse\source\lib dentro del Search Path de nuestro proyecto.

Vamos a ver todo lo que se puede hacer con esta librería. Para hacer las pruebas he utilizado Delphi 2007.

CREAR UN CLIENTE TFTP

El protocolo TFTP (Trivial File Transfer Protocol – Protocolo de Transferencia de Archivos Trivial) es una versión descafeinada del protocolo FTP que solo permite subir o bajar archivos directamente del servidor sin tener en cuenta usuarios, carpetas, permisos, etc.

Para realizar el transporte de datos utiliza el protocolo UDP usando el puerto 69 (en vez del 21 como el FTP). Como necesitaba un servidor TFTP para hacer las pruebas, me bajé el servidor gratuito tftpd32:

Se puede descargar de esta página web:

http://tftpd32.jounin.net/

Solo hay que ejecutarlo y listo. Por defecto, comparte los archivos que están en su misma carpeta, pero podemos pulsar el botón Browse para cambiarla. Comencemos a crear el servidor con un nuevo proyecto te tenga este formulario:

Debemos añadir la unidad FTPTSend en la sección uses de la unidad actual. Y como dije antes, enlazamos la carpeta \Synapse\source\lib dentro del Search Path de nuestro proyecto.

El formulario se divide en tres partes principales:

La dirección del servidor y el puerto que va a utilizar el usuario:

Los componentes TEdit que he utilizado se llaman Servidor y Puerto.

El apartado para descargar un archivo del servidor:

El componente TEdit donde metemos el nombre del archivo se llama Descargar. Al pulsar el botón BDescargar hacemos lo siguiente:

procedure TFClienteTFTP.BDescargarClick(Sender: TObject);
var
Cliente: TTFTPSend;
begin
// Creamos el cliente
Cliente := TTFTPSend.Create;

// Establecemos los parámetros de conexión
Cliente.TargetHost := Servidor.Text;
Cliente.TargetPort := Puerto.Text;

// ¿Podemos descargar el archivo?
if Cliente.RecvFile(Descargar.Text) then
begin
// Lo guardamos a disco
Cliente.Data.SaveToFile(ExtractFilePath(Application.ExeName) + Descargar.Text);
EMensajeDescarga.Caption := 'OK';
EMensajeDescarga.Font.Color := clGreen;
end
else
begin
EMensajeDescarga.Caption := 'ERROR: ' + Cliente.ErrorString;
EMensajeDescarga.Font.Color := clMaroon;
end;

EMensajeDescarga.Visible := True;

// Liberamos el cliente
Cliente.Free;
end;

He utilizado la etiqueta EMensajeDescarga que por defecto está invisible para mostrar OK o Error en caso de que falle. No hace falta introducir la ruta del archivo en el servidor, sólo el nombre del mismo:

Y otro apartado para subir un archivo al servidor:

El usuario debe pulsar el botón Examinar para volcar el nombre con la ruta al campo TEdit llamado Subir:

procedure TFClienteTFTP.BExaminarClick(Sender: TObject);
begin
if AbrirArchivo.Execute then
Subir.Text := AbrirArchivo.FileName;
end;

AbrirArchivo es un componente TOpenDialog. Al pulsar el botón BSubir comprobamos antes si el archivo es válido:

procedure TFClienteTFTP.BSubirClick(Sender: TObject);
var
Cliente: TTFTPSend;
begin
// Comprobamos si el usuario ha seleccionado el archivo
if Subir.Text = '' then
begin
Application.MessageBox('Seleccione el archivo que desea subir al servidor.',
'Atención', MB_ICONEXCLAMATION);
ActiveControl := Subir;
Exit;
end;

// Comprobamos si el archivo a subir existe
if not FileExists(Subir.Text) then
begin
Application.MessageBox('El archivo que intenta subir al servidor no existe.',
'Atención', MB_ICONEXCLAMATION);
ActiveControl := Subir;
Exit;
end;

// Creamos el cliente TFTP
Cliente := TTFTPSend.Create;

// Establecemos los parámetros de conexión
Cliente.TargetHost := Servidor.Text;
Cliente.TargetPort := Puerto.Text;

// Intentamos subir el archivo
Cliente.Data.LoadFromFile(Subir.Text);

if Cliente.SendFile(ExtractFileName(Subir.Text)) then
begin
EMensajeSubida.Caption := 'OK';
EMensajeSubida.Font.Color := clGreen;
end
else
begin
EMensajeSubida.Caption := 'ERROR: ' + Cliente.ErrorString;
EMensajeSubida.Font.Color := clMaroon;
end;

EMensajeSubida.Visible := True;

// Liberamos el cliente
Cliente.Free;
end;

Como puede verse en ambos casos, el código a escribir para la transferencia de archivos por TFTP entre el cliente y el servidor es extremadamente sencilla. Solo le he encontrado un inconveniente y es que no tiene ningún evento especial para monitorizar la transferencia en tiempo real, algo imprescindible para poner una barra de progreso.

CREAR UN SERVIDOR TFTP

Ahora le damos la vuelta a la tortilla y vamos a implementar el servidor para sustituir tftp32. Aquí la cosa no va tan fácil ya que hay que crear todo el servidor a mano leyendo y procesando los mensajes del mismo. Como es natural, me he basado en las demos que lleva esta librería para crearlo, pasando a español todo lo que he podido de la clase que controla el servidor.

Para empezar creamos un nuevo proyecto con este formulario:


Volvemos a vincular la carpeta \Synapse\source\lib dentro del Search Path de nuestro proyecto y añadimos la unidad FTPTSend.

Le he metido el campo Directorio de la clase TEdit para guardar el directorio por defecto que comparte los archivos. También tiene un componente TMemo llamado Mensajes para monitorizar el estado del servidor.

Por si queremos cambiar el directorio que comparte con los clientes, podemos pulsar el botón BExaminar para elegir otro directorio:

procedure TFServidorTFTP.BExaminarClick(Sender: TObject);
var
sDirectorio: string;
begin
if SelectDirectory('Seleccione un directorio', '', sDirectorio) then
Directorio.Text := sDirectorio;
end;

Ahora vamos a crear el servidor. Para que la aplicación no se quede bloqueada hay que meter el servidor dentro de un hilo de ejecución que procese las peticiones de los clientes. Lo primero es crear la clase que controla al servidor:

type
// Hilo encargado de recibir mensajes de los clientes
THiloServidor = class(TThread)
private
{ Private declarations }
FServidor: TTFTPSend;
FIP: string;
FPuerto: string;
FMensaje: string;
procedure ActualizarMensajes;
protected
procedure Execute; override;
public
constructor Create(sIP, sPuerto: string);
end;

El constructor solo recoge la IP y el puerto:

constructor THiloServidor.Create(sIP, sPuerto: string);
begin
FIP := sIP;
FPuerto := sPuerto;
inherited Create(False);
end;

También va a tener un procedimiento sincronizado para enviar al formulario los mensajes, ya que un hilo no puede manejar componentes VCL directamente:

procedure THiloServidor.ActualizarMensajes;
begin
FServidorTFTP.Mensajes.Lines.Add(FMensaje);
end;

Y ahora viene la parte gorda, la encargada de recibir mensajes de los clientes y enviar o recibir archivos a los mismos:

procedure THiloServidor.Execute;
var
RequestType: Word;
FileName: String;
begin
// Creamos el servidor encargado de escuchar a los clientes
FServidor := TTFTPSend.Create;
FMensaje := 'Servidor arrancado con el puerto ' + FPuerto + '.';
Synchronize(Actualizarmensajes);
FServidor.TargetHost := FIP;
FServidor.TargetPort := FPuerto;

try
// Mientras no termine el hilo de ejecución escuchamos los
// mensajes de los clientes
while not Terminated do
begin
// ¿Ha llegado algún mensaje?
if FServidor.WaitForRequest(RequestType,FileName) then
begin
// Mostramos quién realiza las solicitudes
case RequestType of
1:FMensaje := 'Solicitan lectura de ' +
FServidor.RequestIP + ':' + FServidor.RequestPort;

2:FMensaje := 'Solicitan escritura de ' +
FServidor.RequestIP + ':' + FServidor.RequestPort;
end;

Synchronize(ActualizarMensajes);
FMensaje := 'Archivo: ' + Filename;
Synchronize(ActualizarMensajes);

// Procesa la solicitud
case RequestType of
1:begin // Solucitud de lectura (RRQ)
if FileExists(FServidorTFTP.Directorio.Text + FileName) then
begin
FServidor.Data.LoadFromFile(FServidorTFTP.Directorio.Text +
FileName);

if FServidor.ReplySend then
begin
FMensaje := '"' + FServidorTFTP.Directorio.Text +
FileName + '" correctamente enviado.';
Synchronize(ActualizarMensajes);
end;
end
else
FServidor.ReplyError(1, 'Archivo no encontrado');
end;

2:begin // Solicitud de escritura (WRQ)
if not FileExists(FServidorTFTP.Directorio.Text + FileName) then
begin
if FServidor.ReplyRecv then
begin
FServidor.Data.SaveToFile(FServidorTFTP.Directorio.Text +
FileName);
FMensaje := 'Archivo guardardo en ' +
FServidorTFTP.Directorio.Text + FileName;
Synchronize(ActualizarMensajes);
end;
end
else
FServidor.ReplyError(6, 'El archivo ya existe.');
end;
end;
end;
end;
finally
FServidor.Free;
end;
end;

Por último, al crear el formulario establecemos como ruta de compartición de archivos el mismo directorio del programa y arrancamos el servidor:

procedure TFServidorTFTP.FormCreate(Sender: TObject);
begin
Directorio.Text := ExtractFilePath(Application.ExeName);
HiloServidor := THiloServidor.Create('0.0.0.0', '69');
end;

Ahora ejecutamos el servidor y el cliente, y probamos a enviar y recibir archivos:

Utilizando en nuestro software los clientes y servidores de TFTP podemos hacer una transferencia de información entre programas en una red local, independientemente de la conexión al servidor de bases de datos. La velocidad es muy rápida y fiable, pero como he dicho antes, hecho de menos controlar el progreso de la descarga cuando los archivos son grandes.

Quizás la librería Synapse no sea tan sofisticada como los componentes Indy, pero por lo menos dan ejemplos y documentación bastante asequible. En el próximo artículo seguiré investigando sobre otros protocolos.

Pruebas realizadas en RAD Studio 2007.

Publicidad