Tutoriales gratuitos para el aprendizaje de la programacion informatica! Recuerda que si lo puedes imaginar... lo puedes programar!

PROGRAMAR UN CHAT EN C#

Teoria

Para poder realizar un chat con c# y framework .net tenemos varias opciones. Una de ellas es utilizando sockets y otra es remoting. En estaocasión implemetaremos el chat mediante el uso de sockets a través de las clases tcplistener, tcpclient y networkstream .

Antes de empezar

La aplicación que realicé es un ejemplo muy sencillo de como puede implemetarse un chat mediante c#. Esta dividido en 2 proyectos: uno para el servidor y otro para el cliente. El servidor se encargará de manejar las conexiones de los diferentes clientes que se conecten al chat así como de enviar cada uno de los mensajes a todos los clientes conectados en un momento determinado. El cliente enviará los mensajes al servidor. Aclaro, es una aplicación muy simple y la finalidad es aprender como realizar una aplicación de este tipo. Más adelante le meteremos más funciones o si quieres aportar es bienvenido.

La herramientas con las cual desarrollé el ejemplo son las siguientes:

  1. Framework: 4
  2. Lenguaje programación: c#
  3. Ide: visual studio 2010.
Brevario cultural

Demos una revisada rapida a las clases que utilizaremos para entender mejor el código.

Tcplistener


La clase tcplistener proporciona métodos sencillos para escuchar y aceptar solicitudes de conexión entrantes en modo de bloqueo sincrónico (por lo queentiendo, suspende la aplicación hasta que recibe respuesta por parte del cliente). Podemos crear instancias tcplistener mediante un objeto ipendpoint, una dirección ip local y un número de puerto, o mediante tan sólo un número de puerto.

El método start inicia la espera de solicitudes de conexión entrantes. Start pondrá en cola las conexiones entrantes hasta que llame al método stopo hasta que haya puesto en cola
Maxconnections. Utilice acceptsocket o accepttcpclient para retirar una conexión de la cola de solicitudes de conexión entrantes. Estos dos métodos se bloquearán (no sé muy bien como explicarles esta parte de que “se bloquearán”, pero prometo investigarlo y explicarselos más adelante).

Para cerrar la solicitud de conexión entrantes, en otras palabras, apagar el servidor invocaremos el método tcplistener. Es importante destacar que elmétodo stop no cierra ninguna conexión aceptada. El programador es el responsable de cerrarlas por separado.

Tcpclient

La clase tcpclient proporciona métodos sencillos para conectar, enviar y recibir flujos de datos a través de una red en modo de bloqueo sincrónico . Para que tcpclient pueda conectarse e
Intercambiar datos, debe haber un tcplistener o socket creado con el tipo de protocolo (protocoltype) tcp a la escucha para solicitudes deconexión entrantes.

Podemos establecer conexión con un tcplistener dedos maneras diferentes:

Crear un tcpclient y llame a uno de los tres métodos connect disponibles.Crear un tcpclient mediante el nombre de host y el número de puerto del host remoto. Este constructor intentará automáticamente realizar una conexión.

Networkstream

La clase networkstream proporciona métodos para enviar y recibir datos a través de sockets de stream en modo de bloqueo.
Para crear una clase networkstream, es necesario suministrar un socket conectado. También se puede especificar qué permiso de tipo fileaccess tiene networkstream con respecto al socket
Proporcionado. De forma predeterminada, al cerrar networkstream no se cierra el socket proporcionado. Si desea que networkstream tenga permiso para cerrar el socket proporcionado, deberá especificar true para el valor del parámetroownssocket.

Utiliza los métodos read y write para las operaciones de e/s sincrónica y sencilla de bloqueo con un solo subproceso. Las operaciones de lectura y escritura se pueden realizar simultáneamente en una instancia de la clase networkstream sin necesidad de sincronización. Mientras haya un subproceso
Único para las operaciones de escritura y un subproceso único para las operaciones de lectura, no habrá ninguna interferencia cruzada entre los subprocesos de lectura y escritura y no se requiere ninguna sincronización.

El servidor

Referencias para desarrollar el servidor necesitamos agregar las siguientes referencias:


USING SYSTEM;
USING SYSTEM.COLLECTIONS;
USING SYSTEM.NET;
USING SYSTEM.NET.SOCKETS;
USING SYSTEM.TEXT;
USING SYSTEM.THREADING;
USING SYSTEM.WINDOWS.FORMS;

#REGION [ VARIABLES ]

/// LA CLASE TCPLISTENER PROPORCIONA MÉTODOS SENCILLOS PARA ESCUCHAR Y ACEPTAR SOLICITUDES DE CONEXIÓN ENTRANTES EN MODO DE BLOQUEO SINCRÓNICO

PRIVATE TCPLISTENERSERVIDOR;

/// ALMACENARÁ EL NOMBRE DE LOS CLIENTES CONECTADOS

PRIVATE STATIC HASHTABLECLIENTES_CONECTADOS;

/// MENSAJE RECIBIDO DEL CLIENTE O QUE SE ENVIARÁ AL CLIENTE

PRIVATE STRINGMENSAJECLIENTE;

/// MENSAJE QUE SE ENVÍA A LA PANTALLA DEL SERVIDOR.

PRIVATE STRING _MENSAJECHAT;
#ENDREGION
EVENTOS
#REGION [ EVENTOS ]

/// INICIA EL SERVIDOR DE CHAT

PRIVATE VOID BTNINICIAR_CLICK(OBJECT SENDER, EVENTARGS E)
{
    TRY
{
        BTNINICIAR.ENABLED = FALSE;
        BTNDETENER.ENABLED = TRUE;

        //INICIALIZAMOS NUESTRO HASHTABLE

        CLIENTES_CONECTADOS = NEW HASHTABLE();

        //INICIALIZAMOS UN NUEVO SERVIDOR EN LA IP Y PUERTO SELECCIONADO.

        SERVIDOR = NEW TCPLISTENER(IPADDRESS.PARSE(TXTIP.TEXT), INT.PARSE(TXTPUERTO.TEXT));

        //INICIAMOS EL SERVIDOR

        SERVIDOR.START();

        //INDICAMOS QUE EL SERVIDOR ESTA LISTO PARA ACEPTAR PETICIONES DE LOS CLIENTES

        _MENSAJECHAT = "SERVIDOR INICIADO...";
        MENSAJE();
        WHILE(TRUE)
{
             //ACEPTA LA PETICIÓN DE UN CLIENTE

            TCPCLIENT CLIENTE = SERVIDOR.ACCEPTTCPCLIENT();

            //SÓLO ACEPTAREMOS MENSAJES DE MÁXIMO 256 CARACTERES

            BYTE[] BYTESCLIENTE = NEW BYTE[256];

            //LEEMOS EL MENSAJE DEL CLIENTE

            NETWORKSTREAM STREAMCLIENTE = CLIENTE.GETSTREAM();
            STREAMCLIENTE.READ(BYTESCLIENTE, 0, BYTESCLIENTE.LENGTH);

            //TRADUCIMOS EL STREAM DE BYYTES A UN STRING EN CODIFICACIÓN ASCII

            MENSAJECLIENTE = ENCODING.ASCII.GETSTRING(BYTESCLIENTE, 0, BYTESCLIENTE.LENGTH);
            MENSAJECLIENTE = MENSAJECLIENTE.SUBSTRING(0, MENSAJECLIENTE.INDEXOF("$"));

            //VERIFICAMOS SI YA EXISTE ESE NOMBRE DE USUARIO.

            IF(!CLIENTES_CONECTADOS.CONTAINSKEY(MENSAJECLIENTE))
{

                //REALMENTE NO HACEMOS NINGUNA VALIDACIÓN POSTERIOR NI SE MANDA UN MENSAJE AL
                //USUARIO PERO POR FUNCIONALIDAD AGREGAREMOS ESTA CONDICIÓN.

                CLIENTES_CONECTADOS.ADD(MENSAJECLIENTE, CLIENTE);
                _MENSAJECHAT = STRING.FORMAT("{0} SE HA UNIDO AL SERVIDOR", MENSAJECLIENTE);
                MENSAJE();
            }

            //MANDAMOS EL MENSAJE A TODOS LOS CLIENTES EL MENSAJE

            DIFUNDIRATODOS(MENSAJECLIENTE, MENSAJECLIENTE, FALSE);

            //CICLAMOS EL PROCESO PARA QUE SE QUEDE EL SERVIDOREN ESPERA DE NUEVAS CONEXIONES O MENSAJES

            CHAT CHAT = NEW CHAT(CLIENTE, MENSAJECLIENTE);
        }
    }
    CATCH(EXCEPTION EX)
{
        _MENSAJECHAT = EX.TOSTRING();
        MENSAJE();
    }
    FINALLY
{
        SERVIDOR.STOP();
    }
}

/// DETIENE EL SERVIDOR DE CHAT

PRIVATE VOID BTNDETENER_CLICK(OBJECT SENDER, EVENTARGS E)
{
    IF(SERVIDOR != NULL && SERVIDOR.SERVER.CONNECTED)
{
        BTNINICIAR.ENABLED = FALSE;
        BTNDETENER.ENABLED = TRUE;
        SERVIDOR.STOP();
    }
}
#ENDREGION
MÉTODOS
#REGION [ MÉTODOS ]

/// <SUMMARY>
/// DIFUNDE EL MENSAJE DE UN USUARIO A TODOS LOS USUARIOS CONECTADOS.
/// </SUMMARY>
/// <PARAM NAME="MENSAJE">MENSAJE DEL USUARIO</PARAM>
/// <PARAM NAME="NOMBRE">NOMBRE DEL USUARIO</PARAM>
/// <PARAM NAME="FLAG">BANDERA QUE SE UTILIZA PARA DETERMINAR SI SE AGREGA EL TEXTO
/// "DICE" AL MENSAJE ENVIADO POR EL USUARIO</PARAM>

PUBLIC STATIC VOID DIFUNDIRATODOS(STRING MENSAJE, STRING NOMBRE, BOOL FLAG)
{
    TRY
{

        //POR CADA CLIENTE

        FOREACH(DICTIONARYENTRYITEM INCLIENTES_CONECTADOS)
{
            BYTE[] BYTES = NULL;
            TCPCLIENT CLIENTE;
            CLIENTE = (TCPCLIENT)ITEM.VALUE;
            NETWORKSTREAM STREAMCLIENTE = CLIENTE.GETSTREAM();
            IF(FLAG == TRUE)
BYTES = ENCODING.ASCII.GETBYTES(NOMBRE + " DICE : " + MENSAJE);
            ELSE
            BYTES = ENCODING.ASCII.GETBYTES(NOMBRE + " SE HA CONECTADO");

            //TRANSMITIMOS EL MENSAJE

            STREAMCLIENTE.WRITE(BYTES, 0, BYTES.LENGTH);
            STREAMCLIENTE.FLUSH();
        }
    }
    CATCH(EXCEPTION)
{
                //TODO: MANEJAR LA EXCEPCIÓN.
            }
}
PUBLIC VOID MENSAJE()
{
    IF(THIS.INVOKEREQUIRED)
THIS.INVOKE(NEW METHODINVOKER(MENSAJE));
    ELSE
TXTCHAT.TEXT = TXTCHAT.TEXT + ENVIRONMENT.NEWLINE + " -> " + MENSAJECLIENTE;
}
#ENDREGION
CLASE CHAT
PUBLIC CLASS CHAT
{
#REGION [ VARIABLES ]
TCPCLIENT CLIENTECHAT; STRING NOMBREUSUARIO;
#ENDREGION

//CONSTRUCTOR

PUBLIC CHAT(TCPCLIENT CLIENTE, STRING NUSUARIO)
{
    CLIENTECHAT = CLIENTE;
    NOMBREUSUARIO = NUSUARIO;

    //INICIAMOS UN NUEVO PROCESO QUE CICLE LA ESPERA DE MENSAJES NUEVOS POR PARTE DE LOS CLIENTES.

    THREAD CTTHREAD = NEW THREAD(DOCHAT);
    CTTHREAD.START();
}
/// <SUMMARY>
/// CICLA EL PROCESO INDEFINIDAMENTE PARA QUE EL SERVIDOR QUEDE A LA ESPERA DE NUEVOS MENSAJES
/// POR PARTE DE LOS CLIENTES.
/// </SUMMARY>
PRIVATE VOID DOCHAT()
{
    BYTE[] BYTESFROM = NEW BYTE[256];
    STRING MENSAJECLIENTE = NULL;
    WHILE((TRUE))
{
        TRY
{
            NETWORKSTREAMNETWORKSTREAM = CLIENTECHAT.GETSTREAM();
            NETWORKSTREAM.READ(BYTESFROM, 0, BYTESFROM.LENGTH);
            MENSAJECLIENTE = SYSTEM.TEXT.ENCODING.ASCII.GETSTRING(BYTESFROM);
            MENSAJECLIENTE = MENSAJECLIENTE.SUBSTRING(0, MENSAJECLIENTE.INDEXOF("$"));

            //TXTMENSAJES.TEXT += "FROM CLIENT - " + CLNO + " : " + DATAFROMCLIENT;
            //DIFUNDIMOS EL MENSAJE A TODOS LOS CLIENTES
                        FRMSERVIDOR.DIFUNDIRATODOS(MENSAJECLIENTE, NOMBREUSUARIO, TRUE);
        }
        CATCH(EXCEPTIONEX)
{
                        //TODO: MANEJAR EXCEPCIÓN
        }
    }
}
}


El cliente

USING SYSTEM;
USING SYSTEM.NET.SOCKETS;
USING SYSTEM.TEXT;
USING SYSTEM.THREADING;
USING SYSTEM.WINDOWS.FORMS;
VARIABLES
#REGION [ VARIABLES ]

// LA CLASE TCPCLIENTPROPORCIONA MÉTODOS SENCILLOS PARA CONECTAR, ENVIAR Y RECIBIR FLUJOS DE
//DATOS A TRAVÉS DE UNA RED EN MODO DE BLOQUEO SINCRÓNICO.
TCPCLIENTCLIENTE;
// LA CLASE NETWORKSTREAMPROPORCIONA MÉTODOS PARA ENVIAR Y RECIBIR DATOS A TRAVÉS DE
//SOCKETS DE STREAM EN MODO DE BLOQUEO
NETWORKSTREAMSTREAMSERVIDOR;
// MENSAJE QUE SE ENVIA A LA PANTALLA DEL CHAT

STRING_MENSAJECHAT;
#ENDREGION
EVENTOS
#REGION [ EVENTOS ]

/// <SUMMARY>
/// CONECTA AL CLIENTE CON EL SERVIDOR DE CHAT
/// </SUMMARY>
/// <PARAM NAME="SENDER"></PARAM>
/// <PARAM NAME="E"></PARAM>
//BOTON CONECTAR AL SERVIDOR

PRIVATE VOID BTNCONECTAR_CLICK(OBJECT SENDER, EVENTARGS E)
{
    TRY
{
        THIS.TEXT = STRING.FORMAT("CLIENTE:{0}", TXTNOMBRE.TEXT);
        _MENSAJECHAT = "CONECTANDO AL SERVIDOR...";
        MENSAJE();

        //ABRIMOS LA CONEXION CON EL SERVIDOR

        CLIENTE = NEW TCPCLIENT(TXTIP.TEXT, INT.PARSE(TXTPUERTO.TEXT));

        //INCIALIZAMOS EL STREAM

        STREAMSERVIDOR = CLIENTE.GETSTREAM();

        //TRANSFORMAMOS EL STRING EN UN ARREGLO DE BYTES PARA PODER SER ENVIADO MEDIANTE EL
        //NETWORKSTREAM

        BYTE[] DATOS = SYSTEM.TEXT.ENCODING.ASCII.GETBYTES(TXTNOMBRE.TEXT + "$");

        //ENVIAMOS LOS DATOS AL SERVIDOR

        STREAMSERVIDOR.WRITE(DATOS, 0, DATOS.LENGTH);
        STREAMSERVIDOR.FLUSH();

        //CICLAMOS EL PROCESO DE ESCUCHAR RESPUESTAS DEL SERVIDOR, EN ESTA CASO CADA QUE UN CLIENTE
        //ENVÍA UN MENSAJE.

        THREAD CTTHREAD = NEW THREAD(CHAT);
        CTTHREAD.START();
        BTNCONECTAR.ENABLED = FALSE;
        BTNDESCONECTAR.ENABLED = TRUE;
        BTNENVIAR.ENABLED = TRUE;
    }
    CATCH(EXCEPTION EX)
{
        TXTCHAT.TEXT = EX.TOSTRING();
        IF(MESSAGEBOX.SHOW("¿CONECTAR DE NUEVO?") == SYSTEM.WINDOWS.FORMS.DIALOGRESULT.YES)
BTNCONECTAR_CLICK(NULL, NULL);
        ELSE
THIS.CLOSE();
    }
}

/// <SUMMARY>
/// ENVÍA UN MENSAJE AL SERVIDOR
/// </SUMMARY>
/// <PARAM NAME="SENDER"></PARAM>
/// <PARAM NAME="E"></PARAM>
//BOTON ENVIAR MENSAJE AL SERVIDOR

PRIVATE VOID BTNENVIAR_CLICK(OBJECT SENDER, EVENTARGS E)
{
    TRY
{

        //INCIALIZAMOS EL NETWORKSTREAM

        STREAMSERVIDOR = CLIENTE.GETSTREAM();

        //TRANSFORMAMOS EL STRING EN UN ARREGLO DE BYTES PARA PODER SER ENVIADO MEDIANTE EL
        //NETWORKSTREAM

        BYTE[] DATOS = ENCODING.ASCII.GETBYTES(TXTMENSAJE.TEXT + "$" + TXTNOMBRE.TEXT);

        //ENVIAMOS LOS DATOS AL SERVIDOR

        STREAMSERVIDOR.WRITE(DATOS, 0, DATOS.LENGTH);
        STREAMSERVIDOR.FLUSH();
    }
    CATCH(EXCEPTION EX)
{
        TXTCHAT.TEXT = EX.TOSTRING();
    }
}

/// <SUMMARY>
/// DESCONECTA NUESTRA SESION.
/// </SUMMARY>
/// <PARAM NAME="SENDER"></PARAM>
/// <PARAM NAME="E"></PARAM>
//BOTON DESCONECTAR DEL CHAT

PRIVATE VOID BTNDESCONECTAR_CLICK(OBJECT SENDER, EVENTARGS E)
{
    IF(CLIENTE != NULL && CLIENTE.CONNECTED)
{
        CLIENTE.CLOSE();
        BTNCONECTAR.ENABLED = TRUE;
        BTNDESCONECTAR.ENABLED = FALSE;
        BTNENVIAR.ENABLED = FALSE;
    }
}
#ENDREGION
MÉTODOS
#REGION [ METODOS ]

/// <SUMMARY>
/// CICLA INDEFINIDAMENTE EL PROCESO DE ESPERA DE MENSAJES POR PARTE DEL SERVIDOR
/// </SUMMARY>

PRIVATE VOID CHAT()
{
    WHILE(TRUE)
{
        STREAMSERVIDOR = CLIENTE.GETSTREAM();

        //INT BUFFSIZE = 0;

        BYTE[] BYTES = NEW BYTE[256];

        //LEEMOS EL MENSAJE ENVIADO POR EL SERVIDOR

        STREAMSERVIDOR.READ(BYTES, 0, BYTES.LENGTH);

        //Y LO ENVIAMOS A LA PANTALLA DEL CHAT

        _MENSAJECHAT = ENCODING.ASCII.GETSTRING(BYTES);
        MENSAJE();
    }
}

/// <SUMMARY>
/// ENVIA EL MENSAJE AL TEXTBOX DEL CHAT
/// </SUMMARY>

PRIVATE VOID MENSAJE()
{
    IF(THIS.INVOKEREQUIRED)
THIS.INVOKE(NEW METHODINVOKER(MENSAJE));
    ELSE
TXTCHAT.TEXT = TXTCHAT.TEXT + ENVIRONMENT.NEWLINE + " -> " + _MENSAJECHAT;
}

# ENDREGION



Resultado final


Para probar nuestras aplicaciones ejecutamos el servidor en la ip y puerto que deseemos. Si lo corres en tu pc el servidor, asigna la ip 127.0.0.1 o la ip que te asigne tu red. El puerto selecciona uno que no esté en uso en tu pc. Yo utilice el 9090 pero podrías utilizar el 8888 o el 9999.

Una vez iniciado el servidor, ejecuta cuantos clientes quieras. La verdad es que no tuve la curiosidad de checar hasta cuantos aguanta. Para ejemplo, solo abrí dos instancias cliente.

En los parámetros del cliente introduce el nombre de tu pc y asigna el mismo puerto que utilizaste en el servidor.

No hay comentarios:

Publicar un comentario