Descargar

Tutorial de cliente/servidor en Visual Basic 6.0

Enviado por Gemu


    1. ¿Qué es cliente/servidor?
    2. Preparando el Visual Basic
    3. Descripción del componente Winsock
    4. Mi primera aplicación cliente
    5. Mi primera aplicación servidor
    6. Aplicación servidor multi-conexión
    7. Conclusión

    INTRODUCCION

    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.

    ¿QUE ES CLIENTE/SERVIDOR?

    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:

    1. Ejecutamos nuestro navegador (Internet Explorer, Netscape, Firefox, etc.)
    2. Ingresamos la dirección del sitio Web que deseamos ver, por ej. www.google.com
    3. Le damos al botón "Ir", "Ver", etc. Para que nuestro navegador se conecte a la dirección.
    4. Nuestro navegador inmediatamente comienza a recibir poco a poco la página Web solicitada.
    5. 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.

    PREPARANDO EL VISUAL BASIC

    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.

    1. 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í)
      1. Ve al menú "Proyecto" y selecciona "Componentes".
      2. En la lista de componentes busca "Microsoft Winsock Control 6.0", puede ser otra versión o bien terminar con "(SP6)".
      3. Marca este control y dale al botón "Aceptar".
    2. 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 computador remoto.

    sckConnected

    El socket ha conectado al computador remoto.

    sckConnecting

    El socket esta conectando al computador remoto.

    sckConnectionPending

    El socket tiene una petición pendiente

    sckError

    Se ha producido un error

    sckHostResolved

    El socket ha resuelto el nombre del equipo remoto.

    sckListening

    El socket esta a la escucha de peticiones entrantes.

    sckOpen

    El socket esta actualmente abierto.

    sckResolvingHost

    El socket esta resolviendo el nombre del computador remoto.

    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 maquina local.

    sckAlreadyComplete

    La operación esta completa. En progreso la operación de no bloqueo.

    sckAlreadyConnected

    El socket esta actualmente conectado.

    sckBadState

    Protocolo o estado de conexión equivocada para la transacción o petición demandada.

    sckConnectAborted

    La conexión es abortada debido a que se ha superado el tiempo de conexión u otra falla.

    sckConnectionRefused

    La conexión a sido rechazada.

    sckConnectionReset

    La conexión a sido reinicializada por el lado remoto.

    sckGetNotSupported

    La propiedad es de Escritura solamente.

    sckHostNotFound

    Respuesta autorizada. No se ha encontrado el Host.

    sckHostNotFoundTryAgain

    Respuesta no autorizada. No se ha encontrado el Host.

    sckInProgress

    Una operación winsock de bloqueo esta en progreso.

    sckInvalidArg

    El argumento pasado a la función no posee un formato o rango valido.

    sckInvalidArgument

    Argumento invalido

    sckInvalidOp

    Operación invalida en el estado actual.

    sckInvalidPropertyValue

    Valor de propiedad invalido.

    sckMsgTooBig

    El datagrama es muy largo para acomodarlo dentro del buffer y ha sido truncado.

    sckNetReset

    Ha finalizado el tiempo de conexión cuando SO_KEEPALIVE estaba seteado.

    sckNetworkSubsystemFailed

    Falla en el subsistema de red.

    sckNetworkUnreachable

    La red no puede ser alcanzada desde este host.

    sckNoBufferSpace

    No hay espacio disponible en el buffer,

    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 soportado.

    sckSetNotSupported

    La propiedad es de solo lectura.

    sckSocketShutdown

    El socket había sido cerrado.

    sckSucces

    Concluido.

    sckTimedout

    Tiempo fuera en el intento de conexión.

    sckUnsupported

    No soporta tipos Variants.

    sckWouldBlock

    El socket no esta bloqueando y la especifica operación será realizada.

    sckWrongProtocol

    Protocolo equivocado para la especifica transacción o petición.

    MI PRIMERA APLICACIÓN CLIENTE

    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 Command2_Click()

    'asignamos los datos de conexion

    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 Winsock1_Connect()

    'desplegamos un mensaje en la ventana

    Text1.Text = Text1.Text & _

    "*** Conexion establecida." & vbCrLf

    'desplazamos el scroll

    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 Winsock1_Close()

    'cierra la conexion

    Winsock1.Close

    'desplegamos un mensaje en la ventana

    Text1.SelStart = Len(Text1.Text)

    Text1.Text = Text1.Text & "*** Conexion cerrada por el servidor." & vbCrLf

    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 Command3_Click()

    'cierra la conexion

    Winsock1.Close

    'desplegamos un mensaje en la ventana

    Text1.Text = Text1.Text & _

    "*** Conexion cerrada por el usuario." & vbCrLf

    '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 Command1_Click()

    'enviamos el contenido de Text2

    Winsock1.SendData Text2.Text & vbCrLf

    'apuntamos al final del contenido del TextBox e

    'insertamos los nuevos datos obtenidos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    Text1.Text = Text1.Text & "Cliente >" & Text2.Text & vbCrLf 'mostramos los datos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    '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 Winsock1_DataArrival(ByVal bytesTotal As Long)

    Dim Buffer As String 'variable para guardar los datos

    'obtenemos los datos y los guardamos en una variable

    Winsock1.GetData Buffer

    'apuntamos al final del contenido del TextBox e

    'insertamos los nuevos datos obtenidos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    Text1.Text = Text1.Text & "Servidor >" & Buffer 'mostramos los datos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    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 producido

    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 Winsock1_Error(ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)

    'cerramos la conexion

    Winsock1.Close

    'mostramos informacion sobre el error

    MsgBox "Error numero " & Number & ": " & Description, vbCritical

    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 Command2_Click()

    'cerramos cualquier conexion previa

    Winsock1.Close

    'asignamos el puerto local que abriremos

    Winsock1.LocalPort = Text3.Text

    'deja el socket esuchando conexiones

    Winsock1.Listen

    'desplegamos un mensaje en la ventana

    Text1.SelStart = Len(Text1.Text)

    Text1.Text = Text1.Text & "*** Esuchando conexiones." & vbCrLf

    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 Winsock1_ConnectionRequest(ByVal requestID As Long)

    'mostramos un mensaje en la ventana

    Text1.SelStart = Len(Text1.Text)

    Text1.Text = Text1.Text & "*** Peticion numero " & requestID & vbCrLf

    Text1.SelStart = Len(Text1.Text)

    'cerramos previamente el socket

    Winsock1.Close

    'aceptamos la conexion

    Winsock1.Accept requestID

    'desplegamos un mensaje en la ventana

    Text1.SelStart = Len(Text1.Text)

    Text1.Text = Text1.Text & "*** Conexion aceptada, listo para interactuar." & vbCrLf

    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 Command3_Click()

    'cierra la conexion

    Winsock1.Close

    'desplegamos un mensaje en la ventana

    Text1.SelStart = Len(Text1.Text)

    Text1.Text = Text1.Text & "*** Conexion cerrada por el usuario." & vbCrLf

    Text1.SelStart = Len(Text1.Text)

    End Sub

    Private Sub Winsock1_Close()

    'cierra la conexion

    Winsock1.Close

    'desplegamos un mensaje en la ventana

    Text1.SelStart = Len(Text1.Text)

    Text1.Text = Text1.Text & "*** Conexion cerrada por el Cliente." & vbCrLf

    Text1.SelStart = Len(Text1.Text)

    End Sub

    3. Enviando/recibiendo datos

    Esto es idéntico al explicado en la parte del cliente:

    Private Sub Command1_Click()

    'enviamos el contenido de Text2

    Winsock1.SendData Text2.Text & vbCrLf

    'apuntamos al final del contenido del TextBox e

    'insertamos los nuevos datos obtenidos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    Text1.Text = Text1.Text & "Servidor >" & Text2.Text & vbCrLf 'mostramos los datos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    'borramos Text2

    Text2.Text = ""

    End Sub

    Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)

    Dim Buffer As String 'variable para guardar los datos

    'obtenemos los datos y los guardamos en una variable

    Winsock1.GetData Buffer

    'apuntamos al final del contenido del TextBox e

    'insertamos los nuevos datos obtenidos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    Text1.Text = Text1.Text & "Cliente >" & Buffer 'mostramos los datos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    End Sub

    4. Manejo de errores

    Esto es idéntico al explicado en la parte del cliente:

    Private Sub Winsock1_Error(ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)

    'cerramos la conexion

    Winsock1.Close

    'mostramos informacion sobre el error

    MsgBox "Error numero " & Number & ": " & Description, vbCritical

    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:

    1. Agregar un nuevo Winsock al formulario (Winsock2)
    2. Copiar el control (Clic derecho sobre este y seleccionar "Copiar")
    3. 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")
    4. 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 Command1_Click()

    Dim numElementos As Integer 'numero de sockets

    Dim i As Integer 'contador

    'obtiene la cantidad de Winsocks que tenemos

    numElementos = Winsock2.UBound

    'recorre el arreglo de sockets

    For i = 0 To numElementos

    'si el socket se encuentra conectado…

    If Winsock2(i).State = sckConnected Then

    'enviamos el contenido de Text2

    Winsock2(i).SendData Text2.Text & vbCrLf

    'apuntamos al final del contenido del TextBox e

    'insertamos los nuevos datos obtenidos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    Text1.Text = Text1.Text & "Sock" & i & ":Servidor >" & Text2.Text & vbCrLf 'mostramos los datos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    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 Winsock2_DataArrival(Index As Integer, ByVal bytesTotal As Long)

    Dim Buffer As String 'variable para guardar los datos

    'obtenemos los datos y los guardamos en una variable

    Winsock2(Index).GetData Buffer

    'apuntamos al final del contenido del TextBox e

    'insertamos los nuevos datos obtenidos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    Text1.Text = Text1.Text & "Sock" & Index & ":Cliente >" & Buffer 'mostramos los datos

    Text1.SelStart = Len(Text1.Text) 'coloca el cursor al final del contenido

    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 Winsock2_Close(Index As Integer)

    'cierra la conexion

    Winsock2(Index).Close

    'desplegamos un mensaje en la ventana

    Text1.SelStart = Len(Text1.Text)

    Text1.Text = Text1.Text & "Sock" & Index & ":*** Conexion cerrada por el Cliente." & vbCrLf

    Text1.SelStart = Len(Text1.Text)

    End Sub

    Private Sub Winsock2_Error(Index As Integer, ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)

    'cerramos la conexion

    Winsock2(Index).Close

    'mostramos informacion sobre el error

    MsgBox "Error numero " & Number & ": " & Description, vbCritical

    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 arreglo y devuelve su indice

    Private Function NuevoSocket() As Integer

    Dim numElementos As Integer 'numero de sockets

    Dim i As Integer 'contador

    'obtiene la cantidad de Winsocks que tenemos

    numElementos = Winsock2.UBound

    'recorre el arreglo de sockets

    For i = 0 To numElementos

    'si algun socket ya creado esta inactivo

    'utiliza este mismo para la nueva conexion

    If Winsock2(i).State = sckClosed Then

    NuevoSocket = i 'retorna el indice

    Exit Function 'abandona la funcion

    End If

    Next

    'si no encuentra sockets inactivos

    'crea uno nuevo y devuelve su identidad

    Load Winsock2(numElementos + 1) 'carga un nuevo socket al arreglo

    'devuelve el nuevo indice

    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 Winsock1_ConnectionRequest(ByVal requestID As Long)

    Dim numSocket As Integer 'el numero del socket

    'mostramos un mensaje en la ventana

    Text1.SelStart = Len(Text1.Text)

    Text1.Text = Text1.Text & "*** Peticion numero " & requestID & vbCrLf

    Text1.SelStart = Len(Text1.Text)

    'creamos un nuevo socket

    numSocket = NuevoSocket

    'aceptamos la conexion con el nuevo socket

    Winsock2(numSocket).Accept requestID

    'desplegamos un mensaje en la ventana

    Text1.SelStart = Len(Text1.Text)

    Text1.Text = Text1.Text & "Sock" & numSocket & ":*** Conexion aceptada, listo para interactuar." & vbCrLf

    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!.

    CONCLUSIÓN

    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 –

    www.gemu.da.ru

    Todos los derechos reservados – Septiembre de 2005