Anda di halaman 1dari 7

El Rinconcito de Delphi

Gracias a Hadi Hariri por ceder, para su publicacin en


el Rinconcito de Delphi, el presente artculo. No esta permitida
ninguna accin que atente contra los derechos de autor, sin
autorizacin del mismo.
Hadi Hariri
Hadi@urusoft.com
Programacin TCP/IP
Programacin TCP/IP
Creando herramientas de red
Delphi 6, la nueva versin de la herramienta de Borland esta
a la vuelta de la esquina. Una de las novedades respecto al
mbito de la programacin con sockets es la inclusin del
paquete de componentes Indy (Internet Direct). Con este
primer nmero, vamos a empezar un curso que nos ensear
como utilizar estos componentes para desarrollar una variedad
de aplicaciones (tales como clientes de correo, transferencia de
ficheros, lector de noticias, etc).
Un poco de histria..
Indy es el sucesor del conocido paquete Winshoes, y en
concreto, es la versin 8. Winshoes es un conjunto de
componentes que actua como envoltura a la librera
Winsock (de ah el nombre, ya que en ingls Sock es calcetn y
Shoes son zapatos). Originalmente, fue desarrollado por Chad
Z. Hower (mejor conocido por Kudzu) en Visual Basic y se
port en 1993 a Delphi (versin 1). Unos cuantos aos ms
tarde (1998), se declar como proyecto Open-Source y a
mediados de 1999 form parte del proyecto J edi. Actualmente,
hay cientos de personas que colaboran en el proyecto, ya sea
aportando cdigo, arreglando fallos, documentado la
funcionalidad, etc. El soporte tcnico se ofrece atravs de los
grupos de noticias de Borland y mediante listas de correos.
Adems existe una empresa llamado Nevrona Designs que ha
hecho algo similar a lo que hizo RedHat con Linux, es decir,
empresas que quieran optar por un servicio de soporte
profesional, pueden pagar unas cuotas al ao y tener acceso a
un foro privado y soporte telefnico.
Y porque ha decidio Borland incluir a Indy en sus productos?
Las verdaderas razones lo saben ellos nicamente, sin
embargo, se puede pensar en algunos puntos que pueden haber
influido.

1. Estn conscientes de los problemas que los


desarolladores estn teniendo con el paquete actual
(NetMasters).
2. Por la naturaleza de Indy (bloqueante), es un buen
cndidato para la portabilidad entre plataformas (Windows,
Linux).
3. La gran aceptacin y uso que tiene y por ser Open-
Source.

Quiero hacer hincape en el hecho de que estos son hiptesis y


en ningn momento se dice que sean las mismas razones que
tuvo Borland. El cambio de nombre de Winshoes a Indy se
debe al hecho de que los componentes van a estar disponibles
para ambas plataformas (Windows y Linux), y el prefijo
Win no queda muy bien en el mundo Linux.

Primeros pasos
Los sockets se comportan como un modelo cliente/servidor.
Por un lado esta el cliente, por ejemplo, un navegador, un
cliente de correo o nuestro programa FTP, y por otro esta el
servidor (Servidor Web Apache, Microsoft Exchange Server,
etc). El cliente se conecta al servidor para solicitar unos
servicios. Cada socket se identifica principalmente por dos
valores: la direccin IP y el puerto. De esta manera, se puede
utilizar una misma direccin IP (por ejemplo una mquina con
una nica IP) para ofrecer distintos servicios (dependiendo del
puerto). Cuando se baja el correo de Internet, en su programa
habitual de correo-electrnico indica la direccin del servidor
de correo (smtp.mi_isp.com) y el puerto (25). Normalmente,
el puerto viene pre-establecido por defecto ya que muchos de
los servicios ms habituales de Internet tienen fijado un puerto
de comunicacin. Aunque en la configuracin se indica el
nombre del servidor, a la hora de establecer la comunicacin,
se traduce este nombre por la direccin IP correspondiente.
Esto lo realiza el servidor DNS (Domain Name Service), que a
su vez tiene su propio puerto (53). Ntese en este ltimo caso
que cuando se configura la mquina, siempre se debe
introducir la direccin IP del servidor DNS y no el nombre, ya
que no habra forma de resolverlo. Adems de utilizar un
serivdor DNS para traducir los nombres, los sistemas
operativos disponen de un fichero llamado HOSTS que realiza
la misma funcin. Este fichero se compone de entradas de la
forma:

