Reinstating this question with a bounty! I need an example that stays online, like a real instant messenger! It needs to always be ready to receive or send a message to an arbitrary address over an arbitrary port, using TCP. The program must not quit after sending/receiving a message.
Bounty goes to whoever can give the best example of a real, usable instant messenger.
Looking online, all resources I found are either useless tutorials, dead threads, dead tutorials, ancient examples, or tell the programmer to use external APIs. How can I create a basic instant messenger from the ground up, only using Java SE?
There must be a way to do this, and some sample code would be appreciated. It only needs to perform the simplest tasks: Check if a compatible client is online on another computer (IP will be provided by the user) and send a TCP packet to that client, which will receive and display its contents.
When this question was first asked and answered back in 2011, it was simply "Looking online, all resources I found are either useless tutorials, dead threads, or tell the programmer to use external APIs.". The provided links below met the criteria at the time. Further discussion follows in the comments.
First few Google results for "java socket chat":
Or from "java 8 chat client":
Many, many results following in the search. Pick one that suits your needs. You can even modify the Google search to only show results from the past year, if you wish.
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
Hm, I was tempted to direct you to a java implementation of a server implementing imap protocol (eg. gavamail). But this, of cources, might also qualify as "old" code and for sure would kill your expectation (for being a non-standard solution). Nevertheless it is a proper reference fulfilling your (terse) specification.
What do we have?
We need a solution that should be in java. It must implement an basic instant messaging system.
The later is problematic as it covers a really broad range of functionality. But "basic" seem to allow for a minimum solution.
So what is a minimum instant messaging system? Let's try with the following:
We also would need a policy of how a client would identify a proper server. Most trivial solution for the later aspect is using a "central" server with a well-known address. For more complex cases we would need to have the server and/or client functionality distributed across several instances and devise a scheme or policy for identifying the proper instance(s) for communication.
We leave out more complex semantics like having different users or messages being related to a system of categories or tags.
Now, we are down to having two components:
A server implementing two entry points:
POST_MESSAGE
receive a mesage form a client and store it for later retrieval
This immediatley is asking the question of where to store such messages (in a database or filesystem for persistency or simply within memory for a "messages live as long as the server is up" semantics)
LOOKUP_MESSAGE
select a suitable message from the stored ones (preferrably an unread one) and return to caller.
This could also return a set of messages (but think of restricting such set for cases where a caller has a severe backlog of messages)
It might be necessary to keep track of the messages already having been read, either by marking the messages or by maintaining seen status at the client. This could even be as simple as keeping time or ordinal of last message seen and send this information along with the LOOKUP_MESSAGE request.
A client needs to interact with a user on one hand and the service on the other hand.
It will take gat a new message from the user (likely on explicit request (e.g. send button) and call the POST_MESSAGE service at the related server.
It also will (likely regularly, could also be on explicit request (e.g. user is starting client)) poll the server for new messages. (Alternatively, you could devise a separate notification service that is used by the server to notify the client of new messages. What suits your "needs" is beyond your question.)
That's it.
So any example of a TCP based client/server application will form a perfect starting point for a straight implementation.
I should also mention that you could cut the specification logic within the client and delegate user interaction to a standard browser and implement the client application logic into a (web-)server instance (together or separate from the server part). Nevertheless, you still will have both (client/server) logical functionality according to above minimum specification.
Another aspect you should be aware of:
With some comments you mentioned "host" and "guest" attributions available in current messenger examples. This actually is a logical structure of a tagging system provided with those messengers. The messages are still sent from a client to a server and then being retrieved by other clients. Whether a client can see a message is determined by the client being eligible to the specific tag. E.g posting a message to a contact from yours (user b) just tags the message with the tag "for_user_b" and as such it is only visible to the poster and anybody that is also allowed to read "for_user_b" tag messages (user b in our example). So, please be aware that the logical structure of a messaging system is determined by the access and distribution policy and not by the physical distribution structure!
I am not even sure if this question is still being used or what but I liked the task and I thought:
why not?
Here is my implementation, as simple as it gets but without forgetting fundamental parts. Written in pure Java, makes use of, among the rest, Sockets, Threads and SynchronizedList:
SimpleChat.java (Main)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class SimpleChat {
private static boolean isRunning = true;
private static Sender sender;
private static Receiver receiver;
public static void main(String[] args) throws IOException {
if(args.length < 3){
showUsage();
}
try {
receiver = new Receiver(Integer.parseInt(args[1]));
sender = new Sender(args[0], args[2], Integer.parseInt(args[3]));
} catch (InterruptedException e) {
showUsage();
}
// Read user input
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Chat started. Type '\\exit' to quit.");
while(isRunning) {
String input = br.readLine();
if(input.equals("\\exit")){
receiver.stop();
sender.stop();
isRunning = false;
} else {
sender.sendMessage(input);
}
}
}
static void showUsage(){
System.out.println("Usage: java SimpleChat.java listening_port target_IP target_port");
System.exit(1);
}
}
Receiver.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Receiver {
private boolean isRunning = true;
public Receiver(int listeningPort) throws IOException {
Runnable receiverT = new Runnable() {
public void run() {
ServerSocket serverSocket;
try {
serverSocket = new ServerSocket(listeningPort);
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
while(isRunning) {
try {
System.out.println(in.readLine());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
};
new Thread(receiverT).start();
}
public void stop(){
isRunning = false;
}
}
Sender.java
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class Sender {
private boolean isRunning = true;
private volatile List<String> msgs;
public Sender(String username, String targetIP, int targetPort) throws InterruptedException, UnknownHostException, IOException {
msgs = Collections.synchronizedList(new ArrayList<String>());
Runnable senderT = new Runnable() {
public void run() {
try {
Socket socket = new Socket(targetIP, targetPort);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
while(isRunning) {
synchronized(msgs){
Iterator<String> it = msgs.iterator();
while(it.hasNext()){
out.println(username + ": " + it.next());
}
// Clear messages to send
msgs.clear();
}
}
out.close();
socket.close();
} catch (UnknownHostException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
};
new Thread(senderT).start();
}
public void stop(){
isRunning = false;
}
public void sendMessage(String msg){
synchronized(msgs){
msgs.add(msg);
}
}
}
As the 'showUsage()' says, use this program as follows:
java SimpleChat.java username listening_port target_ip target_port
Example:
java SimpleChat.java Supuhstar 1234 127.0.0.1 1234
[To talk to yourself]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With