1
\$\begingroup\$

In the book Head First Java there was this coding example that created a very simple chat client and server. Both the server and the client consisted of just one class. I left the server code as is, but tried to improve on the client code.

As it was, it was full of both GUI code and networking code. I thought this was not a good idea and tried to split it up nicely.

I tried to use an observer pattern to let the networking class know the user sent a new message.

SimpleChatServer.java

package nl.rickhurkens.chatting;

import java.util.ArrayList;
import java.io.PrintWriter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.ServerSocket;

public class SimpleChatServer {

    ArrayList<PrintWriter> clientOutputStreams;

    public class ClientHandler implements Runnable {
        BufferedReader reader;
        Socket socket;

        public ClientHandler(Socket clientSocket) {
            try {
                socket = clientSocket;
                InputStreamReader isReader = new InputStreamReader(socket.getInputStream());
                reader = new BufferedReader(isReader);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        public void run() {
            String message;
            try {
                while ((message = reader.readLine()) != null) {
                    System.out.println("read: " + message);
                    tellEveryone(message);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public static void main (String[] args) {
        new SimpleChatServer().go();
    }

    public void go() {
        clientOutputStreams = new ArrayList<>();
        try {
            ServerSocket serverSocket = new ServerSocket(5000);

            while(true) {
                Socket clientSocket = serverSocket.accept();
                PrintWriter writer = new PrintWriter(clientSocket.getOutputStream());
                clientOutputStreams.add(writer);

                Thread t = new Thread(new ClientHandler(clientSocket));
                t.start();
                System.out.println("Got a connection.");
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void tellEveryone(String message) {
        for (PrintWriter pw : clientOutputStreams) {
            try {
                pw.println(message);
                pw.flush();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
}

ChatController.java

package nl.rickhurkens.chatting;

import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.InputStreamReader;
import java.net.Socket;

public class ChatController implements SendListener{

    ChatView view;
    BufferedReader reader;
    PrintWriter writer;
    Socket socket;

    public ChatController(ChatView view) {
        setUpNetworking();
        new Thread(new IncomingReader()).start();
        this.view = view;
        view.subscribeSendListener(this);
    }

    private void setUpNetworking() {
        try {
            socket = new Socket("127.0.0.1",5000);
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            writer = new PrintWriter(socket.getOutputStream());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public class IncomingReader implements Runnable {
        public void run() {
            receiveMessages();
        }

        private void receiveMessages() {
            String message;
            try {
                while ((message = reader.readLine()) != null) {
                    view.receiveMessage(message);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public void sendMessage(String message) {
        try {
            writer.println(message);
            writer.flush();
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void messageGiven(String message) {
        sendMessage(message);
    }
}

ChatViewGUI.java

package nl.rickhurkens.chatting;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;

import java.awt.event.ActionListener;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;

public class ChatViewGUI extends ChatView {

    JTextField outgoing;
    JTextArea incoming;

    public ChatViewGUI() {
        initGUI();
    }

    private void initGUI() {
        JFrame frame = new JFrame("Chat Client");
        JPanel mainPanel = new JPanel();

        incoming = new JTextArea(25,30);
        incoming.setLineWrap(true);
        incoming.setWrapStyleWord(true);
        incoming.setEditable(false);
        JScrollPane qScroller = new JScrollPane(incoming);
        qScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        qScroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        outgoing = new JTextField(20);
        JButton sendButton = new JButton("Send");
        sendButton.addActionListener(new SendButtonListener());

        mainPanel.add(qScroller);
        mainPanel.add(outgoing);
        mainPanel.add(sendButton);

        outgoing.requestFocus();

        frame.getContentPane().add(BorderLayout.CENTER, mainPanel);
        frame.setSize(400, 500);
        frame.setVisible(true);
    }

    @Override
    public void receiveMessage(String message) {
        incoming.append(message + "\n");
    }

    public class SendButtonListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            sendMessage(outgoing.getText());
            outgoing.setText("");
            outgoing.requestFocus();
        }
    }
}

ChatView.java

package nl.rickhurkens.chatting;

import java.util.List;
import java.util.ArrayList;

public abstract class ChatView {

    private List<SendListener> sendListeners = new ArrayList<>();

    public void sendMessage(String message) {
        for (SendListener sl : sendListeners) {
            System.out.println(sl);
            sl.messageGiven(message);
        }
    }

    public abstract void receiveMessage(String message);

    public void subscribeSendListener(SendListener listener) {
        sendListeners.add(listener);
    }
}

SendListener.java

package nl.rickhurkens.chatting;

public interface SendListener {
    void messageGiven(String message);
}

Main.java

package nl.rickhurkens.chatting;

public class Main {

    public static void main(String[] args) {
        ChatViewGUI view = new ChatViewGUI();
        new ChatController(view);
    }

}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ The separation looks ok. What I noticed immediately is that you should do something better with exceptions instead of dumping them to the stdout. What if the port of the socket is already in use? How would you recover from that error? What if somehow a timeout occurs? Etc.. \$\endgroup\$ Sep 20, 2017 at 13:04

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.