17 octubre 2008

Crea tu propio servidor HTTP (1)

Si bien hoy en día hay servidores de páginas web gratuitos y de calidad como pueden ser Apache, LightHTTP, IIS, etc. no está de más crear nuestro propio servidor de páginas web para tener nuestra propia intranet en nuestro hogar o incluso en nuestra oficina. Si a eso le sumamos los conocimientos que ya tenemos de Delphi respecto al acceso a bases de datos podemos crear pequeñas aplicaciones web muy interesantes.

Como viene siendo habitual en esta serie de artículos vamos a utilizar los componentes Indy. Lo que vamos a hacer es que el usuario que se conecte al servidor entre a una página principal y luego para entrar a nuestra zona privada tenga que identificarse con usuario y contraseña.

CREANDO LA VENTANA DEL SERVIDOR

Al igual que hicimos con el servidor de Telnet, sería interesante monitorizar las conexiones de los usuarios que se van a conectar a nuestro servidor. Por ello vamos a crear un nuevo proyecto cuya ventana principal es la siguiente:

En este formulario hemos introducido dos botones para conectar y desconectar el servidor y un componente de la clase TMemo llamado Log donde iremos mostrando los eventos que ocurren. Y por último añadimos el componente más importante: IdHTTPServer situado en la pestaña Indy Servers.

El código asociado a los botones Activar y Desactivar no puede ser más sencillo:

procedure TFServidorHTTP.BActivarClick(Sender: TObject);
begin
Servidor.Active := True;
Log.Lines.Add( 'Servidor activado.' );
BActivar.Enabled := False;
BDesactivar.Enabled := True;
end;

procedure TFServidorHTTP.BDesactivarClick(Sender: TObject);
begin
Servidor.Active := False;
Log.Lines.Add( 'Servidor desactivado.' );
BActivar.Enabled := True;
BDesactivar.Enabled := False;
end;

Al ejecutar el programa y pulsar el botón Activar veremos que salta el cortafuegos de Windows (o el que tengamos configurado por defecto) donde debemos pulsar el botón Desbloquear para tener nuestro servidor operativo:


Después sólo tenemos que abrir nuestro navegador de Internet preferido y teclear localhost o bien http://127.0.0.1 y debe aparecer una página web en blanco.

Pues bien, cada vez que un usuario entra a nuestro servidor de páginas web con cualquier navegador, lo que realmente hace es enviarnos este comando por el puerto 80:

GET /

Esto significa que debemos darle la página principal de entrada, que en nuestro caso va a ser un archivo HTML que vamos a crear con el Bloc de Notas de Windows y que va a contener lo siguiente:


El archivo lo guardamos con el nombre index.html en el mismo directorio donde se encuentra el ejecutable del servidor.

Otra página que vamos a crear es la zona privada:


Este archivo lo guardamos con el nombre zonaprivada.html. Ahora viene el punto fuerte, donde hay que responder a los comandos del cliente utilizando el evento OnCommandGet del componente IdHTTPServer:

procedure TFServidorHTTP.ServidorCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
sDocumento: String;
begin
Log.Lines.Add( ARequestInfo.RemoteIP + ': ' +
ARequestInfo.Command + ARequestInfo.Document );

// ¿Va a entrar a la página principal?
if ARequestInfo.Document = '/' then
AResponseInfo.ServeFile( AContext, ExtractFilePath( Application.ExeName ) + 'index.html' )
else
begin
// Cargamos la página web que vamos a enviar
sDocumento := ExtractFilePath( Application.ExeName ) +
Copy( ARequestInfo.Document, 2, Length( ARequestInfo.Document ) );

// ¿Existe la página que ha solicitado?
if FileExists( sDocumento ) then
begin
// validamos al usuario
if not ( ( ARequestInfo.AuthUsername = 'admin' ) and
( ARequestInfo.AuthPassword = '1234' ) ) then
AResponseInfo.AuthRealm := 'ServidorHTTP'
else
AResponseInfo.ServeFile( AContext, sDocumento );
end
else
// No hemos encontrado la página
AResponseInfo.ResponseNo := 404;
end;

AResponseInfo.CloseConnection := True;
end;

Para poder compilar este ejemplo hay que añadir arriba la unidad IdContext. Este evento se divide en las siguientes partes:

1º Escribimos en la ventana de nuestro servidor quien se está conectando y el documento (pagina web, archivo, etc.) solicita:

Log.Lines.Add( ARequestInfo.RemoteIP + ': ' +
ARequestInfo.Command + ARequestInfo.Document );

2º Si lo que el usuario solicita es la página principal pues entonces se la damos sin rechistar:

// ¿Va a entrar a la página principal?
if ARequestInfo.Document = '/' then
AResponseInfo.ServeFile( AContext, ExtractFilePath( Application.ExeName ) + 'index.html' )
else
...


El método ServeFile envía cualquier archivo al navegador del cliente.

3º Traducimos el documento que nos solicitan en una ruta donde estamos ejecutando el servidor:

// Cargamos la página web que vamos a enviar
sDocumento := ExtractFilePath( Application.ExeName ) +
Copy( ARequestInfo.Document, 2, Length( ARequestInfo.Document ) );

4º Si el documento que nos solicitan no existe le mandamos un error 404. En el caso de que exista, significa que va a entrar en nuestra zona privada, con lo cual compruebo primero su usuario y password antes de dejarle pasar:

