- ¿Qué es
cliente/servidor? - Preparando el Visual
Basic - Descripción del componente
Winsock - Mi primera aplicación
cliente - Mi primera
aplicación servidor - Aplicación servidor
multi-conexión - Conclusión
Este tutorial pretende explicar en forma práctica
la implementación y desarrollo de
aplicaciones cliente y servidor, ya sea mono-conexión o
multi-conexiones, usando el componente Winsock Control 6.0 sobre
el entorno de desarrollo de VisualBasic v6.0. Cualquier otra
versión de este control debería funcionar
también.
Para el seguimiento de este texto, se
recomienda ir practicando las cosas mencionadas a la vez que las
vallas leyendo, así podrás familiarizarte de mejor
manera con el entorno y no te perderás por el
camino.
Se da por entendido que el lector conoce el lenguaje
Visual Basic y
este familiarizado con su sintaxis y metodología de programación.
Imagino que esta es la primera pregunta que se
harán todos, ¿que es eso de cliente servidor?,
cliente/servidor no es más que la forma de llamar a las
aplicaciones que trabajan en conjunto como "nodos" de información (por así decirlo). Esto
es que existe una aplicación totalmente independiente de
la parte cliente la cual esta dispuesta a servir
información cuando el cliente se la
solicita.
Ejemplos de estos pueden ser los servidores de
Paginas Webs (HTTP), servidores
de Transferencia de Archivos
(FTP),
etc.
No podemos continuar sin antes dar una breve
definición de los términos Cliente y
Servidor.
Cliente: Es toda aplicación que
se conecta a un Servidor para solicitarle alguna
información.
Servidor: Es toda aplicación que se
mantiene a la espera de un cliente que solicite
información, la cual se la entregara si fuese posible. Se
dice que este ofrece o sirve un servicio.
Para que quede mas claro, voy a dar un ejemplo sobre el
funcionamiento del servidor de Paginas Webs (HTTP). Para ello
realizaremos una visita a un sitio Web en particular
y analizaremos después lo sucedido:
- Ejecutamos nuestro navegador (Internet
Explorer, Netscape, Firefox, etc.) - Ingresamos la dirección del sitio Web que deseamos ver,
por ej. www.google.com - Le damos al botón "Ir", "Ver", etc. Para que
nuestro navegador se conecte a la dirección. - Nuestro navegador inmediatamente comienza a recibir
poco a poco la página
Web solicitada. - Una vez concluida la descarga nuestro navegador se
desconecta del sitio de forma oculta al usuario.
Ahora si analizamos los pasos que fueron necesarios para
visitar un sitio Web, veremos que lo primero que tenemos que
hacer es ejecutar un programa específico el cual
tiene la habilidad de conectarse a una dirección Web. Este
programa que
se conecta a X dirección, lo llamaremos Cliente,
porque es el que solicita la información, en este caso
solicita una pagina Web. Y quien le entrega dicha
información al cliente se llamara Servidor, que en
este caso es una aplicación que corre bajo la
dirección del sitio en un computador
remoto conectado a Internet (www.google.com).
Como conclusión, si analizamos la petición
de la página Web podemos obtener que los elementos
básicos que necesitamos para una conexión
cliente/servidor son:
- Un programa Cliente (el que solicita la
información) - Un programa Servidor (el que sirve la
información que necesitamos) - Una dirección hacia el servidor (para poder saber
a donde conectar)
Con estos tres elementos podemos realizar una
conexión cliente/servidor sin problemas y es
la base de las aplicaciones.
También es muy importante mencionar, que en lo
que a "dirección" se refiere, esta formada por un
número IP (o DNS) y un
número de PUERTO. Este último es así porque
un computador puede tener muchos puertos destinados para ofrecer
distintos servicios, ya
sean Paginas Webs (Puerto 80), Mails (Puerto 25 y 110), FTP
(puerto 21), Telnet (23),
etc.
Estos puertos que he mencionado son los acostumbrados
para estos servicios, eso no quiere decir que tenga que ser
siempre así, por ej. Podemos usar el puerto 80
(Comúnmente para HTTP) para ofrecer un servicio FTP, o
bien implementar un Chat o
cualquier cosa que se nos ocurra. Los puertos solo están
disponibles para cualquier uso que le queramos dar.
Como referencia es bueno saber cuantos puertos puede
usar una computadora.
Los puertos están direccionados por 16 Bits, esto es que
existe un total de 2^16 puertos, lo que equivale a 65.536 puertos
disponibles, aunque como el puerto 0 no se puede usar solo
tenemos utilizables desde el puerto 1 al puerto
65.535.
El Visual Basic v6.0 por defecto no esta preparado para
trabajar con aplicaciones cliente/servidor, y hace falta acomodar
algunas cosas antes de comenzar a trabajar.
- Lo primero será crear un nuevo proyecto. Elige
Aplicación Estándar (prácticamente puede
ser cualquier otra, pero en este caso se trabajara
así) - Ve al menú "Proyecto" y selecciona
"Componentes". - En la lista de componentes busca "Microsoft Winsock Control 6.0", puede ser
otra versión o bien terminar con
"(SP6)". - Marca este control y dale al botón
"Aceptar".
- Ve al menú "Proyecto" y selecciona
- Ahora necesitaremos cargar el Control Winsock, para
ello realiza lo sig.:
Con esto veremos que se nos agrega un nuevo control
llamado Winsock, con el icono .
Ahora ya nos encontramos listos para realizar una
aplicación Cliente/Servidor.
DESCRIPCION DEL
COMPONENTE WINSOCK
El componente Winsock del Visual Basic es el que permite
realizar conexiones Cliente/Servidor a través de protocolos TCP y
UDP. Este único componente puede trabajar de dos formas,
como Cliente (Conecta a un servidor) y como Servidor (Recibe
conexiones), además de poder realizar vectores de
Winsock lo que permite administrar varias conexiones con un mismo
código
en común.
Este componente depende directamente del control ActiveX
MSWINSCK.OCX.
A continuación paso a describir las principales
propiedades, métodos y
eventos del
componente.
Propiedades
Property BytesReceived As Long
(Solo lectura)
Retorna el número de Bytes
recibidos en la conexión.
Property Index As Integer (Solo
lectura)
Retorna/Asigna el numero que identifica
al control en un arreglo de controles.
Property LocalHostName As String
(Solo lectura)
Retorna el nombre de la maquina
local.
Property LocalIP As String (Solo
lectura)
Retorna la dirección IP de la
maquina local.
Property LocalPort As Long (Solo
lectura)
Retorna el puerto usado en la maquina
local.
Property Protocol As
ProtocolConstants
Retorna/Asigna el tipo de protocolo que
usara el Socket
Estos valores pueden
ser dos: sckTCPProtocol y sckUDPProtocol.
Property RemoteHost As
String
Retorna/Asigna el nombre
(dirección) usado para identificar a la maquina
remota.
Property RemoteHostIP As String
(Solo lectura)
Retorna la dirección IP del Host
Remoto.
Property RemotePort As
Long
Retorna/Asigna el puerto al cual se
conectara en el computador remoto.
Property SocketHandle As Long
(Solo lectura)
Retorna el manejador (Handle) del
Socket. (Solo para usuarios avanzados)
Property State As Integer (Solo
lectura)
Retorna el estado de
la conexion del Socket.
Metodos
Sub Accept(requestID As
Long)
Acepta un petición de
conexión entrante.
Sub Bind([LocalPort],
[LocalIP])
Amarra un socket a un específico
puerto y adaptador.
Sub Close()
Cierra la conexión
actual.
Sub Connect([RemoteHost],
[RemotePort])
Conecta a un computador
remoto.
Sub GetData(data, [type],
[maxLen])
Recibe datos enviados
por el computador remoto.
Sub Listen()
Se pone a la escucha de peticiones de
conexión entrantes.
Sub PeekData(data, [type],
[maxLen])
Mira los datos entrantes sin removerlos
desde el buffer.
Sub SendData(data)
Envía datos al computador
remoto.
Eventos
Event Close()
Ocurre cuando la conexión a sido cerrada
remotamente.
Event Connect()
Ocurre cuando la operación de conexión se
ha completo.
Event ConnectionRequest(requestID
As Long)
Ocurre cuando un cliente remoto se intenta
conectar.
Event DataArrival(bytesTotal As
Long)
Ocurre cuando se reciben datos desde un computador
remoto.
Event Error(Number As Integer,
Description As String, Scode As Long, Source As String, HelpFile
As String, HelpContext As Long, CancelDisplay As
Boolean)
Se dispara cuando ocurre algún error.
Event SendComplete()
Ocurre después que una operación de envio
se haya completado.
Event SendProgress(bytesSent As
Long, bytesRemaining As Long)
Ocurre mientras se esta enviando un dato.
Constantes
Enum StateConstants
Estas son las constantes devueltas por la propiedad
State.
Constante | Descripción |
sckClosed | El socket se encuentra cerrado. |
sckClosing | El socket esta cerrando la conexión al |
sckConnected | El socket ha conectado al computador |
sckConnecting | El socket esta conectando al computador |
sckConnectionPending | El socket tiene una petición |
sckError | Se ha producido un error |
sckHostResolved | El socket ha resuelto el nombre del equipo |
sckListening | El socket esta a la escucha de peticiones |
sckOpen | El socket esta actualmente abierto. |
sckResolvingHost | El socket esta resolviendo el nombre del |
Enum ProtocolConstants
Estos son los valores
permitidos por la propiedad Protocol.
Constante | Descripción |
sckTCPProtocol | Protocolo TCP. |
sckUDPProtocol | Protocolo UDP. |
Enum ErrorConstants
Constantes de errores, devueltas por el evento
Error.
Constante | Descripción |
sckAddressInUse | Dirección en uso. |
sckAddressNotAvailable | La dirección no esta disponible desde la |
sckAlreadyComplete | La operación esta completa. En progreso |
sckAlreadyConnected | El socket esta actualmente conectado. |
sckBadState | Protocolo o estado |
sckConnectAborted | La conexión es abortada debido a que se |
sckConnectionRefused | La conexión a sido rechazada. |
sckConnectionReset | La conexión a sido reinicializada por el |
sckGetNotSupported | La propiedad es de Escritura solamente. |
sckHostNotFound | Respuesta autorizada. No se ha encontrado el |
sckHostNotFoundTryAgain | Respuesta no autorizada. No se ha encontrado el |
sckInProgress | Una operación winsock de bloqueo esta en |
sckInvalidArg | El argumento pasado a la función no posee un formato o rango |
sckInvalidArgument | Argumento invalido |
sckInvalidOp | Operación invalida en el estado |
sckInvalidPropertyValue | Valor de propiedad invalido. |
sckMsgTooBig | El datagrama es muy largo para acomodarlo dentro |
sckNetReset | Ha finalizado el tiempo de conexión |
sckNetworkSubsystemFailed | Falla en el subsistema de red. |
sckNetworkUnreachable | La red no puede ser alcanzada desde este |
sckNoBufferSpace | No hay espacio disponible en el |
sckNoData | Nombre valido. No hay tipo de datos del registro demandado. |
sckNonRecoverableError | Error irrecuperable. |
sckNotConnected | El socket no esta conectado. |
sckNotInitialized | WinsockInit debe ser llamado primero. |
sckNotSocket | El descriptor no es un socket. |
sckOpCanceled | Esta operación es cancelada. |
sckOutOfMemory | La memoria se ha colapsado. |
sckOutOfRange | El argumento esta fuera de rango. |
sckPortNotSupported | El puerto especificado no esta |
sckSetNotSupported | La propiedad es de solo lectura. |
sckSocketShutdown | El socket había sido cerrado. |
sckSucces | Concluido. |
sckTimedout | Tiempo fuera en el intento de |
sckUnsupported | No soporta tipos Variants. |
sckWouldBlock | El socket no esta bloqueando y la especifica |
sckWrongProtocol | Protocolo equivocado para la especifica |
Esta aplicación trabajara como un cliente simple
que conecte a cualquier servidor, permita enviar texto plano y a
la vez mostrar la información devuelta por este. Parecido
a como trabajan los clientes de
Telnet.
1. Creando la interfaz del usuario
Realiza un formulario como el mostrado abajo, con los
nombres por defecto de cada control y guarda el proyecto con el
nombre "Cliente.vbp".
2. Implementando la conexión
La primera acción
a realizar y fundamental para toda aplicación de este
tipo, es crear la conexión al servidor, ya que solo se
puede transmitir información si la conexión
cliente/servidor se encuentra activa.
Propiedades necesarias
– RemoteHost: Asignamos la dirección a la que
deseamos conectar.
– RemotePort: Asignamos el puerto al que deseamos
conectar en RemoteHost.
Métodos necesarios
– Connect(): Conecta al servidor.
– Close(): Cierra la conexión al
servidor.
Eventos involucrados
– Connect(): Ocurre cuando hemos establecido con
éxito
la conexión al servidor
– Close(): Ocurre cuando el servidor nos cierra la
conexión.
– Error(): Ocurre en caso de errores.
Para realizar la conexión utilizamos el siguiente
código:
Private Sub 'asignamos los datos de Winsock1.RemoteHost = Text3.Text Winsock1.RemotePort = Text4.Text 'conectamos el socket Winsock1.Close Winsock1.Connect End Sub |
Aquí se pueden ver claramente dos partes
principales:
En las primeras dos líneas asignamos los datos de
conexión al host remoto, como son la IP/DNS (RemoteHost) y
Puerto (RemotePort).
En la última línea llamamos al método
"Connect" para realizar la conexión, siempre
asegurándonos que el Socket no este utilizándose.
Para ello llamamos al método "Close" que se encarga de
cerrar toda conexión pendiente en el Socket.
Nota: También se puede especificar los datos
de conexión (IP y Puerto) directamente en el comando
"Connect" como parámetros, de la sig. Forma:
Winsock1.Connect(Host, Puerto).
Si la conexión se realiza con éxito se
dispara un evento para tal fin, en donde podemos realizar
acciones
inmediatas en el momento preciso en que se logra establecer la
conexión con el servidor. El evento es el
siguiente:
Private Sub 'desplegamos un mensaje en la Text1.Text = Text1.Text & _ "*** Conexion establecida." & 'desplazamos el Text1.SelStart = Len(Text1.Text) End Sub |
En este caso solo nos limitamos a mostrar un mensaje en
pantalla especificando que la conexión se ha realizado con
éxito.
En este momento ya tenemos creado los lazos
básicos para realizar cualquier intercambio de datos con
el servidor, ya sea texto ASCII o datos
binarios.
También hay que tener presente que en cualquier
momento el servidor nos puede cerrar la conexión, o bien
cerrarse por algún error, para ello es que contamos con el
evento "Close", que se dispara al perder la conexión con
el servidor:
Private Sub 'cierra la conexion Winsock1.Close 'desplegamos un mensaje en la Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "*** Conexion Text1.SelStart = Len(Text1.Text) End Sub |
Aquí solo desplegamos un mensaje en la pantalla
informando del evento ocurrido, y cerrando previamente el Socket
para asegurarnos de que este actualice sus valores según
el estado actual.
En cambio si
queremos cerrar nosotros mismos la conexión con el
servidor basta con llamar al método "Close"
directamente:
Private Sub 'cierra la conexion Winsock1.Close 'desplegamos un mensaje en la Text1.Text = Text1.Text & _ "*** Conexion cerrada por el usuario." & 'desplazamos el scroll Text1.SelStart = Len(Text1.Text) End Sub |
3. Enviando/recibiendo datos
Una vez realizada con éxito nuestra
conexión, solo resta comenzar a transferir datos, cabe
mencionar que estos datos se envían siempre en forma
binaria aunque sea solo texto, ya que el texto en si es una
representación grafica de un numero binario, con esto
quiero expresar que a través de un socket puedes enviar
texto normal o datos binario, todos como variables de
tipo String (cadenas).
Métodos necesarios
– SendData: Envía datos al otro extremo de la
conexión (socket remoto).
– GetData: Recibe datos enviados por el extremo remoto
(socket remoto).
Eventos involucrados
– DataArrival(): Ocurre cuando el socket remoto nos esta
enviando datos.
– Error(): Ocurre en caso de errores.
Para enviar datos utilizamos el método "SendData"
de la sig. forma:
Private Sub 'enviamos el contenido de Winsock1.SendData Text2.Text & 'apuntamos al final del 'insertamos los nuevos datos Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "Cliente >" Text1.SelStart = Len(Text1.Text) 'borramos Text2 Text2.Text = "" End Sub |
Al método SendData solo se le pasa como
parámetro el dato a enviar (en este caso el contenido de
un TextBox + los caracteres de nueva línea y retorno de
carro) y este lo envía inmediatamente al socket
remoto.
Cuando el socket remoto nos envía un dato (de la
misma forma que realizamos anteriormente) se nos genera el evento
"DataArrival()" indicando que tenemos nueva información
disponible, y esta información la cogemos con el
método "GetData":
Private Sub Dim Buffer 'obtenemos los datos y los Winsock1.GetData Buffer 'apuntamos al final del 'insertamos los nuevos datos Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "Servidor >" Text1.SelStart = Len(Text1.Text) End Sub |
En este ejemplo solo obtenemos los datos y lo mostramos
inmediatamente en la ventana del cliente, no hacemos
ningún tratamiento previo de los datos, como sería
lo habitual.
4. Manejo de errores
Es muy importante tomar alguna acción cuando se
produzca algún error, aunque esta acción tan solo
sea cerrar la conexión e informar al usuario de lo
ocurrido.
Para el manejo de errores producidos durante la
conexión contamos con un evento dedicado, llamado
"Error()" el cual retorna varios valores para darnos
información al respecto, entre ellos los mas comunes
son:
Number As Integer | Informa sobre el numero del error |
Description As String | Entrega una breve descripción del error |
En caso de producirse algún error la
acción más simple de realizar es simplemente cerrar
la conexión con el método "Close":
Private Sub 'cerramos la conexion Winsock1.Close 'mostramos informacion sobre el MsgBox "Error numero " & Number & ": " End Sub |
5. Prueba de la aplicación
En este punto ya estamos listo para comenzar a usar
nuestro programa cliente, solo le damos a ejecutar desde el
entorno del Visual Basic o bien compilamos y ejecutamos el
archivo.
Para asegurarnos que todo ha salido bien, vamos a
realizar una pequeña prueba, conectaremos a www.google.com
y solicitaremos la página de inicio:
En el campo "Servidor" de nuestro programa escribimos
"www.google.com", y en el campo "Puerto" colocamos el "80". Le
damos al botón "Conectar".
Si todo va bien, deberíamos obtener el mensaje
"Conexión establecida", si es así entonces en el
campo de enviar texto, escribimos "GET / HTTP/1.1":
Y para enviar presionamos dos veces el botón
"Enviar". La razón de esto es para que envié la
cadena que escribimos mas dos caracteres de retorno de carro o
nueva línea (vbCrLf), esto por especificaciones del
protocolo HTTP (que es lo que estamos utilizando
aquí).
Deberíamos ver algo como esto:
Si recibimos texto desde el servidor (Las cadenas que
inician con "Servidor >") es que nuestro cliente funciona
perfectamente y hemos realizado la conexión, enviado y
recibido datos con éxito. Ya podemos descasar un rato y
celebrar :).
MI
PRIMERA APLICACIÓN SERVIDOR
Vamos a realizar una aplicación que se mantenga a
la escucha de una conexión entrante y la acepte,
podrá enviar y recibir datos desde el cliente.
Al principio será mono-conexión, es decir,
solo permitirá una conexión a la vez al servidor,
pero luego la implementaremos para múltiples
conexiones.
Algunas partes del código para el servidor son
idénticas al del cliente (realizado anteriormente)
así que solo me limitare a mostrar como queda el
código y la explicación de la misma se
entenderá que es la correspondiente a la del
cliente.
1. Creando la interfaz del usuario
Realiza un formulario como el mostrado abajo, con los
nombres por defecto de cada control y guarda el proyecto con el
nombre "Servidor.vbp".
2. Implementando la conexión
Al igual que en el cliente, lo primero es habilitar el
socket para que pueda quedar esperando una conexión, se
dice que queda "a la escucha de". Para esto solo necesitamos un
botón "Escuchar" y como datos un puerto local (a
elección) en el cual deseamos recibir conexiones
entrantes.
Propiedades necesarias
– LocalPort: Asignamos el puerto local en el cual
deseamos recibir conexiones.
Métodos necesarios
– Listen(): Escucha peticiones entrantes.
– Close(): Cierra la conexión al
servidor.
Eventos involucrados
– ConnectionRequest(): Ocurre cuando un cliente nos
solicita una conexión al servidor.
– Close(): Ocurre cuando el servidor nos cierra la
conexión.
– Error(): Ocurre en caso de errores.
El código utilizado para el botón
"Escuchar" es el siguiente:
Private Sub 'cerramos cualquier conexion Winsock1.Close 'asignamos el puerto local que Winsock1.LocalPort = Text3.Text 'deja el socket esuchando Winsock1.Listen 'desplegamos un mensaje en la Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "*** Esuchando Text1.SelStart = Len(Text1.Text) End Sub |
La primera línea de código cierra la
conexión actual, para luego poder modificar los datos y
crear una nueva conexión sin que nos de
errores.
La siguiente línea le dice en que puerto deseamos
recibir conexiones, y luego llama al socket para que quede a la
escucha de conexiones en ese puerto.
Hasta aquí el socket solo esta "escuchando"
conexiones, es decir aun nadie se puede conectar al servidor
completamente porque no se ha implementado esa parte por el
momento.
Esto solo nos permite avisarnos cada vez que un cliente
se quiera conectar o bien cada vez que un cliente "Solicita una
conexión entrante". Cuando este sucede se genera el evento
"ConnectionRequest()":
Private Sub 'mostramos un mensaje en la Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "*** Peticion Text1.SelStart = Len(Text1.Text) 'cerramos previamente el Winsock1.Close 'aceptamos la Winsock1.Accept requestID 'desplegamos un mensaje en la Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "*** Conexion Text1.SelStart = Len(Text1.Text) End Sub |
Con estas líneas ya estamos conectado
completamente, pero de seguro que quedan
muchas dudas, y precisamente este punto es uno de los mas
importantes, así que pasare a detallar cada
cosa.
Lo primero que habíamos realizado es dejar el
socket a la escucha de conexiones (para esto utilizamos el
método "Listen"). Con esto ya tenemos un puerto abierto y
atento a toda actividad.
Cuando un "Cliente" se intenta conectar a ese puerto, el
socket lo detectara y para ello generara el evento
"ConnectionRequest()" que significa "Petición de
conexión" y además le asigna una identidad a
esa "Petición" que identifica al "Cliente" remoto. Esta
identidad es pasada como parámetro en el evento
"ConnectionRequest()" con el nombre de "requestID" y es de tipo
"Long".
Cuando se genera el evento lo que tenemos que hacer es
"Aceptar" la conexión entrante "requestID" mediante el
metodo "Accept", si no lo hacemos al llegar al "End Sub" del
evento, la conexión del "Cliente" será cerrada
automáticamente.
Algo interesante es ver que antes de aceptar la
conexión con "Accept" primero cerramos la conexión
con "Close", esto que puede parecer ilógico no lo es,
porque el socket lo teníamos ocupado y activo "escuchando
conexiones", y ahora necesitamos que establezca una
conexión par a par con el cliente, por ello es que
cerramos la función de "Escuchar conexiones del socket" y
le decimos que acepte la conexión entrante y así
automáticamente se conecta en forma directa con el cliente
y ya no entenderá nuevas conexiones entrantes. (No puede
realizar dos funciones a la
vez)
Para cerrar la conexión basta con usar el
método "Close" en cualquier momento:
Private Sub 'cierra la conexion Winsock1.Close 'desplegamos un mensaje en la Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "*** Conexion Text1.SelStart = Len(Text1.Text) End Sub |
Private Sub 'cierra la conexion Winsock1.Close 'desplegamos un mensaje en la Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "*** Conexion Text1.SelStart = Len(Text1.Text) End Sub |
3. Enviando/recibiendo datos
Esto es idéntico al explicado en la parte del
cliente:
Private Sub 'enviamos el contenido de Winsock1.SendData Text2.Text & 'apuntamos al final del 'insertamos los nuevos datos Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "Servidor >" Text1.SelStart = Len(Text1.Text) 'borramos Text2 Text2.Text = "" End Sub |
Private Sub Dim Buffer 'obtenemos los datos y los Winsock1.GetData Buffer 'apuntamos al final del 'insertamos los nuevos datos Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "Cliente >" Text1.SelStart = Len(Text1.Text) End Sub |
4. Manejo de errores
Esto es idéntico al explicado en la parte del
cliente:
Private Sub 'cerramos la conexion Winsock1.Close 'mostramos informacion sobre el MsgBox "Error numero " & Number & ": " End Sub |
5. Prueba de la aplicación
Después de mucho "copiar/pegar" :), ya estamos
listos con nuestra aplicación servidor y listos para
realizar las primeras pruebas y ver
si todo trabaja correctamente.
Lo primero, ejecuta la aplicación servidor y
donde dice "Puerto" coloca cualquier número de los 65.535
disponibles, por ej. el "23". Luego dale al botón
"Escuchar":
Ahora ejecuta la aplicación Cliente y en
"Servidor" coloca "localhost" o "127.0.0.1" y en "Puerto" coloca
el "23". Dale al botón "Conectar".
En el servidor obtienes:
Ya estamos listos y trabajando con nuestra
aplicación Cliente/Servidor!!, ¿No lo crees?,
prueba a enviar texto entre cliente->servidor y
servidor->cliente y compruébalo tu mismo:
Ahora que sabemos que todo trabaja correctamente te
invito a hacer una prueba más. Con la conexión
establecida y funcionando de par a par ente cliente/servidor,
ejecuta una nueva aplicación "Cliente" e intenta conectar
al servidor en el mismo puerto (en este caso servidor "localhost"
y puerto "23"), y espera los resultados:
Nos dice que no logra establecer la conexión,
este es el mismo mensaje que entrega si el servidor al que
intenta conectar No tiene ningún puerto abierto!!,
lo que sucede es que el servidor ya no se encuentra "a la escucha
de conexiones" y por lo tanto no atenderá nuevas
peticiones de conexión.
APLICACIÓN SERVIDOR
MULTI-CONEXIÓN
Ahora nos encontramos con los conocimientos suficientes
para implementar un servidor que pueda aceptar un número
indefinido de conexiones entrantes.
En este proyecto usaremos el mismo código fuente
del servidor mono-conexión utilizado anteriormente, ya que
los cambios son muy pocos en realidad.
Bien, entonces abrimos el proyecto del servidor
mono-conexión y lo guardamos como "Servidor Multi.vbp"
para comenzar a trabajar.
La interfaz la dejaremos tal cual, todo el cambio
será en relación al código mismo y a la
modificación de algunos controles. También
trabajaremos con arreglo de controles, si nunca lo has hecho
podrías buscar un poco de información al respecto o
bien intentar seguir adelante, que lo explicare de forma
breve.
1. Vista general del funcionamiento
Como vimos anteriormente en el Servidor
mono-conexión, dejábamos un socket a la escucha de
conexiones entrantes, y al recibir una petición de
conexión (evento "ConnectionRequest") le decíamos
al Winsock que aceptara esa identidad y este a su vez
establecía una conexión con el cliente.
Los principios para
crear un servidor multi-conexión son los mismos, salvo que
necesitamos dejar un socket escuchando permanentemente conexiones
entrantes, este nunca se debe cerrar (al contrario de lo que
pasaba en el caso del servidor mono-conexión), entonces
¿como podemos aceptar una conexión si no podemos
cerrar el socket que tenemos a la escucha?, ¿acaso podemos
dejar un mismo socket escuchando conexiones y atendiendo otra a
la vez?, la respuesta es No podemos, pero nada nos impide
hacer que otro socket que se encuentra inactivo acepte y atienda
una petición de conexión. De esta forma el trabajo
total se reparte entre varios sockets: un socket permanentemente
escuchando peticiones de conexión (recepcionista) y otros
tantos socket que se encargan de atender a cada uno de los
clientes (ejecutivos).
2. Creando el arreglo de WinSocks
Para poder trabajar con varias conexiones a la vez
necesitamos varios sockets disponibles, ya que cada uno solo
puede trabajar con una sola conexión, y como en
principio no conocemos la cantidad de Winsocks que necesitaremos
debemos inclinarnos por crear Arreglos de controles Winsock e
irlos cargando dinámicamente.
Si lo deseas también puedes crear una N cantidad
de Winsocks y solo trabajar con ellos, pero tu número
máximo de conexiones posibles será el máximo
de Winsocks que tengas.
Entonces, para crear el arreglo debemos seguir los sig.
pasos:
- Agregar un nuevo Winsock al formulario
(Winsock2) - Copiar el control (Clic derecho sobre este y
seleccionar "Copiar") - Pegar el control en el formulario, y cuando pregunte
por si deseas crear el arreglo dile que "Si". (Clic derecho
sobre el formulario y seleccionar "Pegar") - Borramos el nuevo Winsock que se ha creado
(Winsock2(1)).
La razón de borrar este último Winsock es
porque no nos hace falta, como mencionamos en un principio,
nosotros crearemos los Winsocks necesarios de forma dinámica, solo necesitamos tener existente
el Winsock2 de índice cero.
3. Limpiando código innecesario
En realidad no hay código innecesario pero si
código que debe cambiar de lugar, como veremos en su
momento, por ahora solo nos limitaremos a borrar todo el
código del evento "Winsock1_DataArrival" ya que nunca lo
usaremos en este Winsock porque solo trabajara como repartidor de
trabajo y
"Winsock1_ConnectionRequest" que será implementada mas
adelante, lo mismo para la acción del botón
"Enviar" (Command1_Click).
4. Enviando/recibiendo datos
Vamos a ver como se realizan las acciones de recibir y
enviar datos cuando tenemos arreglos de sockets (Winsock2()), nos
guardaremos la
administración de las peticiones de conexiones para
más adelante.
Para enviar datos (mediante el botón "enviar")
podemos hacerlo directamente con algún socket
específico, definiendo su identidad, o bien con todos los
sockets recorriendo el arreglo. Esto último es lo que
haremos a modo de ejemplo:
Private Sub Dim Dim i 'obtiene la cantidad de Winsocks numElementos = Winsock2.UBound 'recorre el arreglo de For i = 0 'si el socket se encuentra If 'enviamos el contenido de Winsock2(i).SendData Text2.Text & 'apuntamos al final del 'insertamos los nuevos datos Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "Sock" & i Text1.SelStart = Len(Text1.Text) End If Next 'borramos Text2 Text2.Text = "" End Sub |
Y cuando recibamos datos desde el cliente se nos
generara el evento "Winsock2_DataArrival" que ahora incluye un
nuevo parámetro "Index" de tipo "Integer" y contiene el
número o índice del socket que genera el evento
(todo en relación al arreglo de sockets).
Para recibir datos solo tenemos que tomar en cuenta ese
"Index" y el resto es igual a lo visto en el servidor de
conexión mono-usuario:
Private Sub Dim Buffer 'obtenemos los datos y los Winsock2(Index).GetData Buffer 'apuntamos al final del 'insertamos los nuevos datos Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "Sock" & Index Text1.SelStart = Len(Text1.Text) End Sub |
5. Evento Error y Close
El código para los eventos "Error" y "Close" del
arreglo de sockets es muy simple:
Private Sub 'cierra la conexion Winsock2(Index).Close 'desplegamos un mensaje en la Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "Sock" & Index Text1.SelStart = Len(Text1.Text) End Sub |
Private Sub 'cerramos la conexion Winsock2(Index).Close 'mostramos informacion sobre el MsgBox "Error numero " & Number & ": " End Sub |
5. Escuchando y atendiendo a las
conexiones
El siguiente paso será recibir las peticiones de
conexión y asignárselas a cada socket para que las
atienda. Para ello necesitaremos crear un nuevo socket cada vez
que recibamos una petición de conexión y decirle
que acepte la identidad de la conexión.
Para facilitar las cosas nosotros haremos una
función que se encargue de crear los sockets y que
además devuelva el número del nuevo socket
creado:
'Carga un nuevo socket al Private Function Dim Dim i 'obtiene la cantidad de Winsocks numElementos = Winsock2.UBound 'recorre el arreglo de For i = 0 'si algun socket ya creado esta 'utiliza este mismo para la If NuevoSocket = i 'retorna el indice Exit Function End If Next 'si no encuentra sockets 'crea uno nuevo y devuelve su Load Winsock2(numElementos + 1) 'devuelve el nuevo NuevoSocket = Winsock2.UBound End Function |
Esta función no solo crea un nuevo socket, sino
que además si encuentra alguno que se había creado
antes y este se encuentra inactivo (desconectado) lo selecciona
para volverlo a utilizar y así aprovechar mas los recursos del
sistema.
Nota: Esta no es la forma más óptima de
manejar arreglos de objetos, ya que no nos permite ir liberando
de la memoria
(borrando) los sockets que ya no son utilizados y solo se limita
a crear nuevos.
Ahora nos situamos en el evento "ConnectionRequest()"
del "Winsock1" (el que hará de recepción) y
escribimos el siguiente código:
Private Sub Dim 'mostramos un mensaje en la Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "*** Peticion Text1.SelStart = Len(Text1.Text) 'creamos un nuevo numSocket = NuevoSocket 'aceptamos la conexion con el Winsock2(numSocket).Accept requestID 'desplegamos un mensaje en la Text1.SelStart = Len(Text1.Text) Text1.Text = Text1.Text & "Sock" & Text1.SelStart = Len(Text1.Text) End Sub |
Aquí lo primero es crear un nuevo socket (o
reutilizar alguno disponible) y decirle a ese socket que acepte
aquella conexión. Una vez realizado esto ya estamos libres
nuevamente para recibir otra conexión.
También es posible denegar conexiones realizadas
desde alguna IP especifica, para ello solo hay que revisar la
propiedad "Winsock1.RemoteHostIP" y ver si aceptamos su
conexión o bien se la rechazamos con "Close".
6. Prueba de la aplicación
Ahora que todo parece estar completo, realizaremos la
prueba del servidor.
Ejecuta la aplicación, como "Puerto" coloca el
"23" y dale al botón "Escuchar".
Ahora ejecuta dos aplicaciones "Cliente" y
conéctalos al puerto "23" de "Localhost". Notaras que ya
no da el error que vimos con el servidor mono-conexión y
que ambos se encuentran conectados:
Prueba a enviar mensajes entre ellos y veras que todo
trabaja perfectamente!!, puedes identificar cada conexión
porque en el mensaje aparece "SockN" donde "N" es el
índice del socket, así sabrás en cada
momento que socket es el que esta enviando el mensaje:
y ya funciona todo ok!.
Hemos llegado al final de este tutorial, y hemos
aprendido a realizar conexión Cliente/Servidor mono y
multi-conexiones de forma básica, digo básica
porque hay mejores maneras de implementarlas y mas minuciosas,
pero esta es la base de todas ellas, y el resto lo
obtendrás por la practica.
Espero que este texto les halla sido de utilidad y
cualquier duda o errores encontrados no dudes en
comunicármelo, que lo corregiré tan pronto como
pueda.
Por ultimo los códigos fuente de los proyectos
aquí realizados los puedes bajar desde la pagina
Web http://www.gemu.da.ru.
Gemu –
Todos los derechos reservados –
Septiembre de 2005