Estoy enviando bastantes datos hacia y desde un servidor, para un juego que estoy haciendo.
Actualmente envío datos de ubicación como este:
sendToClient((("UID:" + cl.uid +";x:" + cl.x)));
sendToClient((("UID:" + cl.uid +";y:" + cl.y)));
sendToClient((("UID:" + cl.uid +";z:" + cl.z)));
Obviamente está enviando los respectivos valores X, Y y Z.
¿Sería más eficiente enviar datos como este?
sendToClient((("UID:" + cl.uid +"|" + cl.x + "|" + cl.y + "|" + cl.z)));
networking
joehot200
fuente
fuente
Respuestas:
Un segmento TCP tiene bastante sobrecarga. Cuando envía un mensaje de 10 bytes con un paquete TCP que realmente envía:
resultando en 42 bytes de tráfico para transportar 10 bytes de datos. Por lo tanto, solo utiliza menos del 25% de su ancho de banda disponible. Y eso aún no tiene en cuenta la sobrecarga que consumen los protocolos de nivel inferior como Ethernet o PPPoE (pero estos son difíciles de estimar porque hay muchas alternativas).
Además, muchos paquetes pequeños ejercen más presión sobre los enrutadores, firewalls, conmutadores y otros equipos de infraestructura de red, por lo que cuando usted, su proveedor de servicios y sus usuarios no invierten en hardware de alta calidad, esto podría convertirse en otro cuello de botella.
Por esa razón, debe intentar enviar todos los datos que tenga disponibles a la vez en un segmento TCP.
Con respecto al manejo de la pérdida de paquetes : cuando usa TCP no necesita preocuparse por eso. El protocolo en sí mismo asegura que los paquetes perdidos se reenvíen y los paquetes se procesen en orden, por lo que puede suponer que todos los paquetes que envíe llegarán al otro lado, y llegarán en el orden en que los envió. El precio de esto es que cuando hay pérdida de paquetes, su reproductor experimentará un retraso considerable, porque un paquete descartado detendrá todo el flujo de datos hasta que se vuelva a solicitar y recibir.
Cuando esto es un problema, siempre puedes usar UDP. Pero entonces usted necesita para encontrar su propia solución de y fuera de orden mensajes perdidos (al menos garantiza que los mensajes que no llegan, llegan completa y en buen estado).
fuente
Uno grande (dentro de lo razonable) es mejor.
Como dijiste, la pérdida de paquetes es la razón principal. Los paquetes generalmente se envían en cuadros de un tamaño fijo, por lo que es mejor tomar un cuadro con un mensaje grande que 10 cuadros con 10 mensajes pequeños.
Sin embargo, con TCP estándar, esto no es realmente un problema, a menos que lo desactive. (Se llama algoritmo de Nagle , y para los juegos debe deshabilitarlo). TCP esperará un tiempo de espera fijo o hasta que el paquete esté "lleno". Donde está "lleno" sería un número ligeramente mágico, determinado en parte por el tamaño del cuadro.
fuente
recv()
llamada por cadasend()
llamada, que es lo que la mayoría de las personas está buscando. Sin embargo, usar un protocolo que garantice esto, como UDP. "Cuando todo lo que tienes es TCP, todo parece una transmisión"Todas las respuestas anteriores son incorrectas. En la práctica, no importa si emite uno largo
send()
llamada o variassend()
llamadas pequeñas .Como afirma Phillip, un segmento TCP tiene cierta sobrecarga, pero como programador de aplicaciones, no tiene control sobre cómo se generan los segmentos. En lenguaje sencillo:
El sistema operativo es completamente gratuito para almacenar en búfer todos sus datos y enviarlos en un segmento, o tomar el largo y dividirlo en varios segmentos pequeños.
Esto tiene varias implicaciones, pero la más importante es que:
El razonamiento detrás de esto es que TCP es un protocolo de flujo . TCP trata sus datos como una secuencia larga de bytes y no tiene absolutamente ningún concepto de "paquetes". Con
send()
usted agrega bytes a esa secuencia, y conrecv()
usted obtiene bytes del otro lado. TCP almacenará en búfer agresivamente y dividirá sus datos donde lo considere conveniente para asegurarse de que sus datos lleguen al otro lado lo más rápido posible.Si desea enviar y recibir "paquetes" con TCP, debe implementar marcadores de inicio de paquete, marcadores de longitud, etc. ¿Qué tal usar un protocolo orientado a mensajes como UDP? ¡UDP garantiza que una
send()
llamada se traduzca en un datagrama enviado y en unarecv()
llamada!fuente
recv()
, debe hacer su propio almacenamiento en búfer para compensar eso. Lo clasificaría en la misma dificultad que implementar la confiabilidad sobre UDP.while(1) { uint16_t size; read(sock, &size, sizeof(size)); size = ntoh(size); char message[size]; read(sock, buffer, size); handleMessage(message); }
(omitiendo el manejo de errores y las lecturas parciales por brevedad, pero no cambia mucho). Hacer estoselect
no agrega mucha más complejidad y, si está utilizando TCP, es probable que necesite almacenar los mensajes parciales de todos modos. Implementar una fiabilidad robusta sobre UDP es mucho más complicado que eso.Muchos paquetes pequeños están bien. De hecho, si le preocupa la sobrecarga de TCP, simplemente inserte un
bufferstream
que recopile hasta 1500 caracteres (o lo que sea que sea su MTU de TCP, lo mejor es solicitarlo dinámicamente) y lidiar con el problema en un solo lugar. Hacerlo le ahorra la sobrecarga de ~ 40 bytes por cada paquete adicional que de otro modo hubiera creado.Dicho esto, aún es mejor enviar menos datos, y construir objetos más grandes ayuda allí. Por supuesto, es más pequeño enviar
"UID:10|1|2|3
que enviarUID:10;x:1UID:10;y:2UID:10;z:3
. De hecho, también en este punto no debería reinventar la rueda, use una biblioteca como protobuf que pueda disminuir datos como ese a una cadena de 10 bytes o menos.Lo único que no debes olvidar es insertar un
Flush
comando en su flujo en ubicaciones relevantes, porque tan pronto como deje de agregar datos a su flujo, puede esperar infinito antes de enviar algo. Realmente problemático cuando su cliente está esperando esos datos, y su servidor no enviará nada nuevo hasta que el cliente envíe el siguiente comando.La pérdida del paquete es algo que puede afectar aquí, marginalmente. Cada byte que envíe puede estar dañado y TCP solicitará automáticamente una retransmisión. Los paquetes más pequeños significan una menor posibilidad de que cada paquete se corrompa, pero debido a que se suman a los gastos generales, envía aún más bytes, lo que aumenta aún más las posibilidades de un paquete perdido. Cuando se pierde un paquete, TCP almacenará en el búfer todos los datos subsiguientes hasta que el paquete faltante se reenvíe y reciba. Esto da como resultado un gran retraso (ping). Si bien la pérdida total en el ancho de banda debido a la pérdida del paquete puede ser insignificante, el ping más alto sería indeseable para los juegos.
En pocas palabras: envíe la menor cantidad de datos posible, envíe paquetes grandes y no escriba sus propios métodos de bajo nivel para hacerlo, sino que confíe en bibliotecas y métodos conocidos como
bufferstream
y protobuf para manejar el trabajo pesado.fuente
bufferstream
es trivial, por eso lo llamé un método. Todavía desea manejarlo en un lugar y no integrar su lógica de búfer con su código de mensaje. En cuanto a la serialización de objetos, dudo mucho que obtenga algo mejor que las miles de horas de trabajo que otros ponen allí, incluso si lo intenta, le sugiero que compare su solución con las implementaciones conocidas.Aunque soy un neófito en la programación de redes, me gustaría compartir mi experiencia limitada agregando algunos puntos:
En cuanto a las mediciones, las métricas que deben considerarse son:
Como se mencionó, si descubres que no estás limitado en un sentido y que puedes usar UDP, hazlo. Existen algunas implementaciones basadas en UDP, por lo que no tiene que reinventar la rueda o trabajar contra años de experiencia y experiencia comprobada. Tales implementaciones que vale la pena mencionar son:
Conclusión: dado que una implementación UDP podría superar (por un factor de 3x) a una TCP, tiene sentido considerarla, una vez que haya identificado su escenario como compatible con UDP. ¡Ten cuidado! Implementar la pila TCP completa sobre UDP siempre es una mala idea.
fuente