Chat Multicast UDP: uma tarefa de aula!
Em uma de minhas aulas de Desenvolvimento de Aplicações Distribuídas recebi a tarefa de criar um CHAT do tipo “Command Line” utilizando Multicast e UDP. Como saiu uma coisa legal então resolvi colocar o código a disposição, que claro pode ser melhorado. Foi utilizado Thread e apenas um arquivo foi criado. Para que exista comunicação, execute o arquivo em cada computador que se deseja efetuar a comunicação. A tarefa foi em grupo e os outros integrantes são citados no arquivo. A classe principal eu resolvi fazer uma referencia ao tipo de coleção utilizada para mensagens pendentes e ao próprio exercício, que ficou algo como My LinkedList Chat Multicast.
package br.com.chat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
/**
* Classe responsavel por cuidar da interacao do usuario. A classe obtem o nome
* do usuario, mostra as mensagens pendentes recebedidas por uma thread e entao
* pede se o usuario deseja digitar algo para enviar. A classe tambem instancia
* a thread de controle do receive.
*
* @authors Vinicius Knob, Edipo Federle and Marcela Pedrini
*
*/
public class MyLinkedListChatMulticast {
/*
* Local de mensagens pendentes, ainda nao lidas. Todas as mensagens
* recebidas sao guardadas nesse local que eh um array ordenado para depois
* serem lidas. Esse array eh static pois precisa ser compartilhado com uma
* thread.
*/
private static LinkedList<String> mensagensPendentes = new LinkedList<String>();
public static void main(String[] args) {
int porta = 9999;
InetAddress enderecoMulticast;
MulticastSocket socket;
String nomeUsuario;
byte[] dados = new byte[140];
DatagramPacket datagrama;
try {
/*
* Criando o chat
*/
enderecoMulticast = InetAddress.getByName("233.99.77.22");
socket = new MulticastSocket(porta);
socket.joinGroup(enderecoMulticast);
/*
* Obtendo o nome do usuario do chat
*/
nomeUsuario = lerString("Digite seu nome: ");
/*
* Sendo gentil com o usuario
*/
System.out.println("\n***** Ola " + nomeUsuario
+ ", voce esta online! *****\n");
/*
* Enviando apresentacao a todos do chat
*/
dados = criarStringConsole(nomeUsuario, "Estou Online!");
datagrama = new DatagramPacket(dados, dados.length,
enderecoMulticast, porta);
socket.send(datagrama);
/*
* Isso eh um bloco de limpesa do array de bytes.
*/
for (int i = 0; i < dados.length; i++) {
dados[i] = 0;
}
/*
* Instanciando a thread para cuidar do recebimento de mensagens
*/
new GestorReceive(socket, datagrama, nomeUsuario).start();
/*
* Loop que ira mostrar as mensagens pendentes e oferecer ao usuario
* para digitar algo. O boolean controla a vida do laco.
*/
boolean viver = true;
while (viver) {
/*
* Usuario tem a chance de digitar algo para enviar
*/
String mensagem = lerString(">> "); // bloqueante
/*
* Quando o usuario nao deseja digitar nada, entao ele tera que
* digitar um codigo de escape "w!", se nao for usado esse
* codigo, eh por que o usuario digitou algo que deva ser
* enviado a todos.
*/
if (!mensagem.equals("w!")) {
/*
* Se a mensagem for igual a "exit", significa que o usuario
* quer sair, entao a mensagem eh alterada para que seja
* enviado aos outros usuario algo mais decente = [USUARIO
* OFFLINE].
*/
if (mensagem.equals("exit")) {
mensagem = "[USUARIO OFFLINE]";
viver = false;
}
/*
* O que sera mostrado no console do usuario sera a hora
* atual, o nome do usuario que enviou a mensagem e a
* mensagem dele
*/
dados = criarStringConsole(nomeUsuario, mensagem);
datagrama = new DatagramPacket(dados, dados.length,
enderecoMulticast, porta);
socket.send(datagrama);
/*
* Isso eh um bloco de limpesa do array de bytes.
*/
for (int i = 0; i < dados.length; i++) {
dados[i] = 0;
}
}
/*
* Verifica a existencia de mensagens pendentes e as mostra
*/
verificarMensagensPendentes(nomeUsuario);
}
} catch (Exception exc) {
throw new RuntimeException(exc.toString());
}
}
/**
* Metodo que cria a string que deve ser mostrada para o usuario no console.
*
* @param nomeUsuario
* - Nome do usuario.
* @param mensagem
* - Mensagem enviada.
* @return Um array de bytes formatado.
*/
public static byte[] criarStringConsole(String nomeUsuario, String mensagem) {
byte[] mensagemConsole = new byte[140];
/*
* Montando uma string buffer para conter a mensagem
*/
StringBuffer stringMensagem = new StringBuffer();
stringMensagem.append("[");
stringMensagem.append(horaAtual());
stringMensagem.append("] ");
stringMensagem.append(nomeUsuario);
stringMensagem.append(" diz: ");
stringMensagem.append(mensagem);
mensagemConsole = stringMensagem.toString().getBytes();
return mensagemConsole;
}
/**
* Metodo para mostrar a hora atual no formato "HH:mm:ss"
*
* @return A hora atual formatada como HH:mm:ss
*/
public static String horaAtual() {
return new SimpleDateFormat("HH:mm:ss").format(new Date());
}
/**
* Metodo para ler uma String do teclado. Retorna a String lida ou refaz o
* metodo caso houve algum erro na leitura.
*/
public static String lerString() {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
String lido = br.readLine();
return lido;
} catch (IOException e) {
System.out.println("Erro ao ler a string! Digite novamente.");
return lerString();
}
}
/**
* Metodo que recebe e mostra uma mensagem, depois chama o metodo de ler uma
* nova String.
*/
public static String lerString(String mensagem) {
System.out.print(mensagem);
return lerString();
}
/**
* Metodo que verifica a existencia de mensagens pendentes. Esse metodo
* utiliza o metodo getMensagens() para esvaziar o array de mensagens caso
* exista algo. Nao retorna nada pois o que o metodo faz eh verificar as
* mensagens e ja mostra-las no console.
*/
public static synchronized void verificarMensagensPendentes(
String nomeUsuario) {
/*
* Buscando o array de mensagens pendentes
*/
LinkedList<String> mensagensParaVerificacao = new LinkedList<String>(
getMensagens());
/*
* Agui eh verificado se existem mensagens pendentes se existir, entao
* ele pegara uma a uma as mensagens ja ordenadas e mostrara no console
* para o usuario
*/
if (!mensagensParaVerificacao.isEmpty()) {
Iterator<String> i = mensagensParaVerificacao.iterator();
while (i.hasNext()) {
String textoRecebido = i.next();
/*
* Aqui eh verificado se na mensagem contem a palavra chave
* "EXIT", que indica que um usuario deseja encerrar o chat,
* tambem eh verificado se o usuario solicitante eh½ o mesmo que
* esta na maquina, para que nao exista conflito se a makina
* receber um exit de outro usuario.
*/
if ((textoRecebido.contains("[USUARIO OFFLINE]"))
&& (textoRecebido.contains(nomeUsuario))) {
System.exit(0);
} else {
System.out.println(textoRecebido.trim());
System.out.flush();
}
}
}
}
/**
* Metodo para pegar todas as mensagens pendentes. O metodo faz uma copia do
* array de mensagens e entao esvazia o array para que seja possivel receber
* novas mensagens sem existir as que ja foram lidas, sendo assim, cada vez
* que as mensagens forem pegas, nao serao mostradas as mensagens ja pegas
* anteriormente.
*
* @return Um array de todas as mensagens recebidas.
*/
public static LinkedList<String> getMensagens() {
LinkedList<String> copiaMensagensPendentes = new LinkedList<String>(
mensagensPendentes);
mensagensPendentes.clear();
return copiaMensagensPendentes;
}
/**
* Metodo para substituir o array existente por um novo array vazio para
* receber mensagens
*
* @param mensagens
* - O novo array de mensagens.
*/
public static void setMensagens(LinkedList<String> mensagens) {
mensagensPendentes = new LinkedList<String>(mensagens);
}
/**
* Metodo para armazenar uma mensagem como pendente no array de mensagens.
*
* @param mensagem
* - A mensagem para ser armazenada.
*/
public static synchronized void setMensagem(String mensagem) {
mensagensPendentes.add(mensagem);
}
}
/**
* Thread que ficara encarregada de controlar o recebimento de mensagens por
* receive. Essa Thread guarda as mensagens em um array que seria um local para
* mensagens pendentes, ainda nao lidas.
*
* @authors Vinicius Knob, Edipo Federle and Marcela Pedrini
*
*/
class GestorReceive extends Thread {
MulticastSocket socket;
String nomeUsuario;
byte[] dados = new byte[140];
DatagramPacket datagrama;
public GestorReceive(MulticastSocket skt, DatagramPacket dtg, String nmusr) {
socket = skt;
datagrama = dtg;
nomeUsuario = nmusr;
}
public void run() {
try {
/*
* Essa variavel vai controlar a vida da thread
*/
boolean viver = true;
/*
* Loop encarregado de receber mensagens e guarda-las no array de
* mensagens pendentes
*/
while (viver) {
/*
* Colocando um array de dados vazio sobre o array anterior
*/
datagrama.setData(dados);
/*
* Receive eh bloqueante
*/
socket.receive(datagrama);
dados = datagrama.getData();
String mensagem = new String(dados);
/*
* Aqui eh verificado se na mensagem contem a palavra chave
* "EXIT", que indica que um usuario deseja encerrar o chat,
* tambem eh verificado se o usuario solicitante eh o mesmo que
* esta na maquina, para que nao exista conflito se o makina
* receber um exit de outro usuario. Isso encerrara a vida da
* thread.
*/
if ((mensagem.contains("exit"))
&& (mensagem.contains(nomeUsuario))) {
viver = false;
}
MyLinkedListChatMulticast.setMensagem(mensagem);
/*
* Isso eh um bloco de limpesa do array de bytes.
*/
for (int i = 0; i < dados.length; i++) {
dados[i] = 0;
}
}
} catch (Exception exc) {
System.err.println(exc.toString());
}
}
}
