Search code examples
javaeclipsesocketsserversocketsimplehttpserver

Java ServerSocket/socket multiple chat with default web page


Hey all I am creating a chat program that can handle multipule clients to one server. Everything works just as it should on the chat side. I can have 3 clients open and talking to each other and it all works fine. However, I am also wanting to place a small web page when someone uses that URL:PORT into their browser and just let them know this is a private server please move on type of thing.

For my client code I have this:

import java.io.*;
import java.net.Socket;
import java.net.SocketException;

public class MainClient {
    private Socket clientSocket;
    public PrintWriter out;
    public BufferedReader userInput;
    private ClientReceiver clientReceiver;
    private static final int SERVER_PORT = 6427;
    private String line;

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

    private void go() {
        try {
            clientSocket = new Socket("localhost", SERVER_PORT);
            out = new PrintWriter(clientSocket.getOutputStream(), true);
            clientReceiver = new ClientReceiver(this, clientSocket);
            userInput = new BufferedReader(new InputStreamReader(System.in));

            clientReceiver.start();

            while (true) {
                line = userInput.readLine();

                if (!clientSocket.isClosed()) {

                    if (line != null) {
                        out.println(line);

                        if (line.equals("END")) {
                            clientReceiver.interrupt();
                            break;
                        }
                    } else {
                        break;
                    }
                } else {
                    break;
                }
            }

        } catch (SocketException e) {
            System.out.println("Main thread socketexception");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();

                if (clientSocket != null) {
                    clientSocket.close();
                }
            } catch (IOException e) {
                System.err.println("Failed to close.");
            }
        }

        System.out.println("Main thread ended");
    }

    public class ClientReceiver extends Thread {
        private Socket clientSocket;
        private MainClient mainHandle;
        private BufferedReader socketIn;
        private String line;

        public ClientReceiver(MainClient mainHandle, Socket clientSocket) {
            this.mainHandle = mainHandle;
            this.clientSocket = clientSocket;
        }

        @Override
        public void run() {
            try {
                socketIn = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

                while (true) {
                    line = socketIn.readLine();

                    if (line != null) {
                        if (line.equals("END")) {
                            System.out.println("Server sent END message.");
                            clientSocket.shutdownOutput();
                            System.out.println("Shutted down socket output");
                        } else if (line.equals("CLOSE_SOCKET_INPUT")) {
                            clientSocket.shutdownInput();
                            System.out.println("Shutted down socket input");
                            break;
                        } else
                            System.out.println(line);

                    } else {
                        System.out.println("Server sent null. Exiting...");
                        break;
                    }
                }
            } catch (SocketException e) {
                System.out.println("Caught SocketException");
            } catch (IOException e) {
                System.out.println("Caught IOException");
                e.printStackTrace();
            } finally {
                try {
                    clientSocket.close();
                    System.out.println("Closed client socket");
                } catch (IOException e) {
                    System.err.println("BufferedReader or socket failed to close.");
                }

                System.out.println("ClientReceiver thread ended.");
                System.out.println("Server has shut down. Press any key to quit.");
            }

        }
    }
}

