Dúvida sobre organização de listeners utilizando socket.IO

54 views
Skip to first unread message

Dias

unread,
May 27, 2011, 8:34:48 PM5/27/11
to NodeJS Brasil
E aí pessoal da lista, beleza?
Eu conheci o node.js ano passado a partir da palestra do Emerson na
RubyconfBR, porém só tive tempo de começar a brincar com node.js e
socket.IO a poucos meses.
Pretendo futuramente criar uma engine para jogos isométricos
multiplayer utilizando node.js do lado do server e socket.IO na
comunicação com o client. Inclusive já fiz o primeiro protótipo (por
enquanto você só consegue caminhar com outros players em um mapinha e
conversar através de um mini-chat) e pretendo assim que corrigir
alguns bugs deixar o código disponível no github para o pessoal que tá
começando poder ter mais um exemplo de código para se guiar.
E então, como eu tive que fazer uma manipulação já mediana de sockets
para deixar rodando legal (por exemplo, escutar quando um player novo
conecta, ou quando ele caminha pelo mapa ou quando ele falou alguma
coisa no chat) eu me deparei com uma dúvida e tenho quase certeza que
não estou fazendo da melhor maneira. Por isso queria a ajuda de vocês,
mais experientes do que eu, para me auxiliar qual seria a melhor
maneira de organizar estes meus listeners.

(Vou colocar o código resumido aqui senão ficaria um post ainda maior)

Do lado do cliente:
socket = new io.Socket(null, {port: 8080});
socket.connect();
socket.on('connect', function(client){
//Você conectou.
});
socket.on('message', function(obj) {
if ('players_buffer' in obj) {
//Buffer contendo outros players que já estavam online.
}
else if ('player_move' in obj) {
//Algum player se moveu
}
else if ('player_chat' in obj) {
//Algum player falou algo no chat
}
});

Como vocês podem perceber, eu sempre tenho que fazer um tratamento do
JSON dentro do socket.on('message') pra verificar sobre o que se trata
a socket que eu to recebendo. Do mesmo jeito para receber as sockets e
enviar o broadcast pra todos no lado do server. E isso tende a ficar
gigantesco, por isso acredito que eu não esteja fazendo da maneira
mais correta.

Alguma sugestão pessoal?
Desde já agradeço.
Abraços.

diego nunes

unread,
May 27, 2011, 11:46:53 PM5/27/11
to nod...@googlegroups.com
Dias, essa é uma dúvida comum quando se trabalha com sockets,
justamente por "parecer errado". Na verdade existem várias formas de
tentar resolver isso, mas é preciso tomar cuidado. Você pode
automatizar a chamada e o processamento das requisições sem ter que
fazer um monte de "ifs", implementando algum tipo de automatização com
um loop.
É preciso entender que um socket TCP é uma comunicação "raw",
crua, sem nenhum tipo de tratamento. É só um punhado de bytes indo pra
lá e pra cá, diferente do HTTP (que já é um protocolo por cima do
TCP), então não há como direcionar a mensagem recebida diretamente
para um método. Você pode implementar seu próprio protocolo de
comunicação, mas é importante ter em mente que quanto mais "verbose"
for a sua transmissão, menos concorrência seu servidor vai aguentar,
então mantenha-se sumário.
Um exemplo de automatização bem simples e super leve seria algo assim...

var actionReceivers = {
'move': function (data) {
},
'buffer': function (data) {
},
'chat': function (data) {
}
};

