Search code examples
javasocketswebsocketserversocketjava-io

Why don't my java socket client and server connect [new ObjectInputStream blocking]?


I'm trying to make a client-server socket connection that will send message objects (type,sender,reciever,time,message).

When my client tries connecting the server recognises the connection but the client keeps waiting on objectInputStream = new ObjectInputStream(socket.getInputStream());.

I've seen answers on here suggesting that you should make the outputsream first and then the input but it didn't change anything. I am not sure what I am missing.

Client:

class Message {
    //...
}

public class ChatClient extends Thread
{
    protected int serverPort = 1234;

    public static void main(String[] args) throws Exception {
        new ChatClient();
    }

    public ChatClient() throws Exception {
        Socket socket = null;
        ObjectOutputStream objectOutputStream = null;
        ObjectInputStream objectInputStream = null;

        // connect to the chat server
        try {
            System.out.println("[system] connecting to chat server ...");
            socket = new Socket("localhost", serverPort); // create socket connection
            objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); // create output stream for sending messages
            // works until here
            objectInputStream = new ObjectInputStream(socket.getInputStream()); // create input stream for listening for incoming messages
            // GETS STUCK HERE
            System.out.println("[system] connected");

            ChatClientMessageReceiver message_receiver = new ChatClientMessageReceiver(objectInputStream); // create a separate thread for listening to messages from the chat server
            message_receiver.start(); // run the new thread
        } catch (Exception e) {
            e.printStackTrace(System.err);
            System.exit(1);
        }

        // reading stuff from terminal and sending messages to the chat server
        this.sendMessage(tmp, objectOutputStream); // send the message to the chat server
        

        // cleanup
        objectOutputStream.close();
        objectInputStream.close();
        std_in.close();
        socket.close();
    }

    private void sendMessage(Message message, ObjectOutputStream objectOutputStream) {
        try {
            objectOutputStream.writeObject(message); // send the message to the chat server
            objectOutputStream.flush(); // ensure the message has been sent
        } catch (IOException e) {
            System.err.println("[system] could not send message");
            e.printStackTrace(System.err);
        }
    }
}

// wait for messages from the chat server and print the out
class ChatClientMessageReceiver extends Thread {
    private ObjectInputStream objectInputStream;

    public ChatClientMessageReceiver(ObjectInputStream objectInputStream) {
        this.objectInputStream = objectInputStream;
    }

    public void run() {
        try {
            Message message;
            message = (Message)objectInputStream.readObject();
            System.out.println("["+message.sender+"] " + message.message);
        } catch (Exception e) {
            System.err.println("[system] could not read message");
            e.printStackTrace(System.err);
            System.exit(1);
        }
    }
}

Server:

class Message {
    //...
}

public class ChatServer {

    protected int serverPort = 1234;
    protected List<Socket> clients = new ArrayList<Socket>(); // list of clients

    public static void main(String[] args) throws Exception {
        new ChatServer();
    }

    public ChatServer() {
        ServerSocket serverSocket = null;
        
        // create socket
        try {
            serverSocket = new ServerSocket(this.serverPort); // create the ServerSocket
             
        } catch (Exception e) {
            System.err.println("[system] could not create socket on port " + this.serverPort);
            e.printStackTrace(System.err);
            System.exit(1);
        }

        System.out.println("[system] listening ...");
        try {
            while (true) {
                Socket newClientSocket = serverSocket.accept(); // wait for a new client connection
                synchronized(this) {
                    clients.add(newClientSocket); // add client to the list of clients
                }
                ChatServerConnector conn = new ChatServerConnector(this, newClientSocket); // create a new thread for communication with the new client
                conn.start(); // run the new thread
            }
        } catch (Exception e) {
            System.err.println("[error] Accept failed.");
            e.printStackTrace(System.err);
            System.exit(1);
        }

        // close socket
        System.out.println("[system] closing server socket ...");
        try {
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace(System.err);
            System.exit(1);
        }
    }

    // send a message to all clients connected to the server
    public void sendToAllClients(Message message) throws Exception {
        //...
    }
    //...

    public void removeClient(Socket socket) {
        synchronized(this) {
            names.remove(socket.getPort());
        }
    }
}

class ChatServerConnector extends Thread {
    private ChatServer server;
    private Socket socket;
    private ObjectInputStream objectInputStream;
    private Message message;

    public ChatServerConnector(ChatServer server, Socket socket) {
        this.server = server;
        this.socket = socket;
    }

    public void run() {
        try {
            objectInputStream = new ObjectInputStream(this.socket.getInputStream()); // create input stream for listening for incoming messages
            System.out.println("[system] connected with " + this.socket.getInetAddress().getHostName() + ":" + this.socket.getPort());
        } catch (IOException e) {
            System.err.println("[system] could not open input stream!");
            //...
            return;
        }

        while (true) { // infinite loop in which this thread waits for incoming messages and processes them
            message = new Message();

            try {
                message = (Message)objectInputStream.readObject();
            } catch (Exception e) {
                //...
            }

            switch(message.type) {
                //...
                case "public":
                    try {
                        this.server.sendToAllClients(message); // send message to all clients
                    } catch (Exception e) {
                        //..
                    }
                    return;
                default:
                    //...
            }
        }
    }
}

Solution

  • The ObjectInputStream constructor already tries to read the header data (documentation):

    A serialization stream header is read from the stream and verified. This constructor will block until the corresponding ObjectOutputStream has written and flushed the header.

    So most likely what happens is that both your client and your server create an ObjectInputStream listening for the message from the other one. But because neither of them has written anything yet, both get stuck.
    Maybe this could be solved by creating the ObjectInputStream inside the client thread.

    Also, please only use ObjectInputStream for hobby projects; it should be avoided nowadays, especially for untrusted data. See also the Secure Coding Guidelines for Java. It has already lead to dozens of remote code execution vulnerabilities in the past. Instead prefer other more secure serialization frameworks, for example Gson or Jackson for JSON, or Protocol Buffers, ...