And the server code is:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class MainServer {
    private MessageQueue messageQueue;
    private ConnectionManager connectionManager;
    private BufferedReader inputStream;
    private MessageRetriever messageRetriever;
    private int _PORT = 6427;
    private Socket socket;
    private BufferedReader in;
    private PrintWriter out;
    private ServerSocket serverSocket;

    public static void main(String[] args) {
        MainServer uiThread = new MainServer();
        uiThread.go();
        System.out.println("Main thread ended");
    }

    private void go() {
        System.out.println("multichat server");
        messageQueue = new MessageQueue();
        connectionManager = new ConnectionManager(messageQueue);
        messageRetriever = new MessageRetriever(messageQueue);
        connectionManager.start();
        messageRetriever.start();
        inputStream = new BufferedReader(new InputStreamReader(System.in));

        String line;

        do {
            try {
                line = inputStream.readLine();
                messageQueue.addToSendMessage(line);

                if (line.equals("END")) {
                    connectionManager.stopListening();
                    messageRetriever.interrupt();
                    break;
                }
            } catch (IOException e) {
                return;
            }
        } while (line != null);
    }

    public class MessageQueue {
        private ArrayList<String> toSendList = new ArrayList<String>();
        private LinkedList<String> receivedMessagesList = new LinkedList<String>();
        private LinkedList<ClientInfo> clientList;
        private boolean shutdownTriggered;

        public MessageQueue() {
            clientList = new LinkedList<ClientInfo>();
        }

        synchronized public void addToSendMessage(String message) {
            toSendList.add(message);
            notifyAll();
        }

        synchronized public List<String> retrieveToSendMessages(int index) throws InterruptedException {
            while (toSendList.size() == index) {
                if (!shutdownTriggered)
                    wait();
                else
                    throw new InterruptedException();
            }

            if (index < toSendList.size()) {
                return toSendList.subList(index, toSendList.size());
            } else {
                System.err.println("Index is " + index + ". Send List size is " + toSendList.size());
                return null;
            }
        }

        synchronized public String pollReceivedMessage() throws InterruptedException {
            while (receivedMessagesList.isEmpty()) {
                if (!shutdownTriggered)
                    wait();
                else
                    throw new InterruptedException();
            }

            return receivedMessagesList.poll();
        }

        synchronized public void addReceivedMessage(String message) {
            receivedMessagesList.push(message);
            notifyAll();
        }

        public void addClient(ClientInfo info) {
            clientList.add(info);
        }

        synchronized public LinkedList<ClientInfo> getClientList() {
            return clientList;
        }

        synchronized public void changeClientInfoStatus(int port, String newStatus) {
            for (ClientInfo info : clientList) {
                if (info.getPort() == port) {
                    info.setStatus(newStatus);
                    break;
                }
            }

            notifyAll();
        }

        public void closeClientCommunicatorInputStreams() {
            for (ClientInfo info : clientList) {
                info.getCommunicator().closeInputStream();
            }
        }

        private boolean allClientInputStreamClosed() {
            for (ClientInfo info : clientList) {
                if (!info.getStatus().equals(ClientInfo.STATE_CLOSED_INPUTSTREAM))
                    return false;
            }

            return true;
        }

        private boolean allClientOutputStreamClosed() {
            for (ClientInfo info : clientList) {
                if (!info.getStatus().equals(ClientInfo.STATE_CLOSED_OUTPUTSTREAM))
                    return false;
            }

            return true;
        }

        synchronized public void waitForAllClientInputStreamClose() {
            while (!allClientInputStreamClosed()) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            notifyAll();
        }

        synchronized public void waitForAllClientOutputStreamClose() {
            while (!allClientOutputStreamClosed()) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            notifyAll();
        }

        synchronized public void shutdown() {
            shutdownTriggered = true;
            notifyAll();
        }
    }

    public class ClientInfo {
        private ClientCommunicator communicator;
        private String status;
        public static final String STATE_CLOSED_INPUTSTREAM = "state_closed_inputstream";
        public static final String STATE_CLOSED_OUTPUTSTREAM = "state_closed_outputstream";

        public ClientInfo(ClientCommunicator communicator) {
            this.communicator = communicator;
            _PORT = communicator.getSocket().getPort();
        }

        synchronized public void setStatus(String status) {
            this.status = status;
            notifyAll();
        }

        public int getPort() {
            return _PORT;
        }

        synchronized public String getStatus() {
            return status;
        }

        public ClientCommunicator getCommunicator() {
            return communicator;
        }
    }

    public class ClientCommunicator extends Thread {        
        private MessageQueue messageQueue;
        private Sender sender;
        private Socket socket;

        public ClientCommunicator(Socket socket, MessageQueue messageQueue) {
            System.out.println("Connected to new client at port " + socket.getPort() + ". Local port is " + socket.getLocalPort());
            this.messageQueue = messageQueue;
            this.socket = socket;

            try {
                out = new PrintWriter(socket.getOutputStream(), true);
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            sender = new Sender();
            sender.start();

            try {
                while (true) {
                    String receivedMessage = in.readLine(); //GET / HTTP/1.1
                    if (receivedMessage != null)
                        messageQueue.addReceivedMessage(receivedMessage);
                    else
                        break;
                }
            } catch (SocketException e) {
                // Typically this means that the socket has been closed
            } catch (IOException e) {
                System.err.println(e.getMessage());
            }

            System.out.println("ClientCommunicator thread ended.");
        }

        public void closeInputStream() {
            if (!socket.isInputShutdown()) {
                try {
                    socket.shutdownInput();
                    messageQueue.changeClientInfoStatus(socket.getPort(), ClientInfo.STATE_CLOSED_INPUTSTREAM);
                } catch (IOException e) {
                    System.err.println("Failed to close socket input stream.");
                    e.printStackTrace();
                }
            }
        }

        public void closeOutputStream() {
            if (!socket.isOutputShutdown()) {
                try {
                    socket.shutdownOutput();
                } catch (IOException e) {
                    System.err.println("Failed to close socket output stream.");
                    e.printStackTrace();
                }
            }
        }

        public Socket getSocket() {
            return socket;
        }

        private class Sender extends Thread {
            private int toSendMessageIndex;

            @Override
            public void run() {
                while (true) {
                    try {
                        List<String> messages = messageQueue.retrieveToSendMessages(toSendMessageIndex);

                        if (messages != null) {
                            boolean exit = false;

                            for (String message : messages) {
                                out.println(message); //GET / HTTP/1.1
                                toSendMessageIndex++;

                                if (message.equals("CLOSE_SOCKET_INPUT")) {
                                    closeOutputStream();
                                    messageQueue.changeClientInfoStatus(socket.getPort(), ClientInfo.STATE_CLOSED_OUTPUTSTREAM);
                                    exit = true;
                                    break;
                                } else if (message.equals("GET / HTTP/1.1")) {
                                    System.out.println("GET");
                                    loadWP();
                                    //loadWEBPage();
                                }
                            }

                            if (exit)
                                break;
                        } else {
                            System.err.println("Null messages!");
                            break;
                        }
                    } catch (InterruptedException e) {
                        break;
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

                System.out.println("ClientCommunicator.Sender thread stopped.");
            }
        }
    }

    public class ConnectionManager extends Thread {
        private final int PORT = _PORT;
        private MessageQueue messageQueue;

        public ConnectionManager(MessageQueue messageQueue) {
            this.messageQueue = messageQueue;
        }

        @Override
        public void run() {
            try {
                serverSocket = new ServerSocket(PORT, 0, InetAddress.getByName("localhost"));
                System.out.println("Server listening on port " + PORT);

                while (true) {
                    socket = serverSocket.accept();
                    ClientCommunicator communicator = new ClientCommunicator(socket, messageQueue);
                    ClientInfo clientInfo = new ClientInfo(communicator);
                    communicator.start();
                    messageQueue.addClient(clientInfo);
                }
            } catch (SocketException e) {
                // SocketException is thrown when serverSocket is closed. This
                // is normal when we shut down the server.
            } catch (IOException e) {
                System.err.println(e.getMessage());
            } finally {
                System.out.println("ConnectionManager thread ended.");
            }
        }

        public void stopListening() {
            try {
                messageQueue.closeClientCommunicatorInputStreams();
                messageQueue.waitForAllClientInputStreamClose();
                messageQueue.addToSendMessage("CLOSE_SOCKET_INPUT");
                messageQueue.waitForAllClientOutputStreamClose();

                System.out.println("Closing client sockets.");

                for (ClientInfo info : messageQueue.getClientList()) {
                    Socket socket = info.getCommunicator().getSocket();

                    if (!socket.isClosed()) {
                        try {
                            socket.close();
                        } catch (IOException e) {
                            System.err.println("Failed to close client socket.");
                        }
                    } else {
                        System.err.println("Client socket is already closed.");
                    }
                }

                if (serverSocket != null) {
                    serverSocket.close();
                    System.out.println("Closed server socket.");
                }

                messageQueue.shutdown();
            } catch (IOException e) {
                System.err.println("Failed to close socket");
            }
        }
    }

    public class MessageRetriever extends Thread {
        private MessageQueue messageQueue;

        public MessageRetriever(MessageQueue messageQueue) {
            this.messageQueue = messageQueue;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    String receivedMessage = messageQueue.pollReceivedMessage();

                    if (receivedMessage == null) {
                        System.err.println("Received null message. Terminating.");
                        break;
                    } else {
                        System.out.println(receivedMessage);
                        messageQueue.addToSendMessage(receivedMessage);
                    }
                } catch (InterruptedException e) {
                    break;
                }
            }

            System.out.println("MessageRetriever ended.");
        }
    }

    public void loadWEBPage() {
        final String newLine = "\r\n";

        try {
            while (true) {
                try {
                    String request = in.readLine();

                    if (request == null)
                        continue;

                    while (true) {
                        String ignore = in.readLine();
                        if (ignore == null || ignore.length() == 0)
                            break;
                    }

                    if (!request.startsWith("GET ")
                            || !(request.endsWith(" HTTP/1.0") || request.endsWith(" HTTP/1.1"))) {
                        out.print("HTTP/1.0 400 Bad Request" + newLine + newLine);
                    } else {
                        String response = "Hello, World!";

                        out.print("HTTP/1.0 200 OK" + newLine + "Content-Type: text/plain" + newLine + "Date: "
                                + new Date() + newLine + "Content-length: " + response.length() + newLine + newLine
                                + response);
                    }

                    out.close();
                } catch (Throwable tri) {
                    System.err.println("Error handling request: " + tri);
                }
            }
        } catch (Throwable tr) {
            System.err.println("Could not start server: " + tr);
        }
    }
    }

Once I start up the socket server I test the chat and again it works just fine back and forth. Then I test out the website http:\localhost:6427 and this is the output I get from the Eclipse console once I do that:

multichat server

Server listening on port 6427

Connected to new client at port 58833. Local port is 6427

Upgrade-Insecure-Requests: 1

Accept-Language: en-US,en;q=0.9

Accept-Encoding: gzip, deflate, br

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/a png,/;q=0.8,application/signed-exchange;v=b3

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36 Connection: keep-alive Host: localhost:6427 GET / HTTP/1.1 Exception in thread "Thread-8" java.util.ConcurrentModificationException at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1250) at java.util.ArrayList$SubList.listIterator(ArrayList.java:1110) at java.util.AbstractList.listIterator(AbstractList.java:310) at java.util.ArrayList$SubList.iterator(ArrayList.java:1106) at MainServer$ClientCommunicator$Sender.run(MainServer.java:292)

Connected to new client at port 58834. Local port is 6427

GET

ClientCommunicator thread ended.

I had it working at one point then I tried something else and once that didn't work I tried to remember what I did with the other script and alas I forgot....

Anyone care to point out what I am missing to get this working again?


Solution

  • In your code you iterate through the messages-List but while this happens something else adds another element to the list or removes one so you get ConcurrentModificationException.

    For example: This will not work:

    public static void main(String[] args)
    {
       List<Object> objectList = new ArrayList(Arrays.asList(new Object[] { "1", "2", "3"}));
       int index = 0;
       for (Object object : objectList)
       {
          System.out.println(object.toString());
          objectList.remove(index);
          index++;
       }
    }
    

    But if you use an Iterator this works:

    public static void main(String[] args)
    {
       List<Object> objectList = new ArrayList(Arrays.asList(new Object[] { "1", "2", "3"}));
       Iterator<Object> iterator = objectList.iterator();
       while (iterator.hasNext())
       {
          Object object = iterator.next();
          System.out.println(object.toString());
          iterator.remove();
       }
    }