Skip to content

Trabalho da disciplina de Redes de Computadores (SSC0641), lecionada pelo Professor Rodolfo Ipolito Meneguette, para o curso de Engenharia de Computação - USP São Carlos.

License

Notifications You must be signed in to change notification settings

ISS2718/LiveChat

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LiveChat

Resumo.

Trabalho da disciplina de Redes de Computadores (SSC0641), lecionada pelo Professor Rodolfo Ipolito Meneguette, para o curso de Engenharia de Computação - USP São Carlos.

LiveChat é uma aplicação Servidor-Cliente de chat de "LiveStream" com comunicação UDP. Nele temos funções de usuários moderadores (mute, encerramento de chat) e "sussurros" para todos os usuários.

Autores.

Tabela de conteúdos.


1. Composição do projeto.

O LiveChat é composto de um Servidor e n-Clinetes. Em nosso servidor é recebido todas as mensagens, realizado o monitoramento de conexões e mensagens enviadas, além de, enviar a todos outros clientes. Os clientes são aqueles que enviarão as mensagens no chat e recebem as mensagens eviadas por todos os outros clientes.

2. Pré-Requisitos.

  • GCC;
  • GNU make;

3. Instalação dos Pré-requisitos.

3.1. Ubuntu.

3.1.1 GCC.

Para compilar e executar o LiveChat devemos ter o GCC. Primeiro, verifique se o gcc já não está instalado. Execute

$ gcc -v

Caso não haja, execute

$ sudo apt install build-essential

Se o gcc foi instalado com êxito, então ao executar $ gcc -v, deve aparecer a última versão do gcc. Caso não esteja você pode forçar a instalação do gcc com

$ sudo apt install gcc

3.1.2. GNU make.

Caso você tenha instalado o build-essential o GNU make, também conhecido como Makefile, provavelmente já está instalado. Para descobrir se realmente esté instalado, basta seguir um processo parecido ao do gcc. Execute

$ make -v

Caso não haja, execute

$ sudo apt install make

Se o make foi instalado com êxito, então ao executar $ make -v, deve aparecer a última versão do make.

3.2. Outras Distribuições Linux.

Para outras distribuições linux teste se não há o gcc e o make já instalado com

$ gcc -v
$ make -v

Caso não tenha um, ou os dois, basta procurar por pacotes equivalentes ao do ubunto. Pois certamente existirá.

4. Guia de execução.

4.1 Servidor e Clientes.

  1. Compile todos os arquivos executando (na pasta raiz do LiveChat):
$ make all
  1. Abra o servidor executando:
$ make runServidor
  1. Com o servidor aberto, é preciso mantelo aberto em um terminal o tempo inteiro de execução.

  2. Agora em um terminal separado execute um Cliente:

$ make runCliente
  1. Agora basta preencher o formulário de entrada de usuário com seu nome real, nome de usuário e IP de conexão com o servidor.

  2. No campo de IP caso esteja utilizando um Cliente local (na mesma maquina) utilize localhost, para facilitar o processo. Mas caso queria utilizar o Cliente em máquinas separadas (mas na mesma rede) utilize o 2° - IP que o Servidor printou na tela ao ser iniciado.

4.2. Funções dos usuários.

1. Sussurro: um usuário pode enviar uma mensagem privada a outro usuário, executando a linha

/priv usuário mensagem

2. Fechar cliente: um usuário pode se desconectar do servidor, executando a linha

/quit

3. Mostrar clientes: um usuário pode obter uma lista com todos os clientes conectados, com indicação especial aos moderadores, executando a linha

/users

4. Poder de moderador: um moderador por dar poder de moderador a outro usuário, executando a linha

/mod usuario true

e, ainda, retirá-lo com

/mod usuario false
  • OBS: essa função só pode ser executada por um moderador. Também não é possível dar poder de moderador a um usuário já moderador, ou retirar de um usuário que não é moderador.

5. Mutar: um moderador pode mutar um outro usuário e impedir que suas mensagens sejam enviadas aos usuários conectados. Executa-se