socket.on('message', function(obj) {
for each (action in obj) { if (action in actionReceivers) {
actionReceivers[action](obj[action]);
} }
};

É importante ter uma "camada" intermediária como receiver pra
evitar que algum player possa achar uma falha no seu protocolo de
transmissão e ordenar a execução de comandos arbitrários no computador
dos outros jogadores. Com essa "camada de tradução", você limita os
comandos apenas a um subset dos disponíveis, diferente do que
aconteceria se você checasse diretamente a existência dos métodos na
classe do usuário ou algo assim. Obviamente ela não precisa funcionar
dessa forma (pode ser um dict com a lista de métodos disponíveis, por
exemplo), mas lembre-se sempre de não deixar que uma mensagem do
server execute algo que não seja explicitamente autorizado no cliente.

Amplexos.

--
diego nunes
http://dnun.es

William Dias

unread,
May 28, 2011, 12:55:42 PM5/28/11
to nod...@googlegroups.com
Muito obrigado pela resposta esclarecedora e pelas sugestões Diego.
No caso o que eu estou fazendo no momento para tentar evitar envios
indesejados de sockets é deixar as informações do player do lado do
servidor. Só que tem algumas coisas que eu ainda não sei qual solução
seria melhor... por exemplo, quando um player caminhar, eu não posso
deixar apenas o client fazer o cálculo de menor caminho e ao final da
animação enviar uma socket para o server dizendo qual a sua nova
posição, isso iria gerar uma avalanche de players com speed hack :/
Provavelmente terei que fazer o mesmo cálculo de caminho do lado do
server e sempre verificar se as 2 coordenadas estão sincronizadas...
Outra coisa seria o cara enviar para o server uma socket de
movimentação contendo uma coordenada do outro extremo do mapa do jogo,
algo que ele ainda não teria acesso, pra isso eu teria tratar toda
hora do lado do server se aquela coordenada que ele passou está ao seu
alcance. Tem várias paradas caóticas pra se pensar nesse projeto.
Abraços.

--
William Régis Drawanz Dias
Aluno de Ciência da Computação - UFPel
Grupo de Aplicações de Inteligência Artificial

Ricardo Tomasi

unread,
May 28, 2011, 6:18:36 PM5/28/11
to NodeJS Brasil
Olá Dias,

A versão 0.7 do socket.io tem uma solução pra isso, custom events.
Nela tu vai poder usar

socket.on('chatMessage', function(){
// chat
})

socket.on('move', function(){
// movimento
})

Já fica bem mais fácil né? Deve sair nos próximos dias.

diego nunes

unread,
May 29, 2011, 1:49:27 AM5/29/11
to nod...@googlegroups.com
2011/5/28 William Dias <diasfo...@gmail.com>:

> quando um player caminhar, eu não posso
> deixar apenas o client fazer o cálculo de menor caminho e ao final da
> animação enviar uma socket para o server dizendo qual a sua nova
> posição, isso iria gerar uma avalanche de players com speed hack :/
A movimentação é feita de que forma? Por clique? Ou por setas do
teclado? Por clique você envia ao servidor a posição de destino e essa
posição é repassada aos outros clientes, que vão refazer o cálculo da
movimentação localmente. Se esse processamento no lado do cliente for
excessivo para a sua aplicação, você pode fazer o servidor calcular o
path e enviar o path inteiro (através de pares de coordenadas x,y)
para o cliente só "andar", sem precisar fazer nenhum cálculo. Isso
aumenta a transmissão de dados no socket, então é um trade-off a ser
considerado com calma, dependendo do load do servidor e de quão
complexa é a implementação do lado do cliente.
No caso de comunicação por setas, depende do tipo de movimento.
- Se você só puder parar num "full stop" (parar em um determinado
quadrante "x,y" e não em uma posição arbitrária em pixels), é simples
enviar apenas as "mudanças" de quadrante e o servidor vai validar
facilmente se o novo quadrante é vizinho do anterior ou não.
- Se você tiver movimentação livre, você pode enviar ao servidor
updates de posição periódicos (digamos, a cada meio segundo), junto
com um timestamp, e validar, no servidor, se durante o intervalo
indicado (entre os dois timestamps) é possível, dentro da velocidade
atual do player, se mover o quanto ele indicou. Se não for, você deixa
o personagem dele andar o máximo possível, apenas, de forma que
qualquer "gap" em algum bug de cálculo ou lentidão no socket vai se
auto-corrigir ao longo dos próximos updates no qual o personagem andar
menos do que o máximo permitido.

Essas são só sugestões e existem diversas técnicas diferentes. Eu
fiz um jogo isométrico multiplayer com chat de load mediano (60
usuários simultâneos por sala), mapas de 120x120 quadrantes (mais ou
menos 64 megapixels quadrados) com movimentação pelo teclado e usei
essa última técnica com grande sucesso. Há muita documentação por aí,
inclusive papers de algumas grandes empresas (a Bungie, por exemplo,
tem artigos periódicos sobre game development, cobrindo de uso de
memória a eficiência de protocolo, passando por infraestrutura e
técnicas de rede--recomendo muito que dê uma olhada).


> Provavelmente terei que fazer o mesmo cálculo de caminho do lado do
> server e sempre verificar se as 2 coordenadas estão sincronizadas...

A regra de ouro dos jogos que rodam no cliente (flash e js) é
lembrar que a não ser que a informação seja validada no servidor, ela
é intrinsecamente insegura e deve ser verificada de alguma forma.


> Outra coisa seria o cara enviar para o server uma socket de
> movimentação contendo uma coordenada do outro extremo do mapa do jogo,
> algo que ele ainda não teria acesso, pra isso eu teria tratar toda
> hora do lado do server se aquela coordenada que ele passou está ao seu
> alcance.

Só agora li aqui embaixo, isso, hahaha. É exatamente o que falei
lá em cima. Nesse caso, você só o deixa andar o quanto pode. No
próximo update ele vai enviar de novo uma posição impossível, mas você
só anda o quanto dá. E assim pro diante. Se for obviamente abusivo,
você pode pensar em auto-moderação do sistema e kickar o usuário.


> Tem várias paradas caóticas pra se pensar nesse projeto.

Nem vou falar das opções de melhoria de eficiência no lado do
servidor com cache de posicionamento e envio periódico em lote pra não
adicionar mais loucura :P Mas considere isso, futuramente. Em vez de
enviar os updates imediatamente, pense em um timer periódico pra
enviar em lote. Vai aumentar o número máximo de pessoas simultâneas na
sala enormemente.
Pense que a meta é que nenhum usuário receba mais do que 20
updates (mensagens no socket) por segundo--idealmente não mais do que
10.


2011/5/28 Ricardo Tomasi <ricar...@gmail.com>:


> A versão 0.7 do socket.io tem uma solução pra isso, custom events.

Só lembrando que o que está acontecendo é que o Socket.IO está
implementando um protocolo próprio pra isso: é mais um overhead na
comunicação. Toda "facilidade" pro desenvolvimento traz consigo o peso
da _genericidade_ da implementação, já que ela é voltada a um público
muito amplo e precisa atender a necessidades diversas.
Se não for muito dispendioso e sua aplicação precisar de um
desempenho excelente, recomendo que você implemente seu próprio
protocolo mínimo.

William Dias

unread,
May 29, 2011, 9:38:59 AM5/29/11
to nod...@googlegroups.com
É ricardo, eu tinha lido isso ontem no group do socket.io mas como o
diego citou, não se sabe se alguma quantia considerável de performance
possa se perder nesse processo.
Valeu mesmo por dividir sua experiência diego, me fez prestar atenção
em várias coisas.
Sobre a movimentação eu vou escrever no próximo email no notebook, pq
pelo ipod fica tenso.

--

William Dias

unread,
May 29, 2011, 4:59:56 PM5/29/11
to nod...@googlegroups.com
Então diego, a minha movimentação está sendo feita através do clique
do mouse e pretendo fazer o processo mais ou menos como você
descreveu, ao player clicar em uma posição nova, enviar para o server
um socket com o novo destino (já tendo a posição atual salva no server
é claro) e realizar o mesmo cálculo de deslocamento tanto no client
quanto no server. Assim, a cada "passo" do player ele enviaria uma
socket para o server com a sua posição atual, assim o server poderia
verificar se ambos estão sincronizados.

Se o cara quiser cheatar e enviar as sockets com um valor diferente do
que deveria, o servidor vai ver que os valores não estão batendo e vai
enviar uma socket pro client avisando algo do tipo "cara sua posição
atual não pode ser X é Y" e colocaria o player no seu devido lugar Y,
e se o cara editasse essa parte do código por exemplo e insistisse no
envio de sockets mal intencionadas levaria um kick.

A questão é, conseguir avaliar o que é lag e o que é cheat, isso eu
não consegui formar uma idéia aceitável. :/

Abraços.

Emerson Macedo

unread,
May 30, 2011, 1:52:48 PM5/30/11
to nod...@googlegroups.com
Como o Diego já disse, você precisa tomar cuidado com esses inputs do usuário. No caso da movimentação, uma possibilidade é o usuário enviar apenas a tecla que ele apertou, ao invés de enviar a posição que ele está se movendo. Dessa forma, você (i.e. o servidor) diz para onde ele se move e não ele diz para onde está se movendo (o que favorece os espertos burlarem).

2011/5/29 William Dias <diasfo...@gmail.com>



--
Emerson Macedo
http://codificando.com

diego nunes

unread,
Jun 1, 2011, 9:14:20 AM6/1/11
to nod...@googlegroups.com
2011/5/30 Emerson Macedo <emer...@gmail.com>:

> No caso da movimentação, uma possibilidade é o usuário enviar
> apenas a tecla que ele apertou, ao invés de enviar a posição que ele está se
> movendo. Dessa forma, você (i.e. o servidor) diz para onde ele se move e não
> ele diz para onde está se movendo (o que favorece os espertos burlarem).

Nesse caso a gente pode ter mais problema com o acúmulo de
comandos por causa de lag. Pra evitar isso, às vezes o desenvolvedor
decide que o fluxo é:
1) o usuário aperta a tecla;
2) a tecla apertada é enviada para o servidor;
3) o servidor recebe o comando e responde dizendo a posição atual do
usuário e a próxima posição;
4) ao receber a mensagem de movimentação do servidor, o personagem
finalmente se move nessa tela.

