web-dev-qa-db-ja.com

純粋なJavaで基本的なインスタントメッセージングプログラムを作成する方法

賞金でこの質問を復活させます!オンラインでいる例が必要です 、本物のインスタントメッセンジャーのように! TCPを使用して、任意のポートを介して任意のアドレスにメッセージを受信または送信する準備が常に整っている必要がありますメッセージの送受信後にプログラムを終了させないでください。

バウンティは、実際に使用できるインスタントメッセンジャーの最良の例を示すことができる人なら誰でも利用できます。


オンラインで見ると、私が見つけたすべてのリソースは 役に立たないチュートリアルデッドスレッドデッドチュートリアル古代の例 、またはプログラマーに外部APIを使用するように伝えます。 Java SE?のみを使用して、基本的なインスタントメッセンジャーをゼロから作成するにはどうすればよいですか?

これを行う方法が必要であり、サンプルコードをいただければ幸いです。最も単純なタスクを実行するだけで済みます。互換性のあるクライアントが別のコンピューターでオンラインであるかどうかを確認し(IPはユーザーから提供されます)、そのクライアントにTCPパケットを送信します。その内容を表示します。

12
Supuhstar

この質問が2011年に最初に尋ねられて回答されたとき、それは単に「オンラインで見たとき、私が見つけたすべてのリソースは、役に立たないチュートリアル、デッドスレッド、または外部APIを使用するようにプログラマーに伝えている」でした。 。以下に提供されたリンクは、当時の基準を満たしていました。さらなる議論はコメントで続きます。

Javaソケットチャット 」の最初のGoogle検索結果:

または「 Java 8チャットクライアント 」から:

検索で多くの、多くの結果が続きます。ニーズに合ったものを選択してください。必要に応じて、Google検索を変更して、昨年の結果のみを表示することもできます。

13
ziesemer

私はJavaを学んでいたときに、10年ほど前にこれを行いました。できます:

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

現在、私はコードを誇りに思っていませんが、実際に機能します。

編集:

一部の人が示唆したように、コードの改善(リファクタリング)を行い、プロジェクトを GitHub:https://github.com/jaumzera/javasocketchat に投稿しました

8
Jaumzera

この質問がまだ使用されているかどうかもわかりませんが、私はタスクが好きで、私は考えました:

何故なの?

これが私の実装です。簡単なものですが、基本的な部分を忘れることはありません。純粋なJavaで記述されており、残りの中で、Sockets、Threads、およびSynchronizedListを使用します。

SimpleChat.Java(メイン)

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

「showUsage()」が言うように、このプログラムを次のように使用します。

Java SimpleChat.Java username listening_port target_ip target_port

例:

Java SimpleChat.Java Supuhstar 1234 127.0.0.1 1234

【自分と話すこと】

2
N3sh

うーん、私はあなたにJava imapプロトコルを実装するサーバーの実装(例:gavamail)に誘導するように誘惑しました。しかし、これは、プロセスの「古い」コードとしても、 (非標準のソリューションであるため)期待を打ち消しますが、それは(簡潔な)仕様を満たす適切なリファレンスです。

何がありますか?

Javaである必要があるソリューションが必要です。基本的なインスタントメッセージングシステムを実装する必要があります。

後者は、非常に幅広い機能をカバーしているため、問題があります。しかし、「基本」は最小限のソリューションを可能にするようです。

つまり、最小限のインスタントメッセージングシステムとは何ですか?以下を試してみましょう。

  • メッセージを投稿および取得するクライアント。
  • (後で)取得するために投稿されたメッセージを保存するサーバー

また、クライアントが適切なサーバーを特定する方法のポリシーも必要です。後者の側面の最も簡単な解決策は、既知のアドレスを持つ「中央」サーバーを使用することです。より複雑なケースでは、サーバーやクライアントの機能を複数のインスタンスに分散させ、通信に適したインスタンスを識別するためのスキームまたはポリシーを考案する必要があります。

カテゴリやタグのシステムに関連するさまざまなユーザーやメッセージなど、より複雑なセマンティクスは省略します。

これで、2つのコンポーネントを持つことになりました。

