Sockets en Delphi

Introducción a los Sockets en Delphi. Enrique González Alonso-Buenaposada Introducción. Hace poco, una persona que q

Views 71 Downloads 5 File size 56KB

Report DMCA / Copyright

DOWNLOAD FILE

Recommend stories

Citation preview

Introducción a los Sockets en Delphi.

Enrique González Alonso-Buenaposada

Introducción.

Hace poco, una persona que quería programar un desbordamiento de buffer me mandó un mail preguntándome (indirectamente y con muchos rodeos y engaños) cómo hacerlo. Casi me muero de la risa al leer su código. Una vez corregido, se lo envié de vuelta, y entonces me dice que no puede hacerse, que los sockets en Delphi están mal programados. Lo que tiene uno que oír a estas alturas de la vida. Intrigado, me puse a investigar un poco mas, constatando que todo funciona bien. De paso escribí esta pequeña guía.

Este artículo no está escrito para gente que no tenga una base programando, bien en Delphi, bien en otros lenguajes, sino para aquellas personas que ya sepan programar y quieran enterarse de una forma clara de como establecer una conexión usando Delphi. Espero que sirva de ayuda, porque para eso lo escribí.

Enrique González Alonso-Buenaposada. [email protected]

Índice: 1. Sockets. 2. Cliente, servidor y puertos que intervienen. 3. Crear un socket en Delphi.

3.1. Crear un Socket servidor. 3.2 Crear un socket cliente. 4. Conectar y desconectar los sockets. 4.1. Activar el servidor. 4.2. Conectar el cliente. 4.3. Comprobar si ya estamos conectados. 4.4. La propiedad “Active”. 5. Obtener información de la conexión. 6. Enviar y recibir información a través de los sockets. 6.1. Conexiones “Blocking” y “Non-Blocking”. 6.2. Enviar y recibir datos. 6.3. Consejos para programar una aplicación. 7. El programa de demostración. 8. Para saber más.

1. Sockets.

Es muy común escuchar frases como “En TCP-IP, todo funciona con sockets.”. ¡Menuda frasecita!. Si buscamos la palabra Socket en un diccionario de inglés nos dirá que es un agujero o abertura al que se puede enchufar algo(Como el zócalo en el que enchufamos el procesador a la placa base). Esta es una traducción hecha a lo bestia, pero nos bastará, porque un socket es una especie de enchufe.

La palabra con la que yo lo designaría es conector. El Socket en todo caso, puede considerarse como el extremo de una conexión. Una vez establecida la conexión entre un cliente y un servidor, usamos los sockets para enviar o recibir datos indistintamente. Este asunto no necesita mas explicación de momento, los detalles se verán mas adelante.

2. Cliente, servidor y puertos que intervienen.

Lo primero que hay que explicar es cómo se establece una conexión en TCPIP. Aunque parezca que no viene mucho al caso, mas adelante quedará claro el porqué. Lejos de mi intención explicar todo el proceso de la transmisión pero creo que es importante explicar como se establece la conexión.

Supongamos que tenemos montada una intranet con TCP/IP y supongamos que nuestro ordenador cliente (IP 192.168.3.127) quiere establecer una conexión con un servidor de la misma intranet (IP 192.168.3.100) a través de HTTP (Generalmente puerto 80). Cliente

Servidor

Puertos Cliente: 1024-65536

Puertos Cliente: 1024-65536

Puerto X

SYN SYN/ACK ACK

Puertos Servicio 0-1023

Puertos Servicio 0-1023 Puerto 80

Lo primero que hará el ordenador cliente será enviar un paquete de información, conocido como SYN, en el que se envían datos tales como su propia dirección IP y el puerto de origen al servidor. Los puertos del cliente los asigna automáticamente el sistema operativo(En adelante S.O.) del mismo, y estarán en el rango que hay entre 210(1024) y 216(65536). La petición va dirigida a un puerto concreto del servidor, en este caso el 80(HTTP).

El servidor, naturalmente, está “escuchando” por el puerto 80. Y cuando recibe el paquete SYN, envía de vuelta un paquete llamado SYN/ACK, que podrían explicarse en realidad como dos paquetes. En primer lugar explicaremos ACK, que es la abreviatura de ACKNOWLEDGED, con la que informa al cliente que se ha enterado del paquete SYN que ha recibido. A la vez, envía un paquete SYN como el que el cliente ha enviado. Este paquete SYN/ACK se envía a la dirección IP y puerto de la que provenía el primer paquete SYN, por el que se supone estará escuchando el cliente. Hay que hacer notar, que ese puerto estaría normalmente cerrado, pero que el S.O. del cliente lo abre para hacer la petición.