Esse fluxo é absolutamente à prova de hacks e, inclusive, ele é
auto-corretivo (auto-manutenção) no caso de hacks locais (que só
influenciariam a posição do usuário na tela dele mesmo). O problema é
que nesse caso temos o pior tipo de lag: o "input lag", que é o tempo
entre a ação do usuário e uma reação do aplicativo. Esse input lag
varia de acordo com a conexão do usuário e pode chegar a mais de meio
segundo, o que é pouco razoável. A vantagem é que todos os usuários
ficam mais sincronizados e as diferenças de posicionamento no mundo
virtual ficam quase irrisórias. De qualquer forma é uma abordagem que
funciona para poucos casos.

Temo que estejamos fugindo um bocado do escopo da lista. Querem
falar a respeito em private?

William, vou responder ao seu último email diretamente, pra não
poluir a lista de Node com assuntos de desenvolvimento de jogos.

Amplexos.

William Dias

unread,
Jun 1, 2011, 2:50:04 PM6/1/11
to nod...@googlegroups.com
Boa idéia Diego, fico aguardando seu e-mail.

Muito obrigado a todos que estão me ajudando nesta discussão.

--

Renato Elias

unread,
Jun 1, 2011, 4:45:29 PM6/1/11
to nod...@googlegroups.com
Que nada continua que o papo ta muito bom !
Renato Elias
Tel: +55 (011) 7694-3872

