Estou enviando bastante dados de e para um servidor, para um jogo que estou criando.
Atualmente, envio dados de localização como este:
sendToClient((("UID:" + cl.uid +";x:" + cl.x)));
sendToClient((("UID:" + cl.uid +";y:" + cl.y)));
sendToClient((("UID:" + cl.uid +";z:" + cl.z)));
Obviamente, ele está enviando os respectivos valores X, Y e Z.
Seria mais eficiente enviar dados como este?
sendToClient((("UID:" + cl.uid +"|" + cl.x + "|" + cl.y + "|" + cl.z)));
networking
joehot200
fonte
fonte
Respostas:
Um segmento TCP possui muita sobrecarga. Quando você envia uma mensagem de 10 bytes com um pacote TCP, você realmente envia:
resultando em 42 bytes de tráfego para transportar 10 bytes de dados. Portanto, você utiliza apenas menos de 25% de sua largura de banda disponível. E isso ainda não explica a sobrecarga que os protocolos de nível inferior, como Ethernet ou PPPoE, consomem (mas é difícil estimar porque há muitas alternativas).
Além disso, muitos pacotes pequenos sobrecarregam roteadores, firewalls, comutadores e outros equipamentos de infraestrutura de rede; portanto, quando você, seu provedor de serviços e seus usuários não investem em hardware de alta qualidade, isso pode se transformar em outro gargalo.
Por esse motivo, você deve tentar enviar todos os dados disponíveis ao mesmo tempo em um segmento TCP.
Sobre o tratamento da perda de pacotes : Quando você usa o TCP, não precisa se preocupar com isso. O protocolo em si garante que todos os pacotes perdidos sejam reenviados e os pacotes sejam processados em ordem, para que você possa assumir que todos os pacotes enviados chegarão ao outro lado e eles chegarão na ordem em que você os enviar. O preço para isso é que, quando houver perda de pacotes, seu player sofrerá um atraso considerável, porque um pacote descartado interromperá todo o fluxo de dados até que seja novamente solicitado e recebido.
Quando isso ocorre, você sempre pode usar o UDP. Mas então você precisa encontrar sua própria solução para e mensagens perdidas fora de ordem (que, pelo menos, garantias de que as mensagens que não chegam, chegam completa e sem danos).
fonte
Um grande (dentro da razão) é melhor.
Como você disse, a perda de pacotes é o principal motivo. Os pacotes geralmente são enviados em quadros de tamanho fixo; portanto, é melhor ocupar um quadro com uma mensagem grande do que 10 quadros com 10 pequenos.
No entanto, com o TCP padrão, isso não é realmente um problema, a menos que você o desative. (Ele é chamado algoritmo de Nagle e, para jogos, você deve desativá-lo.) O TCP aguardará um tempo limite fixo ou até que o pacote esteja "cheio". Onde "cheio" é um número ligeiramente mágico, determinado em parte pelo tamanho do quadro.
fonte
recv()
chamada para cadasend()
chamada, que é o que a maioria das pessoas procura. Usando um protocolo que garante isso, como o UDP faz. "Quando tudo que você tem é TCP, tudo parece um stream"Todas as respostas anteriores estão incorretas. Na prática, não importa se você faz uma
send()
chamada longa ou váriassend()
chamadas pequenas .Como Phillip afirma, um segmento TCP possui alguma sobrecarga, mas como programador de aplicativos, você não tem controle sobre como os segmentos são gerados. Em termos simples:
O sistema operacional é totalmente gratuito para armazenar em buffer todos os seus dados e enviá-los em um segmento ou pegar o longo e dividi-lo em vários pequenos segmentos.
Isso tem várias implicações, mas a mais importante é que:
O raciocínio por trás disso é que o TCP é um protocolo de fluxo . O TCP trata seus dados como um longo fluxo de bytes e não tem absolutamente nenhum conceito de "pacotes". Com
send()
você adiciona bytes a esse fluxo erecv()
obtém bytes do outro lado. O TCP fará buffer agressivo e dividirá seus dados sempre que achar necessário, para garantir que eles cheguem ao outro lado o mais rápido possível.Se você deseja enviar e receber "pacotes" com TCP, é necessário implementar marcadores de início de pacote, marcadores de comprimento e assim por diante. Que tal usar um protocolo orientado a mensagens como o UDP? O UDP garante que uma
send()
chamada se converta em um datagrama enviado e em umarecv()
chamada!fonte
recv()
, é necessário criar seu próprio buffer para compensar isso. Eu o classificaria na mesma dificuldade em implementar a confiabilidade sobre o UDP.while(1) { uint16_t size; read(sock, &size, sizeof(size)); size = ntoh(size); char message[size]; read(sock, buffer, size); handleMessage(message); }
(omitir o tratamento de erros e leituras parciais por questões de brevidade, mas isso não muda muito). Fazer issoselect
não adiciona muito mais complexidade e, se você estiver usando o TCP, provavelmente precisará armazenar em buffer as mensagens parciais. A implementação de confiabilidade robusta sobre UDP é muito mais complicada do que isso.Muitos pacotes pequenos estão bem. De fato, se você estiver preocupado com a sobrecarga do TCP, basta inserir um
bufferstream
que colete até 1500 caracteres (ou seja qual for sua MTUs TCP, melhor solicitá-la dinamicamente) e lide com o problema em um só lugar. Isso poupa a sobrecarga de ~ 40 bytes para cada pacote extra que você teria criado.Dito isto, ainda é melhor enviar menos dados e a criação de objetos maiores ajuda lá. É claro que é menor enviar
"UID:10|1|2|3
do que enviarUID:10;x:1UID:10;y:2UID:10;z:3
. De fato, também neste ponto você não deve reinventar a roda, use uma biblioteca como protobuf que pode diminuir dados como esses para uma sequência de 10 bytes ou menos.A única coisa que você não deve esquecer é inserir um
Flush
comando no seu fluxo em locais relevantes, porque assim que você parar de adicionar dados ao seu fluxo, ele poderá esperar infinito antes de enviar qualquer coisa. Realmente problemático quando seu cliente está aguardando esses dados e seu servidor não envia nada de novo até que o cliente envie o próximo comando.Perda de pacote é algo que você pode afetar aqui, marginalmente. Cada byte que você enviar pode ser potencialmente corrompido e o TCP solicitará automaticamente uma retransmissão. Pacotes menores significam uma chance menor de que cada pacote seja corrompido, mas, como aumentam a sobrecarga, você envia ainda mais bytes, aumentando ainda mais as chances de um pacote perdido. Quando um pacote é perdido, o TCP armazena em buffer todos os dados subsequentes até que o pacote ausente seja reenviado e recebido. Isso resulta em um grande atraso (ping). Embora a perda total de largura de banda devido à perda de pacotes possa ser insignificante, o ping mais alto seria indesejável para jogos.
Resultado: envie o mínimo de dados possível, envie pacotes grandes e não escreva seus próprios métodos de baixo nível para fazê-lo, mas conte com bibliotecas e métodos conhecidos como
bufferstream
e protobuf para lidar com o trabalho pesado.fonte
bufferstream
é trivial, por isso chamei de método. Você ainda deseja manipulá-lo em um só lugar e não integrar sua lógica de buffer ao seu código de mensagem. Quanto à serialização de objetos, duvido muito que você obtenha algo melhor do que as milhares de horas de trabalho que outras pessoas colocam lá, mesmo se você tentar, sugiro fortemente que você compare sua solução com implementações conhecidas.Embora eu seja um neófito da programação em rede, gostaria de compartilhar minha experiência limitada adicionando alguns pontos:
Com relação às medições, as métricas que devem ser consideradas são:
Como mencionado, se você descobrir que não está limitado em um sentido e pode usar o UDP, faça isso. Existem algumas implementações baseadas em UDP, por isso você não precisa reinventar a roda ou trabalhar contra anos de experiência e experiência comprovada. Tais implementações que vale a pena mencionar são:
Conclusão: como uma implementação UDP pode ter um desempenho superior (por um fator de 3x) ao TCP, faz sentido considerá-lo, uma vez que você identificou seu cenário como sendo UDP. Esteja avisado! Implementar a pilha TCP completa sobre o UDP é sempre uma má ideia.
fonte