/mute usuário true

Para desmutá-lo, executa-se

/mute usuário false
  • OBS: essa função só pode ser executada por um moderador. Também não é possível mutar um usuário já mutado, ou desmutar um usuário já desmutado.

6. Fechar servidor: um moderador pode fechar o servidor. Esse comando desconecta todos os outros usuários e finaliza o servidor. Executa-se:

/fservidor
  • OBS: essa função só pode ser executada por um moderador.

5. Resumo do Servidor.

5.1. Lista de clientes conectados.

Conforme os clientes são conectados, eles passam a ser identificados, dentro do servidor, como uma estrutura básica de uma lista encadeada.

typedef struct cliente {
struct sockaddr_in endereco;
InfoCliente registro;
struct cliente * proximo;
}Cliente;

Assim, é guardado o seu endereço, seu registro de informações e o cliente encadeado. O endereço é formado pela combinação do endereço de IP, a porta e a família da conexão.

O registro de informações, por sua vez, é formado pelo nome do cliente, o nome de usuário, se moderador e se mutado.

typedef struct InfoCliente {
char nome[TAM_NOME];
char user[TAM_USER];
int moderador;
int mute;
}InfoCliente;

5.2. Socket.

O socket é criado utilizando a biblioteca <sys/socket.h>, em C. Primeiro, cria-se o socket através da função

socket(AF_INET, SOCK_DGRAM, 0);

onde AF_INET indica o uso do protocolo IPV4 e SOCK_DGRAM indica o uso de uma conexão por datagramas (UDP). O último argumento utiliza o protocolo padrão da familía de endereço. Se o socket não for criado, a função retorna -1.

Depois que o socket é criado, configura-se o endereço do servidor, selecionando sua família de endereços, porta e endereço de IP.

struct sockaddr_in endServidor;

endServidor.sin_family = AF_INET;
endServidor.sin_port = htons(PORTA);
endServidor.sin_addr.s_addr = htonl(INADDR_ANY);

Por fim, é preciso conectar o servidor ao socket, utilizando a função bind.

bind(rSocket, (struct sockaddr *) &endServidor, sizeof(struct sockaddr))

Caso alguma informação do endereço do servidor esteja sendo utilizada, ou não exista, a conexão entre servidor e o socket retornará -1, logo, não sendo capaz de ligar o servidor.

5.3. Conexão do cliente.

Uma vez que o servidor está conectado ao socket, o servidor passa a poder receber mensagens. Quando um cliente se conecta ao servidor e manda uma mensagem, ela não é imediatamente enviada a todos os conectados, pois é preciso registrar o cliente. Do ponto de vista do servidor, o cliente é apenas um endereço, sem informações de registro. Para se comunicar com outros usuários, é preciso que o cliente envie, primeiro, uma mensagem específica necessária para cadastrá-lo na lista de clientes.

5.3.1. Identificação dos clientes e moderadores.

A primeira mensagem enviada por um cliente deve ser

#nome_cliente#nome_usuario

Ao receber essa linha, o servidor separa o nome do cliente (mRegistro[0]) e o nome de usuário (mRegistro[1]), criando um registro de informações do cliente. Todo usuário entra no servidor desmutado e não-moderador.

strcpy(registro.nome, mRegistro[0]);
strcpy(registro.user, mRegistro[1]);
registro.moderador = 0;
registro.mute = 0;

Os moderadores são identificados pelo seu nome de usuário, que são fixos. Logo após criar o registro, através da primeira mensagem, o servidor verifica se o nome de usuário é de um moderador, trocando o valor de moderador no registro, se positivo.

#define MODERADOR1 "ikuyorih9"
#define MODERADOR2 "iss2718"

if(strcmp(registro.user, MODERADOR1) == 0 || strcmp(registro.user, MODERADOR2) == 0)
infoCliente.moderador = 1;

Um cliente é criado, juntando as informações de registro com o endereço recebido pelo servidor no envio da mensagem. Logo em seguida, o cliente é salvo na lista de clientes conectados.

Cliente * novoCliente = (Cliente*) malloc(sizeof(Cliente));
novoCliente->endereco = endereco;
novoCliente->registro = registro;
novoCliente->proximo = NULL;

5.3.2. Envio de mensagens aos clientes.

Quando o cliente já está conectado, ele passa a ser buscado na lista através do seu endereço, obtendo as informações do cliente diretamente da lista. Assim, a sua mensagem é formada por

Nome(usuário): mensagem.

Obviamente, essa mensagem será enviada aos outros usuários somente se o mensageiro não estiver mutado, ou seja

if(!clienteMensageiro->registro.mute){
enviaMensagemParaOutros(rSocket, mensagemCompleta,endMensageiro, listaClientes);
}

5.4. Identificação das funções.

As funções são identificadas através de

/função param1 param2

Toda mensagem enviada para o servidor passa pelo filtro da identificação das funções. Assim, uma linha de mensagem é separada em três strings:

char funcao[TAM_MSG];
char param1[TAM_MSG];
char param2[TAM_MSG];

Caso a string funcao esteja vazia, após a passagem do filtro, então a mensagem não indica uma função e pode ser enviada aos clientes. Os parâmetros podem ser vazios, pois algumas funções não necessitam de parâmetros para serem executados. Assim, uma série de if-else direciona a execução de cada função.

if(strcmp(funcao, SUSSURRO) == 0){...}
else if(strcmp(funcao, MOD) == 0){...}
else if(strcmp(funcao, FECHAR_CLIENTE) == 0){...}
else if(strcmp(funcao, FECHAR_SERVIDOR) == 0){...}
else if(strcmp(funcao, MUTE) == 0){...}
else if(strcmp(funcao, MOSTRAR_CLIENTES) == 0){...}

6. Resumo do Cliente.

6.1. Usuário

Ao executar o cliente primeiramente é requisitado as informações do cliente como nome real, nome de usuário e o ip do servidor em quele irá se conectar. Todas essas informções do cliente são salvas na estrutura de InfoCliente (também utilizada pelo servidor). Já o ip é processado pela fução

struct hostent *gethostbyname(const char *name);

da biblioteca <netdb.h>, para conseguir as informações do servidor a partir do endereço IP digitado pelo usuário. Essas informações são nome do servidor, e o endereço IP "Real".

6.2. Socket

Assim como no servidor o socket é criado utilizando a biblioteca <sys/socket.h>, em C. Primeiro, cria-se o socket através da função

socket(AF_INET, SOCK_DGRAM, 0);

onde AF_INET indica o uso do protocolo IPV4 e SOCK_DGRAM indica o uso de uma conexão por datagramas (UDP). O último argumento utiliza o protocolo padrão da familía de endereço. Se o socket não for criado, a função retorna -1.

Depois que o socket é criado, configura-se o endereço do servidor, selecionando sua família de endereços, porta e endereço de IP.

struct sockaddr_in endServidor;

endServidor.sin_family = AF_INET;
endServidor.sin_port = htons(PORTA);
endServidor.sin_addr.s_addr = htonl(INADDR_ANY);

Por fim, é preciso conectar o cliente ao servidor (pelo socket), utilizando a função connect.

connect(socket_c, (struct sockaddr*) &socket_endereço, sizeof(struct sockaddr))

Caso o socket do servidor esteja indisponível, sem poder ouvir novas conexões, ou a sua conexão tenha sido recusada por qualquer outro motivo, a função connect() retornará -1 e não será capaz de estabelecer uma conexão com o servidor.

6.3. Primeira Mensagem

A primeira mensagem enviada são as informações do cliente (nome e uruário). Essas informações devem estar no formato de string específico, que só é feito para essa ocasião, #Nome#Usuário. Após esse envio é feito a verificação de estado de envio para garantir que essa mensagem inicial tenha sido enviada.

6.4. Threads

São criadas duas threads utilizando a biblioteca <pthread.h>, uma para envio de mensagens e outra para recebimento de mensagens. Para que não utilizar de variáveis globais foi criado a estrutura de parâmetros para pthreads.

typedef struct  {
    int* socket_c;
    InfoCliente* cliente;
} ParametrosPthreads;

Aqui tem um ponteiro para as informações do cliente e para o socket que foram criados e salvos em memória local na função principal. Essa estrutura é necessária para o envio por parâmetro pois as funções que executam em uma thread, da biblioteca <pthread.h>, recebe 1 parâmetro do tipo pontreiro void (void *).

Após a configuração dos parâmetros que serão enviados criamos as threads própriamente ditos e a variável para estabelecer os atributos de cada thread.

pthread_t thread_envia, thread_recebe;
pthread_attr_t confg_thread;

A thread de envio é configurada para ser Joinable, da seguinte maneira

pthread_attr_init(&confg_thread);

inicia os atributos da thread com os padrões iniciais e depois

pthread_attr_setdetachstate(&confg_thread, PTHREAD_CREATE_JOINABLE);

dizemos que ela será Joinable. E por fim criamos nossa trhead enviando os parâmetros da thread e mostrando a função que ela executará.

pthread_create(&thread_envia, &confg_thread, enviar, (void *) &p);

Da mesma mameira confuiguramos a thread de recebimento só que para não ser Joinabe

pthread_attr_init(&confg_thread);
pthread_attr_setdetachstate(&confg_thread, PTHREAD_CREATE_DETACHED);

Logo após criamos nossa thread passando os parâmetros e mostrando qual função ela executará.

pthread_create(&thread_recebe, &confg_thread, receber, (void *) &p);

6.5. Função de Envio

void* enviar(void * arg)

A função de envio, primeiramente, converte o parâmetro void * recebido para o tipo ParametrosPthreads *. Logo após, cria o buffer de envio e inicia o seu loop. O loop sempre inicia zerando o buffer com a função bzero(bufferEnviar, TAM_MSG). Em seguida, requisita a mensagem do usuário a ser enviada ao chat. Quando o usuário digita uma mensagem, a função envia ao servidor e verifica se não ocorreu nenhum erro. Por fim, analisa se foi um comando de desconexão. Caso tenha sido um comando de desconexão, informa que irá encerrar o cliente e encerra a thread, voltando à thread principal que força o encerramento da thread de recebimento e encerra o programa. Caso contrário, o loop continua.

6.6. Função de Recebimento

void* receber(void * arg)

A função de recebimento, assim como a de envio, começa convertendo o parâmetro void * recebido para o tipo ParametrosPthreads *. Em seguida, é criado o buffer de recebimento e uma variável para o tamanho real da mensagem. O loop sempre inicia zerando o buffer com bzero(bufferReceber, TAM_MSG), depois tenta receber uma mensagem do servidor. Caso ele consiga, e não gere nenhum erro, é colocado o caractere de fim de string no final da string, para garantir que esse caractere exista, e começamos as verificações da mensagem recebida.

Essa mensagem pode ser um comando de erro, comando de desconexão ou uma mensagem de outro usuário. Caso seja um comando de erro é printado o ERRO e o encerramento é forçado gerando um erro de execução. Caso seja um comando de desconexão é printado em uma mensagem de sistema que "O servidor te desconectou" e o encerramento é forçado sem gerar um erro de conexão. Caso seja uma mensagem de usuário, sussuro ou não, ele é printado no chat do cliente e com isso o loop reseta e continua.

7. Tecnologias.

As seguintes ferramentas foram usadas na construção do projeto:

8. Licença.

MIT License © Beatriz Lomes da Silva, Hugo H. Nakamura, Isaac S. Soares, João Pedro Gonçalves, Nicholas Estevão P. de O. Rodrigues Bragança.

Zap2

About

Trabalho da disciplina de Redes de Computadores (SSC0641), lecionada pelo Professor Rodolfo Ipolito Meneguette, para o curso de Engenharia de Computação - USP São Carlos.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published