194. 179. 1. 101 ar t emi s. t t d. net


198. 56. 20. 10 mi ser ver . acme. com

En Windows 2000 y NT, el fichero se encuentra en


c:\winnt\system32\drivers\etc\hosts. En Windows 95/98,
puede encontrarse en el directorio c:\windows, y en Linux en
la ruta /etc/hosts. De igual forma, hay otro fichero llamado
services que lleva los nmeros de los puertos ms conocidos:

echo 7/ t cp
echo 7/ udp
di scar d 9/ t cp si nk nul l
di scar d 9/ udp si nk nul l
syst at 11/ t cp user s
#Usuar i os act i vos
syst at 11/ t cp user s
#Usuar i os act i vos
dayt i me 13/ t cp
dayt i me 13/ udp
qot d 17/ t cp quot e
#Cuot a del d a
qot d 17/ udp quot e
#Cuot a del d a
char gen 19/ t cp t t yt st sour ce
#Gener ador del car ct er
char gen 19/ udp t t yt st sour ce
#Gener ador del car ct er
f t p- dat a 20/ t cp
#FTP, dat os
f t p 21/ t cp
#FTP. cont r ol
t el net 23/ t cp

En cada lnea aparace el nombre del servicio, el puerto


asociado, el protocolo que se utiliza (TCP o UDP), una lista de
aliases (o nombres comunes) y por ltimo una descripcin del
serivcio. El nmero de puerto es un valor de 16 bits y los
valores por debajo de 1024 estn reservados para el sistema
1
Programacin TCP/IP
UDP TCP
O rientado a la no conexin O rientado a la conexin
No se garantiza la llegada
de todos los paquetes
Se garantiza la llegada de
todos los paquetes
No se garantiza que los
paquetes lleguen en el
m ism o orden en que fueron
em itidos
Los paquetes llegan en el
m ism o orden en que fueron
em itidos.
Hay un lm ite sobre el
tam ao de los paquetes.
Tabla 1
operativo y servicios ya conocidos. A la hora de crear un
servidor (servicio) propio, es recomendable analizar el fichero
services y asegurarse de asignar un nmero de puerto (por
encima de 1024) que no est siendo utilizado (y adems crear
una entrada en el fichero).
La comunicacin
Ya se ha visto que para intercambiar informacin mediante
sockets es necesario indicar una direccin IP y un nmero de
puerto. Adems se debe indicar el tipo de comunicacin que se
desea realizar. En el apartado anterior vimos que en el fichero
services haba una entrada que indicaba el protocolo (TCP o
UDP). El primero de ellos es Transfer Control Protocol y es
un protocolo orientado a la conexin. El segundo es User
Datagram Protocol y es orientado a la no conexin. Y que
diferencias hay? Cuando se utiliza el protocolo TCP, antes de
empezar a transferir informacin, es necesario realizar una
conexin. Supongamos que queremos mandar la cadena XXX
a un servidor. Los pasos necesarios para hacer esto utilizando
TCP serian:

1. Conectar al servidor.
2. Mandar la cadena XXX.
3. Desconectar del servidor.

En el caso de UDP, no es necesario realizar una conexin


previa o desconectar al terminar:

1. Mandar la cadena XXX al servidor.

A primera vista UDP parece ser ms rpido ya que no se tiene


la sobrecarga de la conexin/desconexin, y de hecho lo es.
Sin embargo, este incremento de velocidad se tiene que pagar
de alguna forma, y eso es con la fiabilidad. UDP es un
protocolo no-fiable, es decir, nadie asegura que la cadena
XXX llegue al servidor, y adems, si llega, no se puede
garantizar que lo haga sin errores. UDP puede parecerse a una
carta normal. Se manda la carta por correo (sin acuse de
recibo) y no se sabe si llegar o no a su destinatario. En contra,
TCP es como un telfono: marca el nmero de telfono de un
amigo, habla con l y cuelga el receptor. Las principales
diferencias entre cada protocolo se pueden contemplar en la
"tabla 1". Cuando se utiliza UDP, el programador tiene que
encargarse de la fiabilidad de los paquetes. Es decir, tiene que
implementar un mtodo de re-envio de paquetes perdidos.
Entonces por qu utilizar UDP si es poco fiable? La respuesta
a sta pregunta depende de lo que se quiere hacer. Si tiene que
desarrollar una aplicacin que tiene que transferir datos de alta
sensibilidad, donde no hay lugar para errores, es mejor utilizar
TCP. En cambio, si no importa que de vez en cuando se
pierdan paquetes, entonces puede optar por UDP (por ejemplo,
video/audio-streaming).