Finalmente el cliente envía al servidor un paquete ACK al servidor, indicándole que ha recibido a su vez el paquete SYN/ACK. Si todo este ciclo se completa, la conexión ha quedado establecida en los dos sentidos. Esto es importante, ya que en una conexión se puede leer y escribir a la vez de forma asíncrona.

Conclusión: En este caso hipotético, quedan fijadas las IPs y los puertos. La IP del cliente a 192.168.3.127, la del servidor a 192.168.3.100. El puerto cliente(en el idem) será variable asignándolo el S.O. en su momento, pero quedando fijo mientras dure la conexión. El puerto de servicio(en el servidor) será fijo. El puerto de servicio estará activo solo mientras dure la conexión, mientras que el del servidor permanecerá a la escucha atento a otras peticiones (bien desde la misma máquina, o desde otras).

3. Crear un socket en Delphi.

Como Delphi es un lenguaje orientado a objetos, todas las operaciones de sockets se realizarán recurriendo a una clase, TclientWinSocket, que es un puerto a las Dll’s que usa el S.O. para establecer conexiones. Sin embargo para mayor comodidad, existen las clases TclientSocket y TserverSocket, que descienden de ella,

y nos permiten implementar en cada caso, una conexión cliente o servidor. Ambas pueden encontrarse en la paleta de componentes, en la pestaña Internet.

Para incluir un objeto de una de estas clases en nuestro programa, bastará con incluir uno de estos componentes.

Atención: Hay que tener muy claro que clase de socket queremos crear, bién cliente, bién servidor. Aunque desde ambos se puede enviar y recibir, es necesario la existencia del primero para que funcione el segundo. Si no tienes claro que es un cliente y que es un servidor, vuelve a leerte el punto anterior (2) de este artículo.

3.1. Crear un Socket servidor.

El dato mas importante, en el caso del socket servidor es el del puerto, ya que una vez activado el mismo, este quedará “Escuchando”, de forma que le introducimos el puerto deseado en el object inspector, o directamente:

ServerSocket1.Port:=80;

// Por ejemplo.

Atención: Hay que tener en cuenta que si nuestro ordenador tiene otros servidores corriendo, estos pueden estar ocupando ya los puertos que queremos usar. Por ejemplo, si estuviese funcionando un servidor apache (HTTP), con casi toda probabilidad el puerto 80 estaría ocupado (Puede configurarse para otro puerto, pero no es habitual). Los programas peer-to-peer y las webcam también suelen reservar puertos específicos, ya que establecen conexiones en modo servidor.

Los puertos utilizados (normalmente) por los servidores más comunes son:

Puerto

Servicio

ftp

21

Ssh

22

Telnet

23

Smtp

25

Dns

42

Http

80

Pop3

110

Irc

194

Imap

220

Https

443

Imaps

993

Pop3s

995

Aunque esto puede variar ya que muchos de estos servidores se pueden configurar para utilizar otros puertos distintos. Además, existen muchos más puertos utilizados por otros servicios. La lista completa de los puertos utilizados por los distintos servicios puede encontrarse en:

http://www.iana.org/assignments/port-numbers

No obstante, si lo que tenemos es un windows “doméstico” (95-98-ME-XP), no habrá que complicarse demasiado la existencia, bastando con que el puerto de servicio sea un número mayor que 20.

3.2 Crear un socket cliente.

Para este, el dato mas importante es la IP a la que queremos conectar, junto al puerto del servidor, que esperamos que esté escuchando. La IP se la podemos dar en el object inspector, aunque es mejor hacerlo en tiempo de ejecución, ya que un cliente debería conectarse en tiempo de ejecución. Lo mismo puede decirse del puerto.

ClientSocket1.Address:= ‘192.168.3.127’; ClientSocket1.Port:=80;

// Por ejemplo

// Por ejemplo.

4. Conectar y desconectar los sockets.

Bién, hasta ahora hemos visto que para crear una aplicación que tenga un socket cliente o servidor bastará con incluirlo desde la paleta de componentes. También hemos visto como darle los valores necesarios. Todo ello es bastante simple si se tiene claro lo que se está haciendo. Crear la conexión es sencillo también.

4.1. Activar el servidor.

Para activar el servidor, bastará con usar el procedimiento open:

ServerSocket1.port:=80; ServerSocket1.Open;

// Damos el puerto. // Conectamos.