// ¿Existe la página que ha solicitado?
if FileExists( sDocumento ) then
begin
// validamos al usuario
if not ( ( ARequestInfo.AuthUsername = 'admin' ) and
( ARequestInfo.AuthPassword = '1234' ) ) then
AResponseInfo.AuthRealm := 'ServidorHTTP'
else
AResponseInfo.ServeFile( AContext, sDocumento );
end
else
// No hemos encontrado la página
AResponseInfo.ResponseNo := 404;

Una vez autentificado el usuario le enviamos la página web solicitada.

5º Por último cerramos la conexión con el cliente:

AResponseInfo.CloseConnection := True;

Ahora ejecutamos el programa y pulsamos el botón Conectar:


Ejecutamos por ejemplo Internet Explorer y escribimos locahost:

Al pulsar el enlace Ir a la zona privada hará que el navegador nos pida usuario y contraseña:


Escribimos admin Y 1234 y saltará a la zona privada:


Si volvemos a ir a la página principal y e intentamos entrar en la zona privada ya no será necesario introducir usuario y contraseña, ya que lo memoriza automáticamente el navegador.

Si intentamos solicitar a nuestro servidor una página que no existe nos devolverá un error 404:


Mientras ha sucedido todo esto, nuestro servidor ha monitorizado todo el proceso:


En esto hay que ver tres cosas importantes:

1º Si el usuario vuelve a la página principal index.html el servidor no se entera, ya que el navegador web la ha cogido de su caché de páginas, evitando el tráfico innecesario.

2º Cuando nos conectamos por primera vez a una página web, el navegador solicita el archivo favicon.ico. Este icono es el que aparece al lado de la URL de la página web donde hemos entrado:


Si os interesa más información sobre el icono favicon podéis verlo en esta página:

http://www.favicon.net/

De todas formas no hay que tomarle mucha importancia a esto porque por ejemplo Firefox no lo pide.

3º El control de entrada de usuarios que hemos realizado sólo es eso, control de usuarios, pero no es una sesión. Una sesión es la encargada de guardar el estado del usuario en nuestra página. Eso lo veremos más adelante.

CONCLUSIÓN

Como puede apreciarse en el código, con algo tan sencillo como esto ya tenemos un pequeño servidor web que permite servir páginas web a los clientes y tener una zona privada que podíamos validar con una pequeña base de datos de Internase o bien un pequeño archivo de texto encriptado.

Pero no hay que hacerse muchas ilusiones ya que un servidor web incluye muchas más cosas. Una cosa es dejar que un usuario entre a una zona privada y otra guardar el estado del usuario con una sesión mediante cookies. Eso lo veremos en el siguiente artículo. También sería interesante poder recoger información de usuarios y controlar eventos al estilo web 2.0, como puede ser la implementación de un blog, un foro, un chat online, etc.

MODIFICACIONES QUE HAY QUE REALIZAR PARA DELPHI 7

El único inconveniente que tiene este código en Delphi 7 (cuya versión de los componentes Indy es más antigua) es que el objeto AResponseInfo no tiene el método ServeFile por lo que tenemos que dárle la página web a mano. Lo que he realizado en este ejemplo es cargar la página web en un StringList y se la doy al cliente:

procedure TFServidorHTTP.ServidorCommandGet(AThread: TIdPeerThread;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
sDocumento: String;
S: TStringList;
begin
S := TStringList.Create;
Log.Lines.Add( ARequestInfo.RemoteIP + ': ' +
ARequestInfo.Command + ARequestInfo.Document );

// ¿Va a entrar a la página principal?
if ARequestInfo.Document = '/' then
begin
S.LoadFromFile( ExtractFilePath( Application.ExeName ) + 'index.html' );
AResponseInfo.ContentText := S.Text;
end
else
begin
// Cargamos la página web que vamos a enviar
sDocumento := ExtractFilePath( Application.ExeName ) +
Copy( ARequestInfo.Document, 2, Length( ARequestInfo.Document ) );

// ¿Existe la página que ha solicitado?
if FileExists( sDocumento ) then
begin
// validamos al usuario
if not ( ( ARequestInfo.AuthUsername = 'admin' ) and
( ARequestInfo.AuthPassword = '1234' ) ) then
AResponseInfo.AuthRealm := 'ServidorHTTP'
else
begin
S.LoadFromFile( sDocumento );
AResponseInfo.ContentText := S.Text;
end
end
else
AResponseInfo.ResponseNo := 404;
end;

AResponseInfo.CloseConnection := True;
S.Free;
end;

Igualmente se podía haber cargado el archivo con AssignFile y Reset o bien con un objeto TStreamFile. Eso lo dejo al gusto del usuario.

En el próximo artículo veremos como sacarle más partido a este componente.

Pruebas realizadas en RAD Studio 2007 y Delphi 7.

10 octubre 2008

Creando un cliente de Telnet

Si en artículo anterior vimos como crear un servidor de Telnet, teníamos el inconveniente de utilizar como cliente el programa Telnet.exe que trae por defecto Windows. Aunque hay muchos clientes de Telnet tanto de pago como libres vamos a ver como crear el nuestro propio.

CREANDO LA VENTANA DEL CLIENTE

Creamos un nuevo proyecto cuyo formulario principal se compone de estos componentes:


Contiene dos componentes de la clase TEdit llamados IPServidor y Puerto, así como un botón llamado BConectar. Insertaremos un componente TMemo llamado Log el cual vamos a utilizar para mostrar el texto del terminal al conectarse y el componente TIdTelnet que llamaremos Telnet y que se encuentra en la pestaña de componentes Indy Clients.

CONECTANDO CON EL SERVIDOR

Al pulsar el botón Conectar incluimos este código:

procedure TFClienteTelnet.BConectarClick(Sender: TObject);
begin
Telnet.Host := IPServidor.Text;
Telnet.Port := StrToInt( Puerto.Text );
Telnet.Connect;
Log.SetFocus;
end;

Es tan sencillo como indicarle la IP del servidor, el puerto y conectar. Después de conectarse mandamos el foco al memo para que el usuario pueda escribir en el terminal.

En el evento OnConnect del componente Telnet confirmamos la conexión por pantalla:

procedure TFClienteTelnet.TelnetConnect(Sender: TObject);
begin
Log.Lines.Add( 'Conectando con ' + Telnet.Host + '.' );
Log.Lines.Add( '' );
end;

RECIBIENDO LA INFORMACIÓN DEL SERVIDOR

Una vez conectados, es el servidor el que nos manda el texto del terminal a través del evento OnDataAvailable:

procedure TFClienteTelnet.TelnetDataAvailable(Sender: TIdTelnet;
const Buffer: string);
begin
Log.Text := Log.Text + Buffer;
Log.SelStart := Length( Log.Text );
end;

Cuando el servidor nos mande el texto lo introducimos en el campo memo y situamos el cursor en la última línea para que el usuario pueda seguir escribiendo.

PROCESANDO LOS COMANDOS

Para procesar los comandos que vamos a escribir en nuestro terminal vamos a crear una variable privada a nivel de formulario llamada sComando:

...
private
{ Private declarations }
sComando: String;
...

Y en el evento OnKeyPress del campo memo llamado Log hacemos lo siguiente:

procedure TFClienteTelnet.LogKeyPress(Sender: TObject; var Key: Char);
begin
if not Telnet.Connected then
Exit;

if Key = #13 then
begin
Telnet.Socket.WriteLn( sComando );
sComando := '';
end
else
sComando := sComando + Key;
end;

Si el usuario no ha pulsado la tecla Intro vamos añadiendo letras al comando hasta que la pulse. Entonces enviamos el comando al servidor y volvemos a vaciar la variable sComando para la siguiente instrucción.

PROBANDO LA CONEXIÓN

Pues con sólo lo que hemos hecho ya tenemos un minicliente de Telnet que vamos a probar en el mismo PC con el servidor que creamos en el artículo anterior:


Como podeis apreciar en la imagen (hacer clic para ampliar) cuando introducimos el usuario admin y la clave 1234 el servidor de telnet nos devuelve un eco con lo tecleado, aunque luego una vez dentro se comporta de manera normal.

Con un par de programas como este bien realizados se puede crear un servicio de teletexto entre varias empresas que funciona a una velocidad mucho más rápida que mediante páginas web.

Pruebas realizadas en RAD Studio 2007.

03 octubre 2008

Monitorizando aplicaciones con Telnet

El protocolo de comunicación Telnet (TELecommunication NETwork) fue diseñado inicialmente para acceder a una máquina remota por medio de comandos de teclado a través de una consola virtual utilizando para ello el puerto 23. Fue muy utilizado en los comienzos de Internet para administrar servidores Unix desde terminales remotos.

Aunque es un protocolo algo obsoleto (comparado con monitorizar ordenadores con programas como VNC o Terminal Server) si que podemos sacarle partido para controlar el estado de nuestras aplicaciones a través de Internet accediendo a la ordenador de nuestro cliente.

CREANDO EL SERVIDOR DE TELNET

Para crear un servidor de Telnet vamos a utilizar el componente IdTelnetServer situado en la pestaña de componentes Indy. Sólo tenemos que crear un nuevo formulario e introducir dicho componente:


Insertamos un componente de la clase TMemo que llamaremos Log que vamos a utilizar para monitorizar la conexión con el cliente. Luego añadimos el componente IdTelnetServer y los llamamos Servidor.

En este componente tenemos que dejar activado el servidor poniendo la propiedad Active a True. En el campo DefaultPort viene por defecto el 23 aunque podemos poner cualquier otro evitar evidencias por si hay alguién por la red escaneando puertos abiertos por debajo del 1024.

Lo que hay que asegurarse es que dicho puerto este habilitado por nuestro software que hace de cortafuegos y si tenemos un router habrá que redireccionar ese puerto desde nuestra IP pública a la tarjeta de red por medio de las opciones NAT.

Para ver si todo funciona bien ejecutamos el programa y saltará el cortafuegos de Windows (o el que tengamos por defecto) para habilitar el servidor:


Para poder probar si funciona nuestro servidor necesitamos un programa que haga de cliente de Telnet. Afortunadamente, Windows dispone en la consola de el cliente telnet.exe. Abrimos una ventana de comandos (Símbolo del sistema) y hacemos una conexión local escribiendo telnet 127.0.0.1. Si todo marcha según lo previsto debe verse este mensaje:


Podemos escribir cualquier cosa pulsando luego Intro y no hará absolutamente nada. Quedará como si estuviésemos escribiendo en un editor de texto. Al cerrar la ventana de nuestro servidor, la ventana de comandos nos dirá que hemos perdido la conexión:


También disponemos del programa Hyperterminal que se encuentra en Inicio -> Programas -> Accesorios -> Herramientas del sistema -> Comunicaciones -> Hyperterminal. Aunque este programa da algunos problemas que luego comentaré más adelante. Si no lo tuviésemos instalado se puede hacer fácilmente a través de la opción Agregar o quitar programas que se encuentra en el Panel de control.

De todas formas, con el cliente telnet de la línea de comandos nos sobra para hacer las pruebas pertinentes.

CONTROLANDO LA CONEXIÓN CON EL SERVIDOR

Para controlar el estado de la conexión vamos a utilizar los eventos OnConnect y OnDisconnet para informar en la ventana del servidor quien se ha conectado:

procedure TFServidorTelnet.ServidorConnect(AContext: TIdContext);
begin
Log.Lines.Add( 'Se ha conectado: ' + AContext.Binding.PeerIP );
end;

procedure TFServidorTelnet.ServidorDisconnect(AContext: TIdContext);
begin
Log.Lines.Add( 'Se ha desconectado: ' + AContext.Binding.PeerIP );
end;

Ahora pasemos a la validación del usuario.

VALIDANDO LOS USUARIOS QUE SE CONECTAN

Lo primero que vamos a hacer es cambiar el mensaje de bienvenida poniendo en la propiedad LoginMessage del componente IdTelnetServer la frase Servidor de Telnet.

Antes de proceder a leer comandos del cliente primero tenemos que autentificarlo para evitar intrusiones de terceros. Esto se hace en su evento OnAuthentitacion:

procedure TFServidorTelnet.ServidorAuthentication(AContext: TIdContext;
const AUsername, APassword: string; var AAuthenticated: Boolean);
begin
Log.Lines.Add('Usuario: '+AUserName);
Log.Lines.Add('Password: '+APassword);

if (AUsername = 'admin') and (APassword = '1234') then
begin
AAuthenticated := True;
AContext.Connection.Socket.WriteLn( 'Bienvenido al servidor de Telnet.' );
AContext.Connection.Socket.Write( '' );
AContext.Connection.Socket.Write( '>' );
Log.Lines.Add( 'Acceso concedido.' );
end
else
begin
AAuthenticated := True;
AContext.Connection.Socket.WriteLn( 'Acceso denegado.' );
AContext.Connection.Disconnect;
Log.Lines.Add( 'Acceso denegado.' );
end;
end;

Para poder compilar sin problemas con este evento debemos añadir arriba la unidad IdContext.

Es este evento lo primero que hacemos es imprimir en la ventana del servidor el usuario y password del que se conecta. Luego si adivina el usuario admin y la clave 1234 entonces le damos el mensaje de bienvenida y esperamos a que el usuario escriba un comando. Si no ha podido acceder le denegamos la conexión.

LEYENDO LOS COMANDOS ENVIADOS POR EL CLIENTE TELNET

Una vez que el usuario esta dentro lo que hay que hacer es ir leyendo los comando que envía y según nuestro criterio le vamos dando respuestas. Esto se hace en el evento OnExecute:

procedure TFServidorTelnet.ServidorExecute(AContext: TIdContext);
var
sComando: String;
begin
sComando := LowerCase( AContext.Connection.Socket.ReadLn );
Log.Lines.Add( 'Comando: ' + sComando );

if sComando = 'quit' then
begin
AContext.Connection.Socket.WriteLn( 'Gracias por su visita.' );
AContext.Connection.Disconnect;
end
else
if sComando = 'fecha' then
begin
AContext.Connection.Socket.WriteLn( DateToStr( Date ) );
AContext.Connection.Socket.Write( '>' );
end
else
if sComando = 'hora' then
begin
AContext.Connection.Socket.WriteLn( TimeToStr( Time ) );
AContext.Connection.Socket.Write( '>' );
end
else
begin
AContext.Connection.Socket.WriteLn( 'Comando desconocido: '+sComando );
AContext.Connection.Socket.Write( '>' );
end;
end;

Este es un proceso cíclico en el que vamos leyendo comandos y dando su respuesta. En este caso sólo he programado tres comandos:

quit -> abandona la conexión

fecha -> devuelve la fecha del ordenador remoto

hora -> devuelve la hora del ordenador remoto


PROBANDO EL SERVIDOR


Vamos a realizar una prueba de conexión:


Esta sería la ventana de bienvenida:


Ahora escribimos como usuario admin con password 1234:


Una cosa rara que pasa con nuestro servidor telnet de Indy es que envía el eco de lo que escribimos, cuyo problema no se si esta en el cliente telnet.exe o en el componente IdTelnetServer. Cuando lo averigüe actualizaré este artículo con la solución (se aceptan sugerencias). Con el programa Hyperterminal no sucede esto al hacer login, pero tiene el problema que una vez estamos dentro no vemos lo que estamos escribiendo.

En fin, escribimos usuario y clave y al entrar veremos el mensaje de bienvenida:


Ahora probamos cada uno de nuestros comandos:


En nuestro servidor también vamos registrando los eventos:


Como se puede apreciar en la ventana del servidor vemos las acciones que ha ido haciendo nuestro cliente. Este protocolo tiene la ventaja de que gasta un ancho de banda ridículo para los tiempos que estamos (ADSL, Cable, etc.) dándonos la potencia de monitorizar varios ordenadores simultáneamente.

Podemos inventarnos todos los comandos que nos vengan en gana, por ejemplo:

- Leer las unidades de disco del ordenador remoto.

- Acceder a las tablas de una base de datos y ampliar o reducir campos.

- Ejecutar eventos en el PC remoto: abrir CD-ROM, apagar equipo, ejecutar copia de seguridad, capturar la pantalla y enviarla por correo, etc.

Es decir, se pueden crear todas las funciones de un troyano (no seáis malos). El único inconveniente que tiene este protocolo es que viaja por la red sin encriptar, con lo que si alguien un poco avispado intercepta nuestra conexión con un sniffer podría averiguar el usuario y password de nuestro servidor telnet.

En el próximo artículo veremos las alternativas que tenemos para solucionar este problema.

Pruebas realizadas en RAD Studio 2007.

26 septiembre 2008

Enviar mensajes entre aplicaciones con IdTCPClient

Para rematar mas o menos por encima el tema de envío de mensajes entre aplicaciones (aunque se podría hacer también por FTP, IRC, TELNET, etc.) vamos a ver como enviar mensajes utilizando el componente de la clase TIdTCPClient de la paleta de componentes Indy.

A diferencia del procotolo UDP, el protocolo TCP establece una conexión estable y permanente entre el emisor y el receptor asegurando que el mensaje ha llegado a su destino. Si no se puede conectar entonces no puede enviarse el mensaje.

El único inconveniente que tiene este protocolo es que no es tan rápido como el protocolo UDP, aunque si es más seguro para comunicaciones importantes como pueden las conversaciones de un cliente de mensajería instantánea, la transferencia de datos masivos entre aplicaciones (P2P) o el envío de señales críticas entre sistemas distribuidos.

Vamos a ver como crear los dos proyectos para enviar y recibir mensajes.

CREANDO LA APLICACIÓN QUE ENVÍA EL MENSAJE

Creamos un nuevo proyecto y en el formulario principal podemos estos componentes:


En el formulario añadimos dos componentes TEdit para que el usuario escriba la IP y el puerto con el que va a realizar la conexión. También tendrá el componente de la clase TMemo llamado Mensaje donde escribiremos el texto a enviar. Por último añadimos el componente IdTCPClient que llamaremos Cliente y un botón Enviar que va ejecutar este código:

procedure TFEnviar.BEnviarClick(Sender: TObject);
begin
Cliente.Host := IP.Text;
Cliente.Port := StrToIntDef( Puerto.Text, 0 );
Cliente.Connect;
Cliente.Socket.WriteLn( Mensaje.Text );
Cliente.Disconnect;
end;

Lo que hacemos es fijar en el componente Cliente la IP y puerto elegidos por el usuario y después conectamos con el servidor antes de enviar el mensaje. Una vez conectados enviamos el texto utilizando su propiedad Socket y desconectamos.

La comunicación la inicia el programa cliente, que es el encargado de abrir la conexión y es recibida por el programa servidor que tiene ya un puerto abierto en escucha (hemos puesto el 80) esperando a que alguien contacte con él. Una vez establecida la conexión puede comenzar la transferencia de datos.

Si los mensajes se van a enviar muy a menudo, no es necesario que el cliente conecte y desconecte continuamente, sino que podemos dejar la conexión abierta:

procedure TFEnviar.BEnviarClick(Sender: TObject);
begin
Cliente.Host := IP.Text;
Cliente.Port := StrToIntDef( Puerto.Text, 0 );

if not Cliente.Connected then
Cliente.Connect;

Cliente.Socket.WriteLn( Mensaje.Text );
end;

Vamos a ver ahora como implementar el servidor.

CREANDO LA APLICACIÓN QUE RECIBE EL MENSAJE

Hacemos un nuevo proyecto con este formulario:


En este formulario vamos a insertar dos componentes:

- Un componente TMemo llamado Mensaje que va contener el texto recibido.

- Un componente IdTCPServer que llamaremos Servidor.

Al servidor le vamos a poner en su propiedad Port el valor 80. Después lo activamos poniendo su propiedad Active a True.

En el evento OnExecute del componente Servidor se ejecutará este código:

procedure TFRecibir.ServidorExecute(AContext: TIdContext);
var
sMensaje: String;
begin
sMensaje := AContext.Connection.Socket.ReadLn;
Mensaje.Lines.Add( 'De: ' + AContext.Binding.PeerIP );
Mensaje.Lines.Add( 'Mensaje: ' + sMensaje );
end;
Una aclaración: Para que este evento funcione correctamente hay que añadir en nuestro formulario un enlace a la unidad IdContext ya que sino lo hacemos nos dará un error al compilar. De hecho, esta unidad debería haberse insertado automáticamente en el código al añadir el componente IdTCPServer en el formulario. Un tirón de orejas para los programadores de los componentes Indy.

En este procedimiento guardo en la variable sMensaje el texto que envía el cliente a través del socket y luego imprimo en pantalla la IP del emisor y su mensaje.

Ejecutamos ambos programas y pulsamos el botón Enviar:


Al igual que vimos en el envío de mensajes con el protocolo UDP, si el mensaje se va a enviar entre dos aplicaciones que se encuentran en el mismo PC sólo hay que poner como dirección IP la 127.0.0.1.

Con los tres métodos que hemos visto podemos enviar pequeños mensajes en tiempo real entre aplicaciones y separar los programas muy pesados en distintos ejecutables que en envíen la información entre si. Y como no, hacer vuestro propio MSN.

Pruebas realizadas en RAD Studio 2007.

19 septiembre 2008

Enviar mensajes entre aplicaciones con IdUDPClient

En el último artículo vimos como enviar mensajes entre dos aplicaciones sin utilizar ningún protocolo de comunicación cuando los dos ejecutables se encuentran en el mismo PC. Pero si el mensaje hay que enviarlo entre dos ejecutables que se encuentran en dos PC distintos estamos obligados a utilizar un protocolo de comunicación y el más sencillo es el protocolo UDP.

El protocolo UDP goza de la ventaja de que no se establece una conexión entre la IP origen y la IP destino, sino que envía la información sin saber si va a llegar o no (al contrario de TCP que si establece una conexión segura entre ambos puntos).

El protocolo UDP es utilizado comúnmente en aplicaciones P2P para envío rápido de mensajes entre clientes y servidores así como en juegos online donde es necesario transmitir las coordenadas de cada personaje en tiempo real sin que existan demoras.

CREANDO LA APLICACIÓN QUE ENVÍA EL MENSAJE

Creamos un nuevo proyecto y en el formulario principal insertamos estos componentes:


Va a constar de dos componentes TEdit para guardar la IP del destinatario y su puerto (que por defecto le hemos puesto el 80). También va a tener un componente TMemo llamado Mensaje que va a contener las líneas de texto a enviar. Luego añadimos el componente IdUDPClient que se encuentra dentro de la paleta de componentes Indy Clients y que vamos a llamar Cliente.

En el evento OnClick del botón Enviar escribimos el siguiente código:

procedure TFEnviar.BEnviarClick(Sender: TObject);
begin
Cliente.Host := IP.Text;
Cliente.Port := StrToIntDef( Puerto.Text, 0 );
Cliente.Send( Mensaje.Text );
end;

Es algo tan sencillo como pasarle la IP destino (el Host), el puerto y ejecutamos el método Send que envía la información.

El único inconveniente que tiene este método es que no sabe si el destinatario existe o si le ha llegado el mensaje, pero es muy rápido.

CREANDO LA APLICACIÓN QUE RECIBE EL MENSAJE

Volvemos a crear un nuevo proyecto con este formulario:


Sólo va a contener un componente TMemo llamado Mensaje y el componente IdUPDServer que llamaremos Servidor. Al servidor le vamos a poner en su propiedad Port el valor 80. Después lo activamos poniendo su propiedad Active a True.

Ahora escribimos este código en el evento OnUDPRead:

procedure TFRecibir.ServidorUDPRead(Sender: TObject; AData: TBytes;
ABinding: TIdSocketHandle);
begin
Mensaje.Lines.Add( 'De: ' + ABinding.PeerIP );
Mensaje.Lines.Add( 'Mensaje: ' + PChar( AData ) );
end;

Mediante la propiedad ABinding.PeerIP obtenemos la IP del que envía el mensaje y la variable AData contiene el mensaje enviado en bytes.

Al ejecutar ambos programas y pulsar el botón Enviar este sería el resultado:


Si el mensaje se va a enviar entre dos aplicaciones que se encuentran en el mismo PC sólo hay que poner como dirección IP la 127.0.0.1 y funciona perfectamente. Incluso si seguimos escribiendo mensajes se añadirán a los que ya están en el destino.

Con algo tan simple como esto ya tenemos un pequeño cliente de mensajería entre dos equipos de la red local o en Internet. Para que la conversación pudiera ser bidireccional habría que crear un solo programa que tuviera un cliente UDP y un servidor UDP. También habría que dejar a elegir al usuario que IP y puertos va a utilizar para enviar y recibir.

Si la información que vamos a enviar es confidencial también sería interesante que las cadenas de caracteres que se envíen fueran encriptadas de algún modo.

Pruebas realizadas en RAD Studio 2007.

12 septiembre 2008

ENVIAR MENSAJES ENTRE APLICACIONES SIN COMUNICACIONES

La forma más usual de copiar información entre dos aplicaciones de Windows es usando algún protocolo de comunicaciones, como TCP o UDP. Pero si la comunicación se va a efectuar entre dos aplicaciones que se encuentran en el mismo PC podemos enviar la información sin utilizar ningún protocolo de comunicación.

Para ello se utiliza un mensaje especial que tiene la API de Windows llamado WM_COPYDATA. Lo que permite este mensaje es copiar cualquier tipo de dato, ya sea texto o binario entre dos aplicaciones Windows tal como si se pasara por el portapapeles. De este modo la comunicación es instantánea y asíncrona.

Una aplicación práctica que se le puede dar a este método es la de dividir nuestro programa en varios ejecutables y se que envíen la información entre sí (siempre que estemos dentro el mismo ordenador).

CREANDO LA APLICACIÓN QUE ENVÍA EL MENSAJE

Lo primero que vamos a hacer es crear un nuevo proyecto y al formulario principal de la aplicación lo vamos a llamar FEnviar:


Este formulario sólo va a contener un objeto de la clase TMemo llamado Mensaje para escribir el texto a enviar y un botón para hacerlo (TButton). El proyecto lo podemos guardar con el nombre Enviar.exe.

Para poder transmitir un mensaje es necesario definir una estructura de datos llamada TCopyDataStruct:

TCopyDataStruct = packed record
dwData: DWORD; // de uso libre: para indicar por ejemplo el tipo de información a transmitir
cbData: DWORD; // el tamaño en bytes de los datos que se van a enviar
lpData: Pointer; // puntero a los datos que se van a enviar
end;

Esta estructura ya viene predefinida en la API original de Windows (en C) del siguiente modo:

typedef struct tagCOPYDATASTRUCT {
DWORD dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT;

El parámetro dwData se le deja libre al programador para que pueda utilizarlo a su propio criterio. Por ejemplo se podría utilizar para decirle al receptor el tipo de dato que vamos a enviar.

En el parámetro cbData debemos indicar el número de bytes que vamos a enviar y lpData es un puntero a los datos que enviamos.

Por último hacemos el evento OnClick del botón Enviar:

procedure TFPrincipal.BEnviarClick(Sender: TObject);
var
CopyDataStruct : TCopyDataStruct;
hReceptor: THandle;
Respuesta: integer;
begin
CopyDataStruct.dwData := 0; // use it to identify the message contents
CopyDataStruct.cbData := Mensaje.Width;
CopyDataStruct.lpData := PChar( Mensaje.Text );

// Comprobamos si existe el receptor

hReceptor := FindWindow( PChar( 'TFRecibir' ), PChar( 'Recibir' ) );

if hReceptor = 0 then
begin
ShowMessage( 'No he encontrado al receptor.' );
Exit;
end;

// Enviamos el mensaje y recogemos la respuesta del receptor

Respuesta := SendMessage( hReceptor, WM_COPYDATA, Integer( Handle ),
Integer( @CopyDataStruct ) ) ;

if Respuesta = 1234 then
ShowMessage( 'El mensaje ha sido recibido satisfactoriamente.' );
end;

Lo primero que hace es almacenar el mensaje en la estructura de datos y luego trata de buscar una ventana llamada Recibir de la clase TFRecibir (que vamos a crear ahora después) que va a recoger la información que se envía.

El programa comprueba si existe esta ventana antes de enviar la información con SendMessage. Como el receptor puede mandar cualquier número entero como respuesta, he establecido el número 1234 como mensaje de que todo ha ido bien.

CREANDO LA APLICACIÓN QUE RECIBE EL MENSAJE

Creamos un nuevo proyecto llamado Recibir.exe cuya ventana principal se llamara FRecibir (de la clase TFRecibir) y que sólo va a contener un objeto TMemo llamado Mensaje que va a recoger la información:


Aquí también tenemos que definir la estructura de datos TCopyDataStruct definida en el proyecto anterior. Otro registro que también vamos a definir es una estructura encargada de recibir de Windows el mensaje de tipo WM_COPYDATA:

TWMCopyData = packed record
Msg: Cardinal;
From: HWND;// Handle de la ventana que envía la información
CopyDataStruct: PCopyDataStruct; // datos enviados
Result: Longint; // usado para enviar la respuesta
end;

Ahora viene la parte especial del programa. Debemos definir un procedimiento dentro del formulario principal que recoja el mensaje de tipo WM_COPYDATA:

TFRecibir = class(TForm)
Mensaje: TMemo;
procedure WMCopyData( var Msg: TWMCopyData ); message WM_COPYDATA;
private
{ Private declarations }
public
{ Public declarations }
end;

Esta sería su implementación:

procedure TFRecibir.WMCopyData( var Msg: TWMCopyData );
begin
Mensaje.Text := PChar( Msg.CopyDataStruct.lpData );
Msg.Result := 1234; // Mensaje de respuesta de que todo ha ido bien
end;

En cuanto la aplicación Enviar.exe pulse el botón Enviar se ejecutará en la aplicación Recibir.exe automáticamente el procedimiento WMCopyData de manera instantánea:


Lo que he hecho en ese procedimiento es mandar al Memo llamado Mensaje el texto que hemos recibido.

Una cosa importante que hay que tener en cuenta es que si hay varios receptores de mensaje ejecutándose a la vez (Recibir.exe) entonces sólo lo recibirá el primero de ellos.

Como he mencionado al principio del artículo, este tipo de mensaje es muy útil si tenemos varios ejecutables dentro del mismo PC que necesitan enviarse mensajes de información rápidamente, ya sea como activación de procesos automáticos y como sincronización entre distintas aplicaciones.

Pruebas realizadas en RAD Studio 2007.

05 septiembre 2008

Pasando de Delphi 7 a RAD Studio 2007 (y 9)

Hoy vamos a terminar de ver las características más importantes que incorpora RAD Studio 2007.

Otra ventaja que incorporan las nuevas versiones de Delphi es la posibilidad es generar la documentación online de nuestro proyecto automáticamente utilizando la tecnología de Together.

También veremos el explorador de datos que incorpora que nos permite acceder a las tablas de las bases de datos sin tener que utilizar herramientas externas como IBConsole, IBExpert, etc.

GENERANDO DOCUMENTACIÓN AUTOMÁTICAMENTE

Para generar la documentación de la unidad UFacturacion que vimos en el artículo anterior debemos ir a la ventana del Model View pulsamos la unidad UFacturacion con el botón derecho del ratón y seleccionamos Generate Documentation:


Aparecerá una ventana donde podemos elegir la carpeta donde vamos a generar la documentación:


Si no modificamos nada en esta ventana lo que va a hacer es crear dentro de la carpeta de nuestro proyecto dos carpetas mas: out y dentro de esta doc donde finalmente irá la documentación.

Pulsamos el botón Ok y nos pedirá una última confirmación antes de proceder:


Cuando termine nos creará esta página web automáticamente:


En la parte superior de la página tenemos el diseño UML de nuestras clases. En la parte inferior izquierda está el índice de unidades disponibles y si pulsamos en una de ellas aparecerá a la derecha la documentación.

La documentación también puede generarse desde el menú superior seleccionando Tools -> Generate Documentation. Se puede generar documentación de sólo una unidad o bien de todo el proyecto.

De este modo, podemos distribuir nuestras librerías de código a otros programadores incorporando su documentación en línea siempre actualizada.

Aunque es una auténtica gozada poder generar la documentación de este modo se hecha mucho de menos poder manipular el formato de salida así como añadir comentarios opcionales y descripciones a la clase, los métodos y sus campos (variables).

ACCEDIENDO A LAS BASES DE DATOS CON DATA EXPLORER

Al igual que tenemos la pestaña Model View en la ventana del proyecto (Project Manager) también tenemos la última pestaña llamada Data Explorer. Esta pestaña nos permite acceder a las tablas de bases de datos como pueden ser Internase/Firebird, Oracle, MySQL, etc.


Vamos a ver un ejemplo de cómo conectar con una base de datos Firerid utilizando esta herramienta. Lo primero que tenemos que hacer el pulsar INTERBASE con el botón derecho del ratón y seleccionar Add New Connection:


Se abrirá una nueva ventana en la que tenemos que escribir el nombre que le vamos a dar a la conexión:


Pulsamos el botón Ok y veremos la conexión creada debajo de la rama INTERBASE:


Pulsamos esa conexión con el botón derecho de ratón y seleccionamos Modify Connection:


Se abrirá una ventana de configuración en la que debemos configura una conexión DBExpress para conectar con nuestra base de datos:


Una vez introducidos los parámetros de la conexión pulsamos el botón Test Connection para comprobar si todo ha ido bien. Nos tiene que mostrar este mensaje:


Después pulsamos Ok y si abrimos el apartado Tables del nuestra conexión deben verse todas las tablas de nuestra base de datos:


Al hacer doble clic sobre una tabla nos abrirá una nuestra pestaña en el editor de código con una rejilla preparada para modificar los campos de la tabla:


Si hacemos cualquier modificación sobre un registro podemos actualizarlo definitivamente enviando la transacción al servidor pulsando la tabla con el botón derecho del ratón y seleccionando Update:


Igualmente podemos deshacer los cambio pulsando sobre la opción Rollback.

Si abrimos una tabla no sólo podemos ver los nombres de los campos, sino que además se pueden ver sus índices:


Se pueden alterar los metadatos de una tabla seleccionándola con el botón derecho del ratón y eligiendo la opción Alter table:


Nos abrirá una nueva pestaña en medio del editor para modificar los campos creados:


De esta forma se pueden ampliar los campos String a más caracteres sin tener que realizar ninguna SQL. Cuando terminemos de realizar las modificaciones pulsamos la rejilla con el botón derecho del ratón y seleccionamos Save Changes:


Si antes de guardar los cambios seleccionamos la opción Show DLL nos mostrará la SQL que va a ejecutar para realizar las modificaciones:


No es que sea ninguna maravilla de editor de tablas como pueden ser IBConsole o IBExpert pero por lo menos nos permite ver los resultados de las operaciones de nuestro programa sin tener que salir del IDE.

Si nos interesa hacer alguna consulta avanzada entonces debemos seleccionar la conexión con el botón derecho del ratón y elegir SQL Window:


Esto abrirá una nueva pestaña en el centro de la ventana donde podemos escribir nuestras sentencias SQL:


En la parte inferior de este editor podemos escribir la consulta manualmente y luego pulsamos el botón Execute SQL:


Debajo de la sentencia SQL abrirá un apartado con el resultado de la operación:


Si no tenemos gana de escribir sentencias SQL se pueden realizar automáticamente haciendo doble clic sobre las tablas que hay a la derecha y pasarán al centro:


Además se creará abajo la sentencia SQL automáticamente. Incluso se pueden hacer asociaciones entre campos de varias tablas para hacer sentencias SQL más complejas:


Cuando terminemos de trabajar con la base de datos podemos cerrar la conexión pulsándola con el botón derecho del ratón y seleccionando Close connection:


Con esto doy finalizada esta serie de artículos relacionados sobre las diferencias principales entre Delphi 7 y RAD Studio 2007. Quizás en un futuro haga lo mismo entre Delphi 2007 y el recién nacido Delphi 2009 (Tiburón).

Pruebas realizadas con RAD Studio 2007.

Publicidad