Ir al contenido

publicidad

Foto

Biblioteca de comunicaciones TCP para C#


Este tema ha sido archivado. Esto significa que no puedes responder en este tema.
9 respuestas en este tema

  • deviax

  • Methuselah

  • vida restante: 100%
  • Registrado: 23 mar 2005
  • Mensajes: 182
#1

Escrito 20 enero 2010 - 03:02

Descripción:
Como parte de todo el código generado para el MMO en que he estado trabajando voy a dejar por aquí la biblioteca de comunicaciones TCP para C#. En ella se incluyen las siguientes clases para que las uséis:
ServidorTCP - Permite crear un servidor TCP para recibir información de un cliente TCP.
ClienteTCP - Permite crear un cliente TCP para enviar información a un servidor TCP.
Paquete - Abstracción de datos enviados entre el cliente y el servidor.


Requisitos:
.Net Framework 3.5
Que vuestro proyecto sea en C#


Instalación:
Los pasos serían:
1. Descargar la biblioteca de este enlace.
2. Copiar los ficheros BibliotecaTCP.dll y BibliotecaTCP.XML del fichero comprimido descargado al directorio DEBUG de vuestro proyecto. (NOTA: Si no metéis el XML os perderéis la descripción de los métodos y los parámetros que reciben)
3. Desde Visual Studio y con vuestro proyecto abierto, seleccionar arriba en "Proyecto > Agregar referencia...", y en la pestaña de Examinar seleccionáis el BibliotecaTCP.dll que habéis pegado en vuestro DEBUG.
Listo, ya podéis trabajar con él.


Detalles de uso:
Os dejo el diagrama de clases aquí, y para hacer esta parte más fácil, os paso este enlace con el proyecto para Visual Studio 2008 en el que he estado haciendo las pruebas de la librería. He tratado de ser lo más claro posible en el código y he ido comentando línea a línea, así que ahí tenéis un buen ejemplo práctico de todo el funcionamiento que permite esta biblioteca. Para probarla, en vuestro mismo ordenador podéis ejecutar dos veces la aplicación y por un lado seleccionar el servidor y por el otro el cliente para pasar información de uno al otro.

Voy a resumir un poco la información que veréis en ese proyecto, para que también quede aquí:

Paquete
Es la clase que envuelve los datos que se enviarán del cliente al servidor para hacer transparente toda la codificación de strings y enteros a bytes. Es por ello que la idea consiste en: respecto al ClienteTCP, se prepara un paquete con los datos que se quieren enviar y se le pasa al cliente; respecto al ServidorTCP, todos los datos que recibe los empaqueta para su posterior lectura. El funcionamiento es como sigue:

1. Para crear un paquete.
[code:1]Paquete aux = new Paquete();[/code]

2. Para agregar datos (pueden ser de tipo string o int positivos y negativos).
[code:1]aux.AgregarElemento(cadena);
aux.AgregarElemento(entero);[/code]
Ahora se puede pasar el paquete al ClienteTCP para su envío, tenéis el código en el apartado del ClienteTCP.

3. Para leer los datos de un paquete recibido hay que saber en que orden se agregaron las strings y enteros, por ejemplo, en el apartado anterior se agregó una cadena primero y un entero después, pues eso se leería del paquete del siguiente modo.
[code:1]string s = (string)aux.ObtenerElemento(0);
int n = (int)aux.ObtenerElemento(1);[/code]

ServidorTCP
Es la clase que se queda a la escucha por un puerto TCP para recibir información de un ClienteTCP. Algunos detalles de implementación son:
- Programado para correr en un hilo de ejecución paralelo, por lo que no bloqueará la ejecución de la aplicación en la que corra el servidor.
- Respecto al manejo de paquetes de datos, tiene una lista en la que los va guardando según van llegando, y al pedirle al servidor un paquete recibido, lo devuelve y lo borra de la lista.
- En caso de que se produzca alguna excepción inesperada (por ejemplo que el ClienteTCP no cierre la conexión correctamente), el servidor lo detectará y se relanzará pasados 5 segundos para volver a escuchar a futuros clientes.

Por lo que la idea del ServidorTCP consiste en arrancarlo y cada X tiempo (o constantemente) pedirle un paquete de la lista para obtener la información recibida. El funcionamiento es como sigue:

1. Para arrancarlo:
[code:1]ServidorTCP stcp = new ServidorTCP(11985);
stcp.Run();[/code]
2. Para recibir los paquetes tenemos tres opciones.
[code:1]Paquete aux;
List lista;
aux = stcp.UltimoPaquete; //Devolvería el paquete más reciente
aux = stcp.PrimerPaquete; //Devolvería el paquete más antiguo
lista = stcp.TodosPaquetes; //Develve una lista con todos los paquetes[/code]
3. Para detenerlo.
[code:1]stcp.Desconectar = true;[/code]

ClienteTCP
Es la clase que se encargará de enviar los datos al ServidorTCP. Algunos detalles de implementación son:
- Programado para correr en un hilo de ejecución paralelo, por lo que no bloqueará la ejecución de la aplicación en la que corra el cliente.
- Respecto al manejo de paquetes de datos, tiene una lista en la que los va guardando, y el hilo que se ejecuta de forma paralela comprueba de forma constante si hay algo nuevo que enviar para enviarlo automáticamente.
- En caso de que se produzca alguna excepción inesperada (por ejemplo que el ServidorTCP se haya caído), el cliente lo detectará y tratará de volver a conectarse pasados 5 segundos.

Por lo que la idea del ClienteTCP consiste en arrancarlo y pasarle los paquetes que queramos enviar, que él se encargará de hacer esta operación sin necesidad de hacer más por nuestra parte. El funcionamiento es como sigue:

1. Para arrancarlo:
[code:1]ClienteTCP ctcp = new ClienteTCP("127.0.0.1", 11985);
ctcp.Run();[/code]
2. Para enviar los paquetes.
[code:1]Paquete aux;
aux.AgregarElemento("Hola mundo");
ctcp.EnviarPaquete(aux);[/code]
3. Para detenerlo.
[code:1]ctcp.Desconectar = true;[/code]

Otros:
De momento he liberado la librería en DLL, pero dentro de no mucho colgaré el código mejor explicado y en varias entregas (una por clase) en mi blog dedicado al mundillo de la programación.
No obstante, cualquier duda que os surja sobre el funcionamiento, tema que os gustaria ampliar, lo que sea, podéis dejármela en este hilo que en cuanto pueda os hago un feedback.

Un saludo y espero que os sea útil!.

#2

Escrito 20 enero 2010 - 03:28

Una pregunta: El diagrama de clases se puede ver con el XNA o lo hiciste con un programa a parte?

  • deviax

  • Methuselah

  • vida restante: 100%
  • Registrado: 23 mar 2005
  • Mensajes: 182
#3

Escrito 20 enero 2010 - 11:46

Hola NullPointerException,

El diagrama de clases le hice desde Visual Studio, en el Explorador de Soluciones pinchas con el botón derecho y seleccionas Ver diagrama de clases y te genera el diagrama de tu proyecto.

  • Ollydbg

  • Bahamut

  • vida restante: 100%
  • Registrado: 05 sep 2008
  • Mensajes: 6.259
#4

Escrito 21 enero 2010 - 20:18

Hola deviax

Tienes un M.P.

Contesta cuando puedas, sin prisas :)

Saludos.

  • deviax

  • Methuselah

  • vida restante: 100%
  • Registrado: 23 mar 2005
  • Mensajes: 182
#5

Escrito 21 enero 2010 - 21:08

Hello Ollydbg,

A tu petición os dejo aquí el enlace al proyecto con el código visible que hice para generar la librería.

Antes de publicarlo tenía intención de comentar un poco el código aunque tampoco entraña mucho misterio ya que lo hice lo más simple posible.

Como anexo al código os dejo estos dos enlaces:
- Simple TCP Client
- Simple TCP Server

La librería la hice basándome en esos ejemplos, introduciendo como cambios el uso de hilos para que el servicio no fuera bloqueante, la posibilidad de que se relanzaran en caso de que se produjera una excepción(muy típico cuando no se cierra bien una conexión) y la clase Paquete para encapsular la información y simplificar la transferencia de datos cliente-servidor.

Ya si queréis lo que hacemos es que si os surge alguna duda del código podemos ponerla por aquí y así me ahorro comentarlo.

Un saludo.

  • Ollydbg

  • Bahamut

  • vida restante: 100%
  • Registrado: 05 sep 2008
  • Mensajes: 6.259
#6

Escrito 25 enero 2010 - 19:56

Debo estar empanado, pero hay algo que no entiendo.

Para el server inicias un hilo (PerformWork) que se pone a la escucha para la recepción de los mensajes (paquetes)
Para el cliente inicias un hilo (PerfomWork) para el envío de los mensajes (paquetes).

¿Cómo pones el servidor para que éste también envíe mensajes (a un determinado cliente / todos los clientes) ?
¿Cómo pones el cliente para que éste también reciba mensajes (del servidor y/o de otro cliente en concreto o de todos los clientes) ?

Ya te digo que debo tener el día espeso pero no veo cómo hacerlo.

Por cierto, aprovechando el tema, no sé si conocías esto: Lidgren Network Library para .NET / XNA

Saludos.

  • deviax

  • Methuselah

  • vida restante: 100%
  • Registrado: 23 mar 2005
  • Mensajes: 182
#7

Escrito 25 enero 2010 - 21:16

Hola Ollydbg,

Tranquilo que no estás espeso!, como bien identificas el servidor no devuelve nada al cliente en el código que he dejado.

¿Cómo pones el servidor para que éste también envíe mensajes (a un determinado cliente / todos los clientes) ?
¿Cómo pones el cliente para que éste también reciba mensajes (del servidor y/o de otro cliente en concreto o de todos los clientes) ?


En el lado del servidor deberías tener corriendo un ServidorTCP y un ClienteTCP por cada jugador, y cada jugador debería tener un ClienteTCP y un ServidorTCP conectado a su correspondiente par en el servidor.

En esta idea, la aplicación que hace de servidor debería tener un array con los ServidorTCP y otro array con los ClienteTCP para por cada paquete recibido en un ServidorTCP, recorrer el array de ClienteTCP reenviándolo (quien dice array dice List y List, que es más cómodo ;) )

¿Por qué hacerlo así?
A parte de porque así es más genérico, abstracto y fácil de utilizar sin meterle mano al código, porque son un servidor y un cliente puros.

Hice otra versión en la que había una única clase ServidorClienteHíbrido que contenía la funcionalidad de ambas y lo único que había que definir era quien establecía la conexión con quien al inicio de la comunicación, y luego tenía métodos para obtener y enviar un paquete. Esta opción la descarté porque me pareció más interesante la independencia en la secuencia del envío/recepción de datos, me explico:

Si decides implementar que el estado de una partida le llega al jugador como respuesta del estado que le envías al servidor, entonces estás obligado a enviar información constantemente aunque tu estado no haya cambiado, para recibir un estado que puede ser que tampoco haya cambiado, por lo que generas un tráfico innecesario. En cambio si los canales de comunicación son diferentes sabes que sólo se envía información cuando se producen cambios y toda la información es relevante.


Como coletilla final, si os interesa puedo hacer una clase ServidorMasivoTCP que lo que haga sea administrar multiples ServidoresTCP y ClientesTCP como os indicaba arriba, para que veáis que la solución es tan sencilla como efectiva 8D .

Un saludo!

#8

Escrito 25 enero 2010 - 22:32

Ahora yo me planteo la cuestion de ¿Por que TCP y no UDP? Siempre he tenido entendido que el TCP es relativamente lento aunque sea más fiable, ¿has testeado la aplicación para comprobar velocidades? ¿Nº de paquetes perdidos, reenvios etc etc? Porque para mi una premisa en la comunicacion del juego seria velocidad mas que fiabilidad, porque se envia mucha informacion que tu puedes corregir en tu programa en vez de en el protocolo(esto es un caso hipotético).

Yo he utilizado ambos protocolos, pero en pequeños entornos asi que no he experimentado realmente las limitaciones de cada uno, asi que pregunto desde el desconocimiento.

  • deviax

  • Methuselah

  • vida restante: 100%
  • Registrado: 23 mar 2005
  • Mensajes: 182
#9

Escrito 25 enero 2010 - 23:17

Hola Cloud_1986,

Sobre este tema podría extenderme demasiado :D, pero...

¿Por que TCP y no UDP?


En líneas generales uno no es mejor que el otro, cada uno resuelve un problema distinto dentro del ámbito de la comunicación, pero es el que mejor se adapta a como resuelvo el problema. Te explico mi planteamiento.

... para mi una premisa en la comunicacion del juego seria velocidad mas que fiabilidad, porque se envia mucha informacion que tu puedes corregir en tu programa ...


Estoy de acuerdo contigo en que si se va a enviar mucha información conviene tener de tu lado la velocidad. Por ejemplo, en un shooter donde constantemente todos los jugadores envían las coordenadas al servidor y éste al resto de jugadores, una solución es UDP porque no pierdes tiempo en establecer/cerrar conexiones y además no generas tráfico adicional, cosa que TCP introduce al tener confirmación de recepción de información.

Si se pierde una coordenada no pasa nada, estás enviando muchas y alguna entrará y te ubicará no muy lejos de tu situación, luego desde la aplicación puedes meter un código que corrija la desviación y engañe a los jugadores simulando el movimiento que no recibió. Pero..., ¿qué pasa si disparas y eso no llega al servidor? ¿Vas a arriesgarte a que tu shooter no sea un shooter por confiar en UDP? Puedes hacer una solución en la que los mezcles, las coordenadas por UDP, y eventos más importantes y muy puntuales como disparos, granadas, etc por TCP.

Ahora voy a dar un paso lógico más que es como yo he implementado mi MMO. Partiendo de la idea de que para un evento importante no confiaría en UDP, ¿que pasa si convierto el movimiento en un evento importante?. En lugar de enviar coordenadas constantemente lo que podría hacer es enviarla una única vez diciendo "estoy en (X,Y) y he presionado el cursor derecha". De este modo con un sólo paquete todo el mundo sabe dónde estoy y a dónde voy, cada uno calcula mi movimiento sin que yo les sature con las coordenadas por las que paso ahorrándome mucho tráfico. Si genero un nuevo evento como pararme, ir para arriba, cambiar de arma.., vuelvo a enviar otro paquete y les actualizo con mi estado.

No sé si me habré explicado bien.., pero el caso es que con UDP estoy obligado a informar constantemente de mi estado sea el que sea y me arriesgo a perder información que puede ser muy relevante, y con TCP y transformando el enfoque del problema tengo la misma información y ahorro recursos en cuanto a tráfico. Por eso cojo TCP y no UDP.

En esta librería que os he colgado no he hecho un banco de pruebas tan minucioso, pero te puedo decir que en mi MMO implementé ambas soluciones (envío continuado de coordenadas por UDP y envío de movimientos por TCP) y finalmente se ha quedado en los movimientos por TCP, genero muchísimo menos tráfico y la lógica del programa para corregir desvíos de coordenadas es (y por mucho) más sencilla.


Siento haberme extendido :oops: pero creo que podía ser interesante el ejemplo, un saludo!.

  • deviax

  • Methuselah

  • vida restante: 100%
  • Registrado: 23 mar 2005
  • Mensajes: 182
#10

Escrito 07 febrero 2010 - 16:03

Buenas de nuevo forer@s,

Os dejo una nueva versión de la BibliotecaTCP, los cambios respecto a la versión anterior son:

- Modificado el código del servidor, ahora se puede desconectar obligando al cliente a cerrar la conexión.
- Modificado el código del cliente, ahora al construir el objeto se le pasa un intervalo de tiempo en milisegundos para que envíe paquetes cada X tiempo y así no tener el proceso comprobando de forma constante si hay que enviar un paquete.


Descarga

- Librería DLL
- Diagrama de clases


Instalación

1. Descargar y descomprimir la biblioteca.
2. Copiar los ficheros BibliotecaTCP.dll y BibliotecaTCP.XML del fichero comprimido descargado al directorio DEBUG de vuestro proyecto.
(NOTA: Si no metéis el XML os perderéis la descripción de los métodos y los parámetros que reciben)
3. Desde Visual Studio y con vuestro proyecto abierto, seleccionar en la barra de menú "Proyecto > Agregar referencia...", y en la pestaña de Examinar seleccionáis el BibliotecaTCP.dll que habéis pegado en vuestro directorio DEBUG.
Listo, ya podéis trabajar con él.


Detalles de uso

> Servidor TCP <
1. Para arrancarlo y atender por el puerto 11985:
[code:1]ServidorTCP stcp = new ServidorTCP(11985);
stcp.Run();[/code]
2. Para recibir los paquetes tenemos tres opciones:
[code:1]Paquete aux;
List lista;
aux = stcp.UltimoPaquete; //Devolvería el paquete recibido más reciente
aux = stcp.PrimerPaquete; //Devolvería el paquete recibido más antiguo
lista = stcp.TodosPaquetes; //Develve una lista con todos los paquetes ordenados de mayor a menor antigüedad (el 0 es el más antiguo)[/code]
3. Para detenerlo:
[code:1]stcp.Desconectar();
stcp.Quit();[/code]
4. Para comprobar si el servidor está arrancado (esperando a un cliente o atendiéndole):
[code:1]stcp.Activo; //Devuelve TRUE si está en marcha, FALSE si está detenido[/code]

> Cliente TCP <
1. Para arrancarlo especificando que el cliente se conectará al servidor 127.0.0.1, por el puerto 11985 y que se quieren enviar los paquetes cada 150 milisegundos:
[code:1]ClienteTCP ctcp = new ClienteTCP("127.0.0.1", 11985, 150);
ctcp.Run();[/code]
2. Para enviar los paquetes:
[code:1]Paquete aux;
aux.AgregarElemento("Hola mundo");
ctcp.EnviarPaquete(aux);[/code]
3. Para detenerlo:
[code:1]ctcp.Desconectar();
ctcp.Quit();[/code]
4. Para comprobar si el cliente está conectado a un servidor:
[code:1]ctcp.Activo; //Devuelve TRUE si está conectado, FALSE si está detenido[/code]


Este tema ha sido archivado. Esto significa que no puedes responder en este tema.
publicidad