it-swarm-pt.tech

Como fazer um programa básico de mensagens instantâneas em Java puro

Reintegrando esta questão com uma recompensa! Eu preciso de um exemplo que permanece online , como um mensageiro instantâneo real! Ele precisa estar sempre pronto para receber ou enviar uma mensagem para um endereço arbitrário em uma porta arbitrária, usando TCP. O programa não deve ser encerrado após o envio/recebimento de uma mensagem.

Recompensa vai para quem pode dar o melhor exemplo de um mensageiro instantâneo real e utilizável.


Procurando online, todos os recursos que encontrei são tutoriais inúteis , threads mortos , tutoriais mortos , exemplos antigos , ou dizem ao programador para usar APIs externas. Como posso criar um mensageiro instantâneo básico a partir do zero, usando apenas o Java SE?

Deve haver uma maneira de fazer isso, e algum código de exemplo seria apreciado . Ele só precisa executar as tarefas mais simples: Verifique se um cliente compatível está online em outro computador (o IP será fornecido pelo usuário) e envie um pacote TCP para esse cliente, que receberá e exibirá seu conteúdo .

12
Supuhstar

Quando esta pergunta foi feita pela primeira vez e respondida em 2011, era simplesmente "Procurando on-line, todos os recursos que encontrei são inúteis Tutoriais, encadeamentos falhados ou dizem ao programador para usar APIs ". Os links fornecidos abaixo atenderam aos critérios no momento. Mais discussões seguem nos comentários.

Primeiros resultados do Google para " Java socket chat ":

Ou de " cliente de chat do Java 8 ":

Muitos, muitos resultados seguintes na pesquisa. Escolha um que se adapte às suas necessidades. Você pode até modificar a pesquisa do Google para mostrar apenas os resultados do ano anterior, se desejar.

13
ziesemer

Eu fiz isso quando estava aprendendo Java, algo em torno de 10 anos atrás. Funciona:

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);
    }
}

Hoje em dia não me orgulho do código, mas realmente funciona. 

Editar:

Como alguns sugeriram, eu fiz algumas melhorias de código (refatoração) e postar o projeto em GitHub: https://github.com/jaumzera/javasocketchat

8
Jaumzera

Eu nem tenho certeza se esta pergunta ainda está sendo usada ou o que, mas eu gostei da tarefa e pensei:

por que não?

Aqui está minha implementação, tão simples quanto possível, mas sem esquecer as partes fundamentais. Escrito em Java puro, faz uso, entre os demais, de Sockets, Threads e SynchronizedList:

SimpleChat.Java (Principal)

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);  
    }
}
}

Como o 'showUsage ()' diz, use este programa da seguinte maneira:

Java SimpleChat.Java username listening_port target_ip target_port

Exemplo:

Java SimpleChat.Java Supuhstar 1234 127.0.0.1 1234

[Para falar consigo mesmo]

2
N3sh

Hm, fiquei tentado a direcioná-lo para uma implementação Java de um servidor implementando o protocolo imap (por exemplo, gavamail). Mas isso, dos cources, também pode se qualificar como código "antigo" e com certeza mataria sua expectativa (por ser uma solução não padrão). No entanto, é uma referência adequada cumprindo sua especificação (concisa).

O que nós temos?

Precisamos de uma solução que deveria estar em Java. Ela deve implementar um sistema básico de mensagens instantâneas.

O último é problemático, pois abrange uma ampla gama de funcionalidades. Mas "básico" parece permitir uma solução mínima.

Então, o que é um sistema mínimo de mensagens instantâneas? Vamos tentar com o seguinte:

  • um cliente que está postando e recuperando mensagens.
  • um servidor que está armazenando mensagens postadas para recuperação (posterior)

Também precisaríamos de uma política de como um cliente identificaria um servidor adequado. A solução mais trivial para o aspecto posterior é usar um servidor "central" com um endereço bem conhecido. Para casos mais complexos, precisaríamos ter a funcionalidade de servidor e/ou cliente distribuída em várias instâncias e elaborar um esquema ou política para identificar as instâncias adequadas para comunicação.

Deixamos de fora semântica mais complexa, como ter diferentes usuários ou mensagens relacionadas a um sistema de categorias ou tags.

Agora, temos dois componentes:

Um servidor implementando dois pontos de entrada:

  1. POSTAR MENSAGEM
    receber uma mensagem de um cliente e armazená-lo para recuperação posterior
    Esta pergunta é feita imediatamente sobre onde armazenar tais mensagens (em um banco de dados ou sistema de arquivos para persistência ou simplesmente dentro da memória para um " mensagens ao vivo, desde que o servidor esteja ativo " semântica)

  2. LOOKUP_MESSAGE
    selecione uma mensagem adequada das armazenadas (de preferência uma não lida) e retorne ao chamador.
    Isso também pode retornar um conjunto de mensagens (mas pense em restringir esse conjunto para casos em que um chamador tem uma lista de pendências de mensagens grave)
    Pode ser necessário acompanhar as mensagens que já foram lidas, marcando as mensagens ou mantendo o status visto no cliente. Isso pode até ser tão simples quanto manter o tempo ou o ordinal da última mensagem visto e enviar essas informações junto com a solicitação LOOKUP_MESSAGE.

Um cliente precisa interagir com um usuário, por um lado, e o serviço, por outro.

Será necessária uma nova mensagem do usuário (provavelmente em solicitação explícita (por exemplo, botão de envio) e chamar o serviço POST_MESSAGE no servidor relacionado.

Ele também (provavelmente regularmente, também poderia estar em solicitação explícita (por exemplo, o usuário está iniciando o cliente)) pesquisar o servidor para novas mensagens. (Como alternativa, você pode criar um serviço de notificação separado que seja usado pelo servidor para notificar o cliente sobre novas mensagens. O que se adapta às suas "necessidades" está além da sua pergunta.)

É isso aí.

Portanto, qualquer exemplo de um aplicativo cliente/servidor baseado em TCP formará um ponto de partida perfeito para uma implementação direta.

Também devo mencionar que você poderia cortar a lógica de especificação dentro do cliente e delegar a interação do usuário a um navegador padrão e implementar a lógica do aplicativo cliente em uma instância do servidor (web) (juntos ou separados da parte do servidor). No entanto, você ainda terá a funcionalidade lógica (cliente/servidor) de acordo com as especificações mínimas acima. 

Outro aspecto que você deve estar ciente:

Com alguns comentários, você mencionou as atribuições "Host" e "guest" disponíveis nos exemplos de mensageiros atuais. Na verdade, essa é uma estrutura lógica de um sistema de marcação fornecido com esses mensageiros. As mensagens ainda são enviadas de um cliente para um servidor e, em seguida, são recuperadas por outros clientes. Se um cliente pode ver uma mensagem é determinado pelo cliente sendo qualificado para a tag específica. Por exemplo, postar uma mensagem para um contato seu (usuário b) apenas marca a mensagem com a tag "for_user_b" e, como tal, é visível apenas para o autor e para qualquer pessoa que também possa ler mensagens "for_user_b" (usuário b in nosso exemplo). Portanto, esteja ciente de que a estrutura lógica de um sistema de mensagens é determinada pela política de acesso e distribuição e não pela estrutura de distribuição física!

2
rpy

Eu acho que você deve esclarecer alguns detalhes sobre o que exatamente você quer dizer com um "programa básico de mensagens instantâneas" e quais são seus objetivos em relação a este projeto.

Em um comentário de 2011, você mencionou que não deveria haver "hub central", mas em um comentário mais recente, você diz que gostaria de algo mais em linha com o Skype ou iMessage, onde os usuários não precisam saber qual peer é o server ... É tecnicamente possível (usando protocolos como mdns, dlna ou ssdp) para o programa procurar de forma transparente na rede local por possíveis nós de servidores existentes e fazer com que ele se conecte ao servidor se houver um, ou se estabeleça como um servidor local para outros nós se conectarem a ele. Por exemplo, como o protocolo Bonjour da Apple iChat costumava funcionar. No entanto, esta é uma solução bastante complexa para implementar corretamente, e definitivamente não está alinhada com o que é feito pelos atuais programas de mensagens de mercado de massa.

Também estabelecer uma comunicação ponto-a-ponto direta entre os usuários representa vários problemas práticos (particularmente por causa de firewalls e NAT, mas também há preocupações de confidencialidade e segurança). A maioria dos protocolos, portanto, retransmite a maioria das mensagens através do servidor central e negocia uma conexão direta apenas para fins de transferências de arquivos e chamadas de áudio/vídeo.

Por todas essas razões, a menos que você esteja procurando apenas um exemplo de comunicação de rede local entre dois hosts, você certamente desejará dois programas distintos: um servidor e um cliente.

Então, supondo que minha suposição esteja correta, há duas outras questões que precisam ser esclarecidas. Primeiro, você tem uma razão real para conceber o protocolo sozinho, ou seria aceitável implementar um protocolo existente (como XMPP, IRC, SIMPLE ... existem toneladas). Embora esses protocolos possam parecer altamente complexos no início, quase sempre é possível implementar apenas um subconjunto desses recursos/mensagens do protocolo. Projetar um protocolo de rede ingênuo por si mesmo não é tão difícil, mas há toneladas de possíveis erros (principalmente ineficiências, incompletude e outros problemas menores) que você terá que passar. Talvez seja realmente o que você está especificamente visando (ou seja, ganhar experiência em projetar um protocolo de rede a partir do zero), mas a menos que seja assim, você deve seriamente optar por implementar um protocolo existente. Na verdade, trabalhar com um protocolo existente não apenas evitará tais erros de design, mas, melhor ainda, você obterá conhecimento significativo de como os outros (geralmente designers de protocolo experientes) realmente resolveram os problemas que encontraram ao longo do caminho. Usar um protocolo existente também tornará muito mais fácil e interessante desenvolver esse programa, já que, por exemplo, você poderá testar seus programas cliente e servidor independentemente, conectando-se de/para uma implementação oficial de cliente/servidor. Você também poderá explorar os decodificadores de protocolo existentes nas ferramentas de detecção de tráfego para depurar as mensagens.

A segunda pergunta importante é o quão realista você gostaria que o programa do servidor fosse e, o mais importante, em relação à persistência. O servidor deve manter uma lista persistente de usuários e autenticá-los? O servidor deve armazenar uma lista de contatos permitidos para cada usuário? O servidor deve permitir armazenar mensagens destinadas a um peer que esteja atualmente offline ou que não possa ser alcançado naquele exato momento? Programas de servidor de mensagens reais geralmente fazem essas coisas, e embora a implementação de tais mecanismos não seja muito difícil, é melhor considerado no início do projeto da arquitetura de um programa. Por exemplo, se você decidir que esses recursos são de fato desejáveis, talvez seja muito mais interessante projetar imediatamente seu servidor em torno de um mecanismo de fila de mensagens persistente, como o ActiveMQ ...

Eu sei que este não é o exemplo que você estava pedindo, mas eu ainda espero que esses pensamentos possam ajudá-lo.

1
jwatkins

Como dito antes, há muitas coisas que você precisa colocar em uma conversa real para trabalhar.
Mas eu acredito que você quer algo para começar. E se você souber o endereço e a porta do outro "cliente" é fácil.
Implementação simples de "chat"

import Java.io.InputStream;
import Java.io.OutputStream;
import Java.net.InetSocketAddress;
import Java.net.ServerSocket;
import Java.net.Socket;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;

public class SimpleChat {
    protected boolean running = true;
    protected int port;

    private Thread server;


    public static void main(String... arg) {
        //create 2 clients in the localhost to test
        SimpleChat app1 = new SimpleChat(8989);
        SimpleChat app2 = new SimpleChat(8988);

        app1.sendMessage("localhost", 8988, "Message from app1 to app2");
        app2.sendMessage("localhost", 8989, "Message from app2 to app1");

        System.exit(0); // ugly way to kill the threads and exit
    }

    public SimpleChat(int port) {
        this.port = port;
        start();
    }

    public void start() {
        server = new Thread(new Server());
        server.start();
    }

    public boolean sendMessage(String Host, int port, String message) {
        try {
            //Connect to a server on given Host and port and "send" the message
            InetSocketAddress destination
                    = new InetSocketAddress(Host, port);
            Socket s = SocketFactory.getDefault().createSocket();
            s.connect(destination);
            OutputStream out = s.getOutputStream();
            out.write(message.getBytes());
            out.flush();
            out.close();

            s.close();

            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public void messageRecived(String message) {
        System.out.println("Message recived: " + message);
    }

    public void stop() {
        this.running = false; // only stop after a socked connection
    }

    class Server implements Runnable {
        public void run() {
            try {
                //Create a server socket to recieve the connection
                ServerSocket ss = ServerSocketFactory.getDefault()
                        .createServerSocket(port);
                while (running) {
                    Socket s = ss.accept();
                    InputStream in = s.getInputStream();
                    StringBuilder message = new StringBuilder();
                    int len;
                    byte[] buf = new byte[2048];
                    while ((len = in.read(buf)) > -1) {
                        if (len > 0) {
                            message.append(new String(buf, 0, len));
                        }
                    }
                    messageRecived(message.toString());
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(-1);
            }
        }
    }
}
0
fhofmann