I've done this when I was learning Java, something around 10 years ago. It works:
Constantes.java:
package jsc;
public interface Constantes {
public static final String MULTICAST_IP = "224.0.0.1";
public static final int MULTICAST_PORTA = 3333;
public static final String SEPARADOR = "[>>>]";
public static final int TAMANHO_MENSAGEM = 1024;
public static final long ESPERA = 3000;
public static final String ESTOUONLINE = "EstouOnline";
public static final String DESCONECTANDO = "Desconectando";
public static final String PRIVADO = "Privado";
}
ControladorThread.java
package jsc;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
public class ControladorThread extends Thread implements Constantes{
private MulticastSocket mcSocket;
private Main main;
private Vector<Usuario> listaUsuarios; // lista de usuários ativos
public ControladorThread(Main main){
super("ReceptoraThread_" + main.getNick());
listaUsuarios = new Vector<Usuario>();
listaUsuarios.add(new Usuario(main.getNick(), new Date().getTime()));
this.main = main;
try{
mcSocket = new MulticastSocket(MULTICAST_PORTA);
mcSocket.joinGroup(InetAddress.getByName(MULTICAST_IP));
} catch(IOException e){
e.printStackTrace();
}
}
public void run(){
while(true){
try{
byte[] buffer = receberPacote();
processar(buffer);
removerUsuariosOciosos();
atualizarListaUsuarios();
} catch(IOException e){
e.printStackTrace();
}
}
}
public byte [] receberPacote() throws IOException{
byte[] buffer = new byte[TAMANHO_MENSAGEM];
DatagramPacket pacote = new DatagramPacket(buffer, buffer.length);
mcSocket.receive(pacote);
return buffer;
}
public void processar(byte[] buffer){
String mensagem = new String(buffer);
mensagem = mensagem.trim();
StringTokenizer tokens = new StringTokenizer(mensagem, SEPARADOR);
String t1 = tokens.nextToken();
String t2 = tokens.nextToken();
if(t1.equals(ESTOUONLINE))
atualizarEstadoUsuario(t2);
else if(t1.equals(DESCONECTANDO))
desconectarUsuario(t2);
else if(t1.equals(PRIVADO)){
String t3 = tokens.nextToken();
String t4 = tokens.nextToken();
if(t3.equals(main.getNick())){
receberMensagemPrivada(t2, t4);
}
}
else
main.setTextoEntrada(t1 + " diz: " + t2);
}
public void receberMensagemPrivada(String deUsuario, String mensagem){
main.abrirChatPrivado(main.getNick(), deUsuario, mensagem);
}
public boolean atualizarEstadoUsuario(String nomeUsuario){
int pos;
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
Usuario uAux = (Usuario) i.next();
if(uAux.getNome().equals(nomeUsuario)){
pos = listaUsuarios.indexOf(uAux);
listaUsuarios.remove(uAux);
uAux.setTempoInicio(new Date().getTime());
listaUsuarios.add(pos, uAux);
return true;
}
}
listaUsuarios.add(new Usuario(nomeUsuario, new Date().getTime()));
return false;
}
public void removerUsuariosOciosos(){
Usuario usuario = null;
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
usuario = (Usuario) i.next();
if(new Date().getTime() - usuario.getTempoInicio() > ESPERA){
desconectarUsuario(usuario.getNome());
i = listaUsuarios.iterator();
}
}
}
public void desconectarUsuario(String nomeUsuario){
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
Usuario uAux = (Usuario) i.next();
if(uAux.getNome().equals(nomeUsuario)){
i.remove();
break;
}
}
}
public void atualizarListaUsuarios(){
Vector<String> sVector = new Vector<String>();
Usuario uAux = null;
System.out.println("\nOnline: ");
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
uAux = (Usuario) i.next();
System.out.print( uAux.getNome() + " ");
sVector.add(uAux.getNome());
}
main.setUsuariosOnline(sVector);
}
private class Usuario{
private String nome;
private long tempoInicio;
public Usuario(){}
public Usuario(String nome, long tempoInicio){
this.nome = nome;
this.tempoInicio = tempoInicio;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public long getTempoInicio() {
return tempoInicio;
}
public void setTempoInicio(long tempoInicio) {
this.tempoInicio = tempoInicio;
}
}
public void sair(){
try {
mcSocket.leaveGroup(InetAddress.getByName(MULTICAST_IP));
mcSocket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
EstouOnlineThread.java
package jsc;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class EstouOnlineThread extends Thread implements Constantes{
private MulticastSocket mcSocket;
private String nick;
private byte[] buffer;
public EstouOnlineThread(String nick){
super("EstouOnlineThread_" + nick);
this.nick = nick;
try {
mcSocket = new MulticastSocket();
} catch(IOException e) {
e.printStackTrace();
}
}
public void run(){
String saida = ESTOUONLINE + SEPARADOR + nick;
buffer = saida.getBytes();
while(true){
try{
DatagramPacket estouOnline = new DatagramPacket(buffer, buffer.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(estouOnline);
System.out.println(saida);
sleep(ESPERA);
}
catch(InterruptedException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
}
}
MensagemPrivadaFrame.java
package jsc;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
public class MensagemPrivadaFrame extends Frame implements Constantes{
private static final long serialVersionUID = 1L;
private TextArea entrada;
private TextField saida;
private String nomeJanela;
private String nick;
private String paraUsuario;
private MulticastSocket mcSocket;
private ActionListener saidaListener;
private WindowAdapter frameListener;
private boolean estouVivo; // indica que a janela ainda está ativa
public MensagemPrivadaFrame(String nick, String paraUsuario){
super("JSC - Chat com " + paraUsuario);
setIconImage(Toolkit.getDefaultToolkit().getImage("icone.4"));
this.nick = nick;
this.paraUsuario = paraUsuario;
this.nomeJanela = nick + paraUsuario;
try {
mcSocket = new MulticastSocket();
} catch (IOException e) {
e.printStackTrace();
}
iniciarComponentes();
estouVivo = true;
}
public void setNomeJanela(String nomeJanela){
this.nomeJanela = nomeJanela;
}
public String getNomeJanela(){
return nomeJanela;
}
public String getNick() {
return nick;
}
public void setNick(String nick) {
this.nick = nick;
}
public boolean estouVivo(){
return estouVivo;
}
public void iniciarComponentes(){
saidaListener = new ActionListener(){
public void actionPerformed(ActionEvent e){
TextField origem = (TextField) e.getSource();
enviarMensagem(origem.getText());
entrada.append("\n(" + nick + " diz) " + origem.getText());
origem.setText("");
}
};
frameListener = new WindowAdapter(){
public void windowClosing(WindowEvent e){
estouVivo = false;
dispose();
}
};
entrada = new TextArea("[JSC] Bate papo privado entre " + nick + " e " + paraUsuario + "\n");
entrada.setEditable(false);
saida = new TextField();
saida.addActionListener(saidaListener);
addWindowListener(frameListener);
setLayout(new BorderLayout());
int x = (int) (Math.random() * 500);
int y = (int) (Math.random() * 500);
setBounds(x, y, 400, 300);
System.out.println(x + " " + y);
add("Center", entrada);
add("South", saida);
setVisible(true);
saida.requestFocus();
}
public void setTextoEntrada(String texto){
entrada.append("\n" + texto);
entrada.setCaretPosition(entrada.getText().length());
}
public void enviarMensagem(String mensagem){
try{
mensagem = PRIVADO + SEPARADOR + nick + SEPARADOR + paraUsuario + SEPARADOR + mensagem;
byte[] bMensagem = mensagem.getBytes();
DatagramPacket pacote = new DatagramPacket(bMensagem, bMensagem.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(pacote);
}
catch(UnknownHostException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
}
Main.java
package jsc;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.ScrollPane;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.Vector;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
public class Main extends Frame implements Constantes{
private static final long serialVersionUID = 1L;
private TextArea entrada;
private TextField saida;
private JList usuariosOnline;
private ScrollPane usuariosOnlineScroll;
private WindowAdapter mainListener;
private ActionListener saidaListener;
private MouseAdapter listListener;
private MulticastSocket mcSocket; // soquete para multicasting
private Vector<String> listaUsuariosOnline; // lista com os nomes de usuários online
private Vector<MensagemPrivadaFrame> listaJanelasAbertas; // janelas de conversação privadas abertas
private String nick; // nome do usuário no chat
public void setNick(String nick){
this.nick = nick;
}
public String getNick(){
return nick;
}
public Main(String nick){
super("Java Socket Chat [" + nick + "]");
setIconImage(Toolkit.getDefaultToolkit().getImage("icone.1"));
this.nick = nick;
listaUsuariosOnline = new Vector<String>();
listaUsuariosOnline.add(nick);
listaJanelasAbertas = new Vector<MensagemPrivadaFrame>();
try{
mcSocket = new MulticastSocket();
}
catch(IOException e){
e.printStackTrace();
}
iniciarComponentes();
new EstouOnlineThread(nick).start();
new ControladorThread(this).start();
}
public void iniciarComponentes(){
mainListener = new WindowAdapter(){
public void windowClosing(WindowEvent e){
sair();
}
};
saidaListener = new ActionListener(){
public void actionPerformed(ActionEvent e){
TextField origem = (TextField) e.getSource();
enviarMensagem(origem.getText());
origem.setText("");
}
};
listListener = new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if( e.getClickCount() >= 2 ){
// abrir a janela para mensagens privadas e passar o id do usuário
JList jlAux = (JList) e.getSource();
String paraUsuario = (String) jlAux.getSelectedValue();
abrirChatPrivado(nick, paraUsuario, null);
}
}
};
usuariosOnline = new JList(listaUsuariosOnline);
usuariosOnline.setSize(new Dimension(60, 280));
usuariosOnlineScroll = new ScrollPane();
usuariosOnlineScroll.add(usuariosOnline);
entrada = new TextArea("Olá " + nick);
entrada.setEditable(false);
entrada.setSize(300,280);
saida = new TextField();
saida.addActionListener(saidaListener);
usuariosOnline.addMouseListener(listListener);
usuariosOnline.setMinimumSize(new Dimension(60, 250));
addWindowListener(mainListener);
setSize(400, 300);
setLayout(new BorderLayout());
add("North", new JLabel("Java Socket ChatO"));
add("Center", entrada);
add("South", saida);
add("East", usuariosOnlineScroll);
setVisible(true);
requestFocus();
}
public void enviarMensagem(String mensagem){
try{
mensagem = nick + SEPARADOR + mensagem;
byte[] bMensagem = mensagem.getBytes();
DatagramPacket pacote = new DatagramPacket(bMensagem, bMensagem.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(pacote);
}
catch(UnknownHostException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
private void desconectando(){
try{
String mensagem = "Desconectando" + SEPARADOR + nick;
byte[] bMensagem = mensagem.getBytes();
DatagramPacket pacote = new DatagramPacket(bMensagem, bMensagem.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(pacote);
}
catch(UnknownHostException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
public void abrirChatPrivado(String nick, String paraUsuario, String mensagem){
removerJanelasInativas();
if(nick.equals(paraUsuario)){
JOptionPane.showMessageDialog(null, "Você não pode abrir um janela de conversação para você mesmo!", "Burro!", JOptionPane.ERROR_MESSAGE);
return;
}
String nome = nick + paraUsuario;
MensagemPrivadaFrame janela = null;
for(Iterator i = listaJanelasAbertas.iterator(); i.hasNext();){
janela = (MensagemPrivadaFrame) i.next();
if(nome.equals(janela.getNomeJanela())){
System.out.println(nick + " - " + janela.getNomeJanela() + " - " + janela.toString());
janela.setTextoEntrada("(" + paraUsuario + " diz) " + mensagem);
//janela.requestFocus();
return;
}
}
janela = new MensagemPrivadaFrame(nick, paraUsuario);
if(mensagem != null)
janela.setTextoEntrada("(" + paraUsuario + " diz) " + mensagem);
listaJanelasAbertas.add(janela);
//janela.requestFocus();
}
public void removerJanelasInativas(){
MensagemPrivadaFrame janela = null;
for(Iterator i = listaJanelasAbertas.iterator(); i.hasNext(); ){
janela = (MensagemPrivadaFrame) i.next();
if( !janela.estouVivo()){
i.remove();
}
}
}
public void setTextoEntrada(String texto){
entrada.append("\n" + texto);
entrada.setCaretPosition(entrada.getText().length());
}
public void setUsuariosOnline(Vector<String> listaUsuariosOnline){
usuariosOnline.setListData(listaUsuariosOnline);
}
public void sair(){
desconectando();
dispose();
System.exit(0);
}
public static void main(String args[]){
String nick = JOptionPane.showInputDialog("Digite seu nome (max. 20 caracteres): ");
if(nick != null && !nick.equals("")){
if(nick.length() > 20)
nick = nick.substring(0, 20);
new Main(nick);
}
else
JOptionPane.showMessageDialog(null, "É necessário informar um nome para entrar no bate-papo");
//System.exit(0);
}
}
Nowadays I'm not proud of the code, but it really works.
Edit:
As some have suggested, I've made some code improvements (refactoring) and post the project on GitHub: https://github.com/jaumzera/javasocketchat