Prévia do material em texto
Universidade Federal de Santa Catarina - UFSC Campus Araranguá Centro de Ciências, Tecnologias e Saúde - CTS Departamento de Computação - DEC Disciplina: DEC7558 – Sistemas Distribuídos Turma: 07655 Alunos: Nathan Vieira Marcelino, Vinicius Camozzato Vaz, Willer Lucas Barata Castanheti Relatório: Batalha naval em sistema Distribuído Objetivo O trabalho proposto foi o de construção de um sistema distribuído para a aplicação de um jogo de batalha naval, composto por três elementos: um servidor, e dois cliente, cada qual podendo ser executado em uma máquina distinta na rede. O projeto foi desenvolvido em linguagem de programação Python utilizando protocolo TCP de comunicação. Todos os softwares e bibliotecas necessárias para a correta execução do programa estão detalhados no final deste documento. Descrição O jogo assim como um jogo de batalha naval convencional consiste em dois tabuleiro, um para cada jogador, onde os jogadores estabelecem as posições de seis navios em uma matriz 10 por 10, e tentam “acertar” o navio do inimigo através de chutes aleatórios de coordenadas referentes a posição no tabuleiro do rival. Quem acertar a posição de todos os navios inimigos primeiro ganha o jogo. Desenvolvimento do ambiente gráfico do cliente Com o uso da biblioteca Pygame, construímos uma interface onde o jogador pode interagir com o mouse para selecionar onde ele quer posicionar o seu navio. Cada posição escolhida é marcada em vermelho, e quando todas as seis posições já estiverem sido definidas, o jogador pode digitar F para que a matriz gerada seja enviada ao servidor Caso o jogador decida trocar um navio de posição, basta que ele selecione algum previamente estabelecido para que aquela posição seja limpa e ele tenha direito a reposição. Se ele tentar colocar mais de 6 navios no mapa, o sistema retorna uma mensagem de erro. Quando tudo estiver certo e as posições forem finalizadas, a mensagem de aguarde irá aparecer. Então, o cliente enviará ao servidor uma matriz 10x10 onde cada posição com navio terá seu elemento definido em -1 e todos os demais elementos definidos em 1. No final do relatório, logo após as considerações finais, consta um exemplo de execução. Programação da entidade Servidor Para o início do projeto do servidor, é necessários importar a biblioteca socket responsável pela comunicação entre dois processos. Além a biblioteca socket, utilizamos a biblioteca NumPy para que fosse possível realizar operações com arrays multidimencionais, no caso da nossa aplicação: a matriz contendo as informações de posição de cada barco/alvo do tabuleiro. A biblioteca threading é importada para que seja possível a comunicação com os dois clientes (jogadores) simultaneamente. Após todas a bibliotecas necessárias serem definidas, é iniciada a criação do socket e seus parâmetros de IP e porta do servidor. Quando o socket é criado, utilizamos o parâmetro socket.AF_INET para definir o tipo de família de endereço que será permitido na comunicação entre as entidades para o tipo IPv4, e o parâmetro socket.SOCK_STREAM para definir o protocolo de comunicação TCP. Agora que o socket está finalmente criado, é utilizado a função bind() para definir um relacionamento entre o socket criado e o endereço estabelecido para que seja possível verificar a disponibilidade do endereço e da porta A função listen() é utilizada para deixar o socket em modo passivo, “escutando” as conexões realizadas, e nela é passado um parâmetro de modo a definir o número máximo de conexões enfileiradas permitidas: dois clientes. Um socket setado em modo passivo é chamado informalmente de servidor. Programação da entidade Cliente Na programação do cliente, é necessário setar o endereço e porta onde o servidor está sendo executado Então, é verificada e estabelecida a conexão com o servidor através do código abaixo. Se tudo estiver correto, a função s.connect() cria a conexão client-servidor possibilitando existir a troca de mensagens O cliente por fim envia uma mensagem confirmando a conexão ao dest, nosso servidor. A mensagem antes de ser enviada precisa ser convertida de padrão Unicode para bytes, e para isso utilizamos a função encode() Estabelecendo a comunicação clientes-servidor O servidor utiliza threads para que seja possível gerenciar a conexão e a troca de mensagens de dois clientes simultâneamente Quando um cliente escolhe a posição de todos os navios no grid descrito no item 3 deste relatório, ele envia a matriz codificada em bytes ao servidor. Cliente enviando a matriz de posições para o servidor O servidor recebe a matriz codificada e atribui um número de conexão ao cliente, e fica aguardando a entrada de uma nova matriz referente ao segundo jogador para que seja possível iniciar o jogo servidor recebendo a matriz posição do cliente 1 O segundo jogador faz o mesmo procedimento que o primeiro e envia para o servidor servidor recebendo a matriz posição do cliente 2 No código abaixo é possível entender como o servidor define o número da conexão e como ele cria os threads Basicamente, ele executa um loop até que haja dois jogadores conectados. Para cada jogador que entra, o contador Connection_Numb é incrementado. Quando o contador for igual a zero, significa que nenhum cliente realizou a conexão. A partir do momento em que o primeiro cliente faz uma conexão, o servidor executa a função threading.Thread() que tem como parâmetro target a função client_thread do código (linha 43, server.py) Quando um segundo cliente estabelecer conexão, o loop cria o ultimo thread e é finalizado. Agora o servidor possui as duas matrizes com as posições dos navios de cada cliente/jogador. Resultados e dificuldades Devido a falta de experiência do grupo com a linguagem Python, muitos problemas surgiram durante a execução do trabalho, o que resultou em horas perdidas nas tentativas de correção de bugs, erros e funções sintaticamente mal implementadas. Devido a essa falta de experiência e a problemas ainda não solucionados até a data de entrega dessa atividade, não concluímos a implementação do jogo: ficou faltando implementar a troca de tiros entre os dois jogadores. De todo modo, a lógica para que o jogo fluísse como esperado é apresentada a seguir. A partir do momento que o servidor possui as duas matrizes-posições dos jogadores (isso já está implementado), ele distribui um token ao jogador que realizou a primeira conexão e informa que já é possível que ele possa iniciar a jogada. Quando o jogador envia as coordenadas ao servidor, o servidor as compara com a matriz do jogador rival, e informa se houve um acerto ou não do alvo, modificando a matriz para que seja possível indicar que aquela coordenada não está mais disponível. Caso haja um acerto, um contador é somado. Então, havendo acerto ou não, o servidor retira o token do primeiro cliente e o transfere para o segundo, de modo que o mesmo possa também realizar o seu “tiro”, e novamente o token é repassado. O primeiro contador que atingir a marca de 6 navios atingidos indica o ganhador da partida. Uma vez que o sistema utiliza threads para gerenciar conexões simultâneas, esse token impossibilita que os jogadores realizem dois tiros sequenciais, ou que, os jogadores realizem seus tiros simultâneamente, ou fora de ordem, ou até mesmo que os tiros sejam disparados antes que a matriz no servidor seja atualizada, ou seja, o token garante que os processos aconteçam na ordem cronológica em que se espera em um jogo de turnos: um jogador de cada vez. Ferramentas e bibliotecas necessárias Para que o projeto seja executado corretamente, é necessário que as ferramentas a seguir estejam instaladas e disponíveis. Listamos a ferramenta e o link para download Python3 Site oficial: https://www.python.org/downloads/ Pip3 (geralmente já incluso no python, porém algumas versões não possuem) Site oficial: https://pip.pypa.io/en/stable/installing/NumPy Site oficial: https://www.scipy.org/install.html Pygame Site oficial: https://www.pygame.org/wiki/GettingStarted Conclusão e considerações finais Mesmo com a não finalização do projeto, ele foi de suma importância para o aprendizado e aprimoramento das habilidades em programação dos integrantes do grupo, uma vez que tivemos nosso primeiro contato com a linguagem python e adquirimos experiência e conhecimento para que os mesmos erros cometidos na execução do trabalho não sejam repetidos em futuros projetos. Tivemos a oportunidade de entender melhor e trabalhar com protocolo TCP e Threads, e estudar na prática os conteúdos abordados pelo professor em sala de aula. Exemplo de execução Passo 1) Executar o servidor O servidor é iniciado e informa que está pronto para conexões Passo 2) Executar primeiro cliente O jogador 1 informa as posições e digita F para finalizar A matriz é montada e codificada em bytes para ser enviada ao servidor O servidor recebe a matriz codifica, decodifica para padrão Unicode e informa que está esperando um segundo jogador passo 3) executar o segundo cliente Obs: Basta compilar o mesmo código duas vezes para que se crie dois clientes. O servidor então recebe a segunda matriz Com os dois mapas definidos, o jogo pode ser iniciado.