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:
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:
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:
- Framework: 4
- Lenguaje programación: c#
- Ide: visual studio 2010.
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
}
}
}
}
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