De esta forma el puerto que hayamos elegido para la conexión quedará “Escuchando” en espera de una conexión. Para desactivarlo usaremos el procedimiento close.

ServerSocket1.close;

4.2. Conectar el cliente.

Al igual que ocurre con el servidor para conectar y desconectar el cliente usaremos los procedimientos Open y Close: ClientSocket1.Address:= ‘192.168.3.127’; ClientSocket1.Port:=80;

// Le damos una IP

// Especificamos el puerto al que debe // conectarse.

ClientSocket1.Open;

// Abrimos la conexión.

: : : ClientSocket1.Close;

// Cerramos la conexión.

Si el servidor no estuviese escuchando, o no nos permitiese la conexión, recibiríamos el error de socket correspondiente.

4.3. Comprobar si ya estamos conectados.

Para comprobar si ya estamos conectados, bastará con consultar el flag “Connected” del tipo boolean, que estará en TRUE si la conexión ya está establecida, y a False de no ser así. Esto nos puede evitar, por ejemplo, conectar el cliente si ya está conectado:

If not ClientSocket1.Socket.Connected then Clientsocket1.Open;

4.4. La propiedad “Active”.

Otra forma abrir la conexión es que nuestro socket esté activo desde que arranca la aplicación. Para ello usaremos la propiedad “Active”, que encontramos en el Object Inspector y que normalmente está puesta a FALSE.

Si nuestro socket es servidor ACTIVE=TRUE hará que esté escuchando desde el inicio de la aplicación. Si es cliente, intentará establecer la conexión desde el inicio.

Otra cosa que podemos hacer es con active es cerrar las conexiones. Si ponemos (en tiempo de ejecución) ACTIVE=FALSE la conexión abierta se cerrará. Si lo hacemos sobre un socket servidor, además, cerrará todas las conexiones activas (Puede tener varias), haciendo que deje de escuchar por el puerto que le habíamos asignado.

5. Obtener información de la conexión.

Una vez la conexión está establecida, podemos usar las propiedades LocalHost y LocalPort para obtener la dirección IP y el puerto usados por ambos, cliente y servidor. También se puede usar la propiedad Handle para obtener un controlador directo a la conexión.

6. Enviar y recibir información a través de los sockets.

Esta es la parte mas complicada (a mi entender) de todo el asunto. Lo es porque la gama de posibilidades es enorme, y todas ellas son correctas para un uso concreto. Explicarlas todas escapa a la intención de este artículo, de forma que intentaré explicar las principales por encima. No obstante recomiendo al lector que busque mas información y experimente, dependiendo de sus necesidades.

6.1. Conexiones “Blocking” y “Non-Blocking”.

Como quedó dicho anteriormente al final del capítulo 2, la lectura y escritura pueden funcionar de forma independiente una de la otra.

Esto es posible en las

conexiones “non-blocking”. En las conexiones “Blocking”, nos encontramos que hasta

que no haya terminado una operación (Bién de lectura o escritura) no se puede realizar la siguiente.

La propiedad que determina que tipo de conexión estamos usando, en cada caso, se llama ClientType para un socket cliente y ServerType para un servidor. Esta propiedad la podemos encontrar en el Object Inspector.

6.2. Enviar y recibir datos.

Lo primero que hay que tener en cuenta es el tipo de información que se quiere enviar o recibir. Si vamos a leer algo realmente grande, podemos usar métodos como SendBuf/ReceiveBuf o SendStream/ReceiveStream. Como no es mi intención explicar como funcionan los buffers y streams, no explicaré mas estos métodos. El lector que sepa como funcionan estas formas de trabajar con datos no tendrá dificultad en usar con estos métodos una vez haya concluido este capítulo.

Readln/SendLn es otra forma de enviar datos. Se envían y se reciben datos hasta que aparece un carácter especificado como parámetro, aunque personalmente prefiero SendText/ReceiveText.

ClientSocket1.Socket.SendText (st);

SendText/ReceiveText se puede enviar el contenido de una variable del tipo String. Como las variables de este tipo tienen un tamaño dinámico, se pueden usar para enviar casi cualquier cosa. En una variable de este tipo podemos cargar un archivo de cualquier tipo, que quedará idéntico al grabarlo. Mediante este procedimiento he llegado a enviar y recibir un archivo de 40 mb sin problemas (Se cada vez mas lento a partir de los 4 mb, pero en general funcionaba bien.). No obstante, para cosas grandes, recomiendo evitar las chapuzas y emplear variables del tipo stream, con las que podemos enviar directamente desde un archivo.

Recibir es un poco mas complejo, pero solamente un poco. Como nuestro socket queda establecido, tenemos que esperar a un evento del tipo ClientRead. Crearemos el procedimiento asociado con el Object Inspector, que tendra el siguiente aspecto:

procedure TForm1.ClientSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket); var st:string; begin st:=Socket.ReceiveText;

// Ojo, tener en cuenta que se recibe

:

// de Socket.ReceiveText

:

// no de ClientSocket1.socket.ReceiveText

:

// la razón hay que buscarla en el

:

// encabezado del procedimiento

end;

6.3. Consejos para programar una aplicación.

Como se ha visto, el socket es el extremo de una conexión, a través del cual se pueden enviar y recibir datos. Esto tiene varias aplicaciones, no solamente enviar datos a través de la red, sino por ejemplo el envío de datos entre dos aplicaciones que estén funcionando en el mismo ordenador.

Es fundamental que haya un servidor adecuado funcionando para probar una aplicación cliente. Esto plantea un problema. ¿Cómo programar, por ejemplo, un cliente apache si solamente disponemos de un ordenador? La respuesta es sencilla, usaremos la IP de intranet de nuestro propio ordenador. Ponemos el servidor a funcionar en nuestra máquina mientras se programa la aplicación.

¿Qué no tenemos una intranet montada? No hay problema, podemos hacer lo mismo, empleando la IP de loopback que tenga la máquina, normalmente 127.0.0.1. Con esta IP, normalmente estaremos accediendo a nuestro mismo ordenador.

Otra cosa a tener en cuenta, es que a la hora de programar un servidor deberíamos evitar que haya varias instancias del mismo funcionando. Esto puede hacerse de varias formas. También es muy importante no dejar los sockets abiertos cuando la aplicación finalice.

7. El programa de demostración.

El programa que se incluye con este artículo pone en marcha un socket cliente y uno servidor.

Con el cliente se envía información (texto), mientras que con el

servidor se recibe. Al incluir los dos sockets, el programa puede intercambiar texto con otro programa en otro ordenador.

Lo he probado tanto con la IP 127.0.0.1, como con la dirección de la misma máquina en la intranet. En ambos casos, lo escrito en el memorando de la izquierda se transmite al de la derecha. También está probado desde varias direcciones de una intranet, pudiendo intercambiar texto con otros ordenadores. Para hacerlo así, el servidor al que vamos a conectar debe estar encendido (Una copia del programa corriendo). Escribimos la IP de nuestro objetivo, pulsamos el botón “Conectar” y ya está listo para enviar texto.

El programa evita que se ejecuten varias instancias del mismo recurriendo a un semáforo (Ver archivo dpr).

El socket servidor arranca con el evento “OnCreate” del formulario principal, mientras que el cliente (el que envía texto) solamente envía una vez se haya escrito una IP (Correcta) y se pulse el botón conectar.

El programa se pensó originalmente para intercambiar texto entre dos ordenadores, pero el funcionamiento del mismo permite interconexiones extrañas, como por ejemplo:

A B

D

C

E

En donde el ordenador A no recibe texto de nadie y envía a B. B recibe de A, y envía a C. D y E intercambian entre ellos, pero E recibe también de C.

Por último debe observarse que se usa el evento “OnClose” del formulario principal para cerrar los sockets. No explicaré mas la aplicación ya que después de estas explicaciones, bastará con echarle un vistazo al mismo para comprender que hace y como funciona.

8. Para saber mas.

En este artículo se puede leer una introducción al uso de sockets en Delphi, pero que nadie piense que está todo dicho. En el no está ni la mitad de lo que yo mismo sé, y por supuesto hay personas que saben mucho mas del tema que yo. Lo que hay aquí escrito, como el propio título del artículo sugiere, no es mas que una breve introducción a este tema.

Si alguien quiere saber mas, le recomiendo que empiece por la propia ayuda del Borland Delphi, pero que no pare ahí. Para continuar, podemos echar un buen vistazo al código fuente de algunos componentes incluidos en el propio Delphi, como los INDY components, que son un buen ejemplo de cómo implementar los protocolos mas comunes usando sockets. El código fuente de algunos programas peer-to-peer puede ser otro buen sitio para aprender, si a uno no le importa ver código escrito para otros compiladores. Todos ellos trabajan con las mismas dll’s de Windows.

Y por supuesto existen toneladas de literatura al respecto. Desde artículos como este hasta libros dedicados al tema. En todos ellos se hace un estudio mas detallado sobre los temas tratados en este artículo y de otros que se han quedado en el tintero.