2つのエントリポイントを実装するサーバー:

  1. POST_MESSAGE
    クライアントからメッセージを受け取り、後で取得できるように保存します
    この即時性は、そのようなメッセージを保存する場所(永続性のためにデータベースまたはファイルシステム内、または単にメモリ内に "メッセージはサーバーが稼働している限り存続しているセマンティクス)

  2. LOOKUP_MESSAGE
    保存されているメッセージ(できれば未読のメッセージ)から適切なメッセージを選択し、呼び出し元に戻ります。
    これは一連のメッセージを返す可能性もあります(ただし、発信者がメッセージの重大なバックログを持っている場合は、そのようなセットを制限することを考えてください)
    メッセージにマークを付けるか、クライアントでの表示ステータスを維持することにより、すでに読み取られたメッセージを追跡する必要がある場合があります。これは、最後に表示されたメッセージの時間または序数を保持し、この情報をLOOKUP_MESSAGEリクエストと共に送信するだけの簡単なものでさえあります。

クライアントは、一方ではユーザーと、他方ではサービスと対話する必要があります。

ユーザーからの新しいメッセージを受け取り(明示的なリクエスト(送信ボタンなど)の可能性があります)、関連するサーバーでPOST_MESSAGEサービスを呼び出します。

また、(おそらく定期的に、明示的な要求(たとえば、ユーザーがクライアントを起動している)の可能性もあります)によってサーバーに新しいメッセージがないかポーリングします。 (または、サーバーがクライアントに新しいメッセージを通知するために使用する個別の通知サービスを考案することもできます。「ニーズ」に適したものは問題外です。)

それでおしまい。

したがって、TCPベースのクライアント/サーバーアプリケーションの例はすべて、直線的な実装の完璧な出発点になります。

また、クライアント内の仕様ロジックを切り取り、ユーザーインタラクションを標準ブラウザに委任して、クライアントアプリケーションロジックを(Web)サーバーインスタンスに実装することもできます(サーバーパーツと一緒に、またはサーバーパーツとは別に)。それにもかかわらず、上記の最小仕様に従って、両方の(クライアント/サーバー)論理機能がまだあります。

注意すべきもう1つの側面:

いくつかのコメントで、現在のメッセンジャーの例で使用可能な「ホスト」および「ゲスト」属性について言及しました。これは実際には、それらのメッセンジャーに提供されているタグ付けシステムの論理構造です。メッセージは引き続きクライアントからサーバーに送信され、他のクライアントによって取得されます。クライアントがメッセージを見ることができるかどうかは、クライアントが特定のタグに適格であるかどうかによって決まります。たとえば、自分(ユーザーb)から連絡先にメッセージを投稿すると、メッセージに「for_user_b」というタグが付けられるだけなので、投稿者および「for_user_b」タグメッセージの読み取りを許可されているすべてのユーザー(ユーザーb私たちの例)。したがって、メッセージングシステムのlogical構造は、物理的な分散構造ではなく、アクセスおよび分散ポリシーによって決定されることに注意してください。

2
rpy

「基本的なインスタントメッセージングプログラム」が正確に何を意味するか、およびこのプロジェクトに関する実際の目的は何かについて、いくつかの詳細を明確にする必要があると思います。

2011年のコメントで、「中央ハブ」はないはずだと述べましたが、より最近のコメントでは、SkypeまたはiMessageに沿った、ユーザーがどのピアがサーバー...プログラムが(mdns、dlna、ssdpなどのプロトコルを使用して)潜在的に既存のサーバーノードのローカルネットワークを透過的に検索し、存在する場合はサーバーピアに接続するか、サーバーピアを確立することが技術的に可能です。他のノードが接続するローカルサーバーとして。これはたとえば、Apple iChatのBonjourプロトコルが機能するために使用された方法です。ただし、これは正しく実装するためのかなり複雑なソリューションであり、現在のマスマーケットメッセージングプログラムで行われていることとは明らかに異なります。

また、ユーザー間で直接ピアツーピア通信を確立すると、いくつかの実用的な問題が発生します(特にファイアウォールとNATが原因ですが、機密性とセキュリティも懸念されます)。したがって、ほとんどのプロトコルは中央サーバーを介してほとんどのメッセージを中継し、ファイル転送とオーディオ/ビデオ通話の目的でのみ直接接続をネゴシエートします。

これらすべての理由により、2つのホスト間のローカルネットワーク通信の例を探しているだけの場合を除き、サーバーとクライアントの2つの異なるプログラムが必要です。

次に、私の仮定が正しいと仮定すると、他に2つの質問を明確にする必要があります。まず、自分でプロトコルを考案する実際の理由はありますか、それとも既存のプロトコル(XMPP、IRC、SIMPLEなど)を実装することは許容されますか?これらのプロトコルは最初は非常に複雑に見えるかもしれませんが、ほとんどの場合、これらのプロトコルの機能/メッセージのサブセットのみを実装することが可能です。単純なネットワークプロトコルを自分で設計することはそれほど難しくはありませんが、多くの潜在的なミス(主に非効率、不完全性、その他の小さな問題)を経験する必要があります。たぶん、それが本当にあなたが具体的に目指していることです(つまり、ネットワークプロトコルを最初から設計する経験を積むことです)。そうでない場合は、既存のプロトコルの実装を真剣に選ぶ必要があります。実際、既存のプロトコルを使用することで、そのような設計ミスを回避できるだけでなく、他の人(一般に経験豊富なプロトコル設計者)が途中で遭遇した問題を実際に解決した方法から重要な知識を得ることができます。既存のプロトコルを使用すると、たとえば、公式のクライアント/サーバー実装から/に接続することでクライアントプログラムとサーバープログラムを個別にテストできる場合、そのプログラムの開発がはるかに簡単で興味深いものになります。通過するメッセージをデバッグするために、トラフィックスニッフィングツールで既存のプロトコルデコーダを活用することもできます。

2番目の重要な質問は、サーバープログラムをどの程度現実的にしたいか、そして最も重要なことは永続性に関してです。サーバーはユーザーの永続的なリストを維持し、それらを認証する必要がありますか?サーバーは各ユーザーに許可された連絡先のリストを保存する必要がありますか?サーバーは、現在オフラインになっているか、その瞬間に到達できないピアを対象としたストアメッセージを許可する必要がありますか?実際のメッセージングサーバープログラムは一般にそのようなことを行い、そのようなメカニズムを実装することはそれほど難しくはありませんが、プログラムのアーキテクチャの設計の早い段階で検討するのが最善です。たとえば、これらの機能が本当に望ましいと判断した場合は、ActiveMQなどの永続的なメッセージキューエンジンを中心にサーバーをすぐに設計する方がはるかに興味深いかもしれません...

これがあなたが求めていた例ではないことを知っていますが、これらの考えがあなたの助けになることを願っています。

1
jwatkins

前に述べたように、実際のチャットを機能させるために必要なことがたくさんあります。
しかし、あなたは何かを始めたいと思っています。そして、あなたが他の「クライアント」のアドレスとポートを知っているなら、それは簡単です。
非常にシンプルな「チャット」の実装

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