Bloquear o no bloquear?
Los sockets se originaron de la definicin de Berkely Unix.
Cuando se realiza una operacin de lectura sobre un socket, el
flujo del programa se detiene hasta que se reciban los datos
necesarios. Unix maneja esto mediante una llamada al
procedimiento fork que inicia una copia del programa original
y lo ejecuta. De esta manera se evita que el programa original
se quede bloqueado. Por otro lado, Windows no dispone de
esta funcionalidad, y en la versin 3.x no existan las hebras
(Threads). Por ello, cuando se quiso trabajar con sockets
sncronos (o bloqueantes) en Windows 3.x, exista el problema
de que la GUI se quedaba bloqueada y no responda a las
interacciones del usuario. Adems, como Windows 3.x era un
sistema operativo que utilizaba una multi-tarea cooperativa
(cada proceso ceda el uso del procesador), cuando una
aplicacin quisiera operar con sockets, pareca que todo el
sistema se quedaba colgado. De ah surgi la idea de sockets
asncronos (o no-bloqueantes).
Con la aparicin de plataformas Win32, se poda utilizar las
llamadas bloquentes ya que existan las hebras. El problema
fundamental es que muchas personas se han acostumbrado a
utilizar sockets asncronos y los sncronos han cogido mala
fama. Sin embargo, para trabajar en Linux sigue siendo
necesario utilizar sockets bloqueantes, y es recomendable
perder el miedo a estos y ver lo sencillo que puede ser su uso
(de cara a la salida de Delphi para Linux).
Las diferencias principales entre otros paquetes de
componentes sockets existentes en el mercado e Indy son:

1. Indy se basa en sockets bloqueantes.


2. No depende de eventos.
3. Se ha diseado para trabajar con hebras.
4. Se programa utilizando un flujo secuencial.
Y cmo influye esto a la
hora de programar? Lo mejor
es ver una comparativa de
cdigos. Con los sockets
convencionales (asncronos),
se tiene que basar el cdigo
en los eventos producidos:

2
Programacin TCP/IP
procedure TfmMain.ConectarOnClick(Sender: Tcomponent);
begin
with Socket do begin
Connect;
while not Connected do begin
if bError then
Abort;
Application.ProcessMessages;
end;
while Length(DatosSalida) > 0 do
Application.ProcessMessages;
Disconnect;
end;
procedure TfmMain.SocketOnConnectError;
begin
bError := True;
end;
procedure TfmMain.SocketOnRead;
var
i: Integer;
begin
i := Socket.Send(DatosSalida);
DatosSalida := Copy(DatosSalida, i + 1, MaxInt);
end;
procedure TfmMain.ConectarEnviarOnClick(Sender: Tcomponent);
begin
with Socket do begin
Connect;
try
Send;
Leer; // LLAMADA BLOQUEANTE
finally
Disconnect;
end;
end;
Como se puede contemplar en el cdigo, primero se realiza la
conexin, y se procesa los mensajes de la aplicacin mientras
se espera la conexin. De nuevo, se procesan los mensajes
mientras hay datos para enviar. Hay un evento
OnConnectError que pone el valor del booleano bError a
verdadero, y otro evento OnRead que es el que se encarga de
mandar los datos. Intuitivo? La verdad es que no lo es.
Veamos el mismo ejemplo basado en sockets bloqueantes:

En este caso, en un mismo procedimiento, se realiza primero


la conexin, se envan los datos y se cierra la conexin. Como
se puede ver en el cdigo, hemos aadido adems una lectura
del Socket (Leer). El bloqueo se produce en sta lnea. El
socket se queda esperando hasta recibir datos. Esto hace que
la GUI (nuestra aplicacin) se congele, pero no se asusten ya
que hay muchas formas de remediar esto y lo veremos ms
adelante con bastante detalle.

Ms informacin y enlaces...
En los grupos de noticias de Borland se ofrece soporte
tcnico para preguntas relacionadas con Indy. Adems, las
preguntas ms frecuentes pueden buscarse en
www.deja.com/usenet. Para ms informacin:

Winshoes
www.pbe.com/Winshoes.
Indy
www.nevrona.com/indy
Open-Source
www.opensource.org
Project J edi
www.delphi-jedi.org
Sockets
www.pbe.com/Kudzu
RFC
www.rfc-editor.org

3
Figura 1. Paleta.bmp. Paleta de los componentes Indy.
El mes pasado vimos una breve introduccin a Indy y a los
sockets. Este mes vamos a cubrir lo que son los clientes.
Primero veremos como conectar con un servidor y luego
examinaremos formas de evitar el bloqueo de la interfaz de
usuario.

Instalando el paquete.
Actualmente Indy esta en fase beta. A pesar de ello, es un
paquete bastante estable e incluye muchas mejoras frente a
la versin 7 (en concreto 7.039B). Recomiendo que se
empieza a utilizar estos componentes a pesar de que estn
en esa fase, ya que no se va a seguir el desarrollo de
Winshoes 7.X. A la hora de escribir este artculo, la
versin disponible es la 8.005B. Se incluye
aproximadamente 65 componentes distribuidos entre
clientes, servidores y miscelneos. Por razones de QA
(aseguramiento de la calidad), Borland ha impuesto una
restriccin sobre nuevas funcionalidades que se pueden
sacar en esta versin. En sucesivas versiones del producto
(8.1, 8.2, etc) se aadirn mejoras. Los componentes se
pueden utilizar en las versiones 4 y 5 de Delphi (y desde
luego tambin en Delphi 6 para Windows y Linux, cuando
salgan estos).
Se puede descargar el paquete desde
http://www.nevrona.com/Indy/download.html. Hay dos
versiones disponibles. La primera de ellas incluye un
programa de instalacin que automticamente instalar el
paquete en la IDE. Sin embargo, puede tardar bastante en
bajarse sta ya que ocupa alrededor de 7.5MB. Como la
instalacin es bien sencilla, nos ocuparemos de la otra, que
viene en formato ZIP y se puede descomprimir a cualquier
ruta de su disco duro. Una vez que tenga todos los ficheros,
habr que instalarlos en Delphi. Hay dos paquetes:
dclIndyXX e IndyXX donde XX indica la versi n de
Delphi en cuestin. La primera es el paquete de tiempo de
diseo (Design time package) e incluye cosas como
editores de propiedades, etc. La segunda es el paquete de
tiempo de ejecucin (Runtime package). El procedimiento
para instalarlos es:

1. Abra el paquete IndyXX desde el men File| Open


2. Complelo (no hace falta instalarlo).
3. Abra el paquete dclIndyXX desde el men File| Open

4. Compile e instlelo.

Le debe aparecer en la IDE tres nuevas paletas de


componentes como se contempla en la figura 1. Si por
alguna razn no se ven los componentes en la paleta, lo ms
probable es que no se haya instalado el paquete dclIndy.
Lo nico que queda por hacer es aadir la ruta a la librera.
Para ello seleccione del men Tools| Environment
Options la pestaa Library, y luego aada la ruta (por
ejemplo C:\Indy) a las ya existentes. Y estamos listos para
empezar a trabajar con los componentes.

Los Clientes
Hay 20 clientes que se enumeran a continuacin:

IdTCPClient: Utilizado para realizar conexiones sobre


TCP.
IdUDPClient: Realizar conexiones sobre UDP.
IdDayTime: Permite recuperar la fecha desde un servidor
de tiempo.
IdDNSResolver: Conecta a un servidor DNS.
IdEcho: Cliente echo.
IdFinger: Permite realizar consultas a un servidor Finger.
IdFTP: Protocolo para la transferencia de ficheros.
IdGopher: Permite conectar a un servidor Gopher.
IdNNTP: Permite crear lectores de noticias.
IdICMPClient: Realizar Ping s y Traceroutes.
IdPOP3: Conectar a servidor POP para recuperar correo.
IdHTTP: Protocolo HTTP.
IdQOTD: Cliente Quote of the Day (Frase del d a).
IdRawClient: Permite trabajar con sockets a nivel nativo.
IdSMTP: Para enviar correo.
IdSNTP: Para sincronizar la hora.
IdTime: Conecta a un servidor de tiempo.
IdTrivialFTP: Cliente TFTP.
IdWhoIs: Informaci n sobre dominios (Internic).
IdTelnet: Para realizar apliaciones Telnet.

Como se puede contemplar, la mayor a de los componentes


reflejan lo que hacen por su nombre. Casi todos heredan de
los dos componentes b sicos que son IdTCPClient y
IdUDPClient. Cada uno implementa un protocolo
establecido en alg n RFC (en la mayor a de ellos, el RFC
se indica en el c digo
fuente). Como nos
vamos a dedicar a la
parte de clientes, la
pesta a de servidores
la veremos un poco ms
tarde. En cuanto a los
4
Figura 2.
InspectorObj.bmp. Propiedades del IdTCPClient
componentes que aparecen en Indy Miscel neo, los
iremos explicando a medida que vayan haciendo falta.
Antes de seguir, quiero resaltar que a diferencia de
Winshoes 7, los componenetes HTTP y FTP son nativos
y ya no se basan en la librera WinInet.dll.

IdTCPClient
Indy proporciona el componente IdTCPClient para
trabajar con sockets utilizando el protocolo TCP. En la
figura 2 se puede ver las propiedades de ste.

El trabajo sucio que consiste en inicializar la estructura


de un socket, etc, lo hace Indy detrs de las escenas. Para
trabajar con TCP, lo nico que tenemos que hacer es
asignar un valor a la propiedad Host y otro al puerto. El
primero puede ser tanto una direcci n IP como un
nombre. A parte de estas dos propiedades fundamentales,
hay otras como puede ser Intercept (e InterceptEnabled)
que sirve para interceptar (como su nombre indica) la
comunicacin. Se puede utilizar estas propiedades para
realizar comunicaciones seguras (SSL) o combinarlo con
otros componentes para realizar una traza de la actividad.
Adems se dispone de la propiedad SocksInfo que nos
permite utilizar Socks. Todo esto se ver ms adelante.
Aunque en este nmero solo vamos a ver lo que son
clientes, se incluye un programa servidor que nos sirve
realizar pruebas y mostrar el funcionamiento de un
cliente TCP sencillo. El servidor acta como un servicio
echo sencillo, es decir, devuelve los mismos caracteres
introducidos. Para ejecutarlo, compile y ejectelo fuera
de la IDE. Por ahora, deje el campo Retardo en 0. Ms
tarde veremos como utilizar esto.
Para el cliente, ponemos un TEdit, un TMemo y un
TIdTCPClient sobre el formulario, y asignamos los
siguientes eventos:

procedure TfmCliente1.edTextKeyPress(Sender:
TObject; var Key: Char);
begin
if Key = #13 then
begin
IdTCPClient1.WriteLn(edText.Text);
edText.Text := '';
meReply.Lines.Add(IdTCPClient1.ReadLn);
end;
end;

procedure TfmCliente1.FormActivate(Sender:
TObject);
begin
IdTCPClient1.Host := 127.0.0.1;
IdTCPClient1.Port := 9000;
IdTCPClient1.Connect;
end;

procedure TfmCliente1.FormClose(Sender: TObject;


var Action: TCloseAction);
begin
IdTCPClient1.Disconnect;
end;

Como se puede observar, se activa el cliente en el


OnFormActivate estableciendo los valores de Host
(127.0.0.1) y Port (que en nuestro caso el servidor esta
escuchando en el puerto 9000) y se desactiva en el
OnFormClose. En el evento OnKeyPress del TEdit, se
comprueba que es la tecla ENTER y en caso afirmativo,
se escribe el valor del TEdit al socket utilizando el
mtodo WriteLn. Este mtodo escribe una lnea al socket.
A continuacin, se lee una lnea del socket utilizando el
mtodo similar ReadLn. Ahora solo hace falta compilar
el proyecto y ejecutarlo. Al introducir texto y pulsar
ENTER el efecto es inmediato, lo escrito aparece en el
TMemo. Esto se debe a que el servidor y el cliente estn
en la misma mquina y no se aprecia los efectos que
puede tener la red. Para mostrar el efecto bloqueante de
Indy, vamos a asignar un Retardo al servidor. Lo que
hace esto es simular los retardos que pueden existir en la
comunicacin (mediante un Sleep). Para ello, ejecute el
servidor y introduzca un valor significativo (por ejemplo
10000) en el campo Retardo.
Ahora ejecute de nuevo el cliente. Nota algo? Intente
mover el formulario despus de mandar informacin.
No se puede, verdad? Esto se debe a la llamada
IdTCPClient1.ReadLn. Lo que hace esta llamada es
esperar una contestacin del servidor y como efecto
lateral, causa el congelamiento del UI. Esto es uno de los
5
efectos de sockets bloqueantes. Pero como se dijo el mes
pasado, hay formas de remediar esto, y a continuacin
vamos a ver algunas.

Impidiendo el bloqueo del GUI
En la pestaa Indy Misc hay un componente llamado
TidAntiFreeze. Significa anti-congelante (lo que se utiliza
en los coches) y bsicamente lo que hace es internamente
llamar a Application.ProcessMessages. No entraremos en
los detalles de cmo se implementa el componente ahora
(el curioso puede ver los cdigos fuentes). Lo nico que
hay que saber en este momento es que funciona en
armona con los otros componentes de Indy.
La solucin ms fcil y rpida para prevenir el bloque del
UI es poner un TIdAntiFreeze en el formulario sin ms. El
segundo ejemplo muestra el resultado de esto. Ejecute el
servidor con el mismo retardo que antes y ahora intente
mover el formulario. Ha visto que fcil es? El
componente tiene una serie de propiedades que en
principio no hace falta modificar para el correcto
funcionamiento. Pero si quiere, puede modificarlos y ver
los resultados que se producen. Entre ellos se encuentra
ApplicationHasPriority que indica si la comunicacin o la
aplicacin tiene prioridad y IdleTimeOut que especifica
el tiempo que se debe esperar en cada llamada para ver si
hay datos.
La segunda forma de evitar el bloqueo es un poco ms
compleja. Se trata de utilizar hebras para realizar parte de
la comunicacin en segundo plano. En concreto, vamos a
utilizar una hebra para leer del socket y mostrar el
resultado en el TMemo. Para ello, creamos un
descendiente del TThread, llamado TReaderThread.
Supongo que el lector tiene conocimientos bsicos del uso
de Threads en una aplicacin.

TReaderThread = class(TThread)
private
Client: TIdTCPClient;
Msg : String;
procedure UpdateMemo;
protected
procedure Execute; override;
end;

Declaramos el mtodo Execute y hacemos un override:

procedure TReaderThread.Execute;
begin
while not Terminated do
begin
Msg := Client.ReadLn;
Synchronize(UpdateMemo);
end;
end;

Para introducir la informacin en el TMemo, llamamos al
procedimiento UpdateMemo mediante Synchronize, que
lo nico que hace es aadir una lnea al componente
TMemo del formulario principal. A su vez, en el evento
OnActivate del formulario, creamos el TReaderThread y
establecemos sus propiedades:

Reader := TReaderThread.Create(True);
with Reader do
begin
FreeOnTerminate := True;
Client := Self.IdTCPClient1;
Resume;
end;

Ahora, desde la hebra principal, lo nico que hacemos es


un WriteLn con el texto introducido. El TReaderThread es
la que se encarga de leer el resultado del socket. De esta
forma evitamos que la hebra principal se quede bloquedo
a la recepcin de datos. Como se puede contemplar en los
ejemplos anteriores, utilizar el TIdAntiFreeze es bastante
ms sencillo que utilizar hebras, y en la mayora de los
casos no es necesario el uso de hebras. Tambin es una
cuestin de gusto personal.
El mes que viene, veremos los clientes a un nivel ms
alto. Comenzaremos a desarrollar un cliente de correo
electrnico que servir tanto para enviar como para recibir
correo.

Anda mungkin juga menyukai