2011/6/1 William Dias <diasfo...@gmail.com>:

Renato Elias

unread,
Jun 1, 2011, 4:46:32 PM6/1/11
to nod...@googlegroups.com
Como O Irapuan Martinez fala na ArqHP, só é o offtopic, se o cara
coloca offtopic no titulo, senão, ta dentro. aqui é uma lista p/ falar
sobre programação voltada a node, e poxa este é um problema que
envolve JS parte cliente e Node + JS parte server.

Renato Elias
Tel: +55 (011) 7694-3872

2011/6/1 Renato Elias <renato...@gmail.com>:

William Dias

unread,
Jun 6, 2011, 10:21:22 PM6/6/11
to nod...@googlegroups.com
E aí pessoal, coloquei uma versão de demonstração para download no github.
https://github.com/diaswrd/Singd (É a primeira vez que hospedo algum
projeto no github, se vocês tem alguma dica que julgam importante, por
favor não deixem de enviar para o meu e-mail).

Essa é a versão ainda sem as optimizações debatidas aqui no tópico
(até pq quero que o código seja útil para quem está começando e ainda
não tem uma idéia clara de como node e socket.io funcionam). Por
enquanto nessa versão, os usuários podem apenas caminhar pelo mapinha
e conversar entre si. Se souberem de alguém que está começando e quer
ter um exemplo de um código que não seja de chat para aprender,
repassem o link (eu sei que algumas coisas podem estar feias ou
erradas no código pois afinal é o primeiro projeto em node que eu
trabalho, por isso estou completamente disponível para ouvir suas
críticas e sugestões). E me desculpem também pela falta de testes no
código, estou lendo sobre node-unit no momento e pretendo incorporá-lo
ao projeto em breve.

Estou fazendo uma baita refatoração no projeto, cuidando a prevenção
de cheats, broadcast seletivo e outras melhorias que foram descritas
aqui, principalmente pelo amigo Diego. Ah eu testei no ipod 4 aqui e
rodou, só quando vc der zoom vai perceber um baita lag na
movimentação, estou fazendo novos testes de performance pra melhorar
isso também.

Abraços pessoal! Qualquer coisa é só me chamar aqui ou no twitter @diaswrd

heron medeiros

unread,
Jun 6, 2011, 10:29:37 PM6/6/11
to nod...@googlegroups.com
Bem legal. Vou fazer o fork e tentar ajudar nos testes.

--
Heron Medeiros
___________________________________
Developer


William Dias

unread,
Jun 7, 2011, 3:40:56 PM6/7/11
to nod...@googlegroups.com
Valeu Heron! :D

--

Reply all
Reply to author
Forward
0 new messages