user3100088
user3100088

Reputation: 445

Server client chat room in Java

I was making a chat room project where the server accepts many clients, and whatever the client writes reaches the other clients and so on. Unfortunately the server accepts maximum 2 clients and after one client writes an input it gives errors.

public class Server2 {

    private static ArrayList<Socket> clients;
    private ServerSocket server;
    DataOutputStream os;
    DataInputStream in;
    Socket s;

    public Server2() throws IOException {
        server = new ServerSocket(5000);
        clients = new ArrayList<Socket>();
        System.out.println("Waiting for connections...");
        runOutput();
    }

    public void addClient() throws IOException {
        s = server.accept();
        clients.add(s);
        System.out.println("A new Client has joined");
    }

    public void runOutput() {

        Thread n = new Thread(new Runnable() {
            public void run() {
                try {
                    addClient();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        n.start(); 

        Thread input = new Thread(new Runnable() {
            public void run() {
                try {
                    addClient();
                    in = new DataInputStream(s.getInputStream());
                    os = new DataOutputStream(s.getOutputStream());
                    String st = in.readLine();
                    System.out.println(st);
                    os.writeBytes(st);
                    for(int i = 0; i < clients.size(); i++)
                    {
                        DataOutputStream oo = new DataOutputStream(clients.get(i).getOutputStream());
                        oo.writeBytes(st);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        input.start();
    }

    public static void main(String[] args) throws IOException {
        Server2 server = new Server2();
    }
}

and the client class :

public class Client2 {
    final public static String host = "localhost";
    final public static int port = 5000;
    Socket socket;
    DataInputStream in;
    DataOutputStream ou;
    Scanner chat;
    boolean run;
    String name;

    public Client2(String n) throws IOException {
        name = n ;
        socket = new Socket(host , port);
        System.out.println("Connection Successful");
        run = true;
        runOutput();
    }

    public void runOutput() {
        Thread input = new Thread(new Runnable() {
            public void run() {
                while (run) {
                    try {
                        in = new DataInputStream(socket.getInputStream());
                        String s = in.readLine();
                        System.out.println(s);
                        if(chat.nextLine().compareTo("QUIT") == 0)
                            run = false;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        input.start();

        Thread t = new Thread(new Runnable() {
            public void run() {
                while (run) {
                    try {
                        ou = new DataOutputStream(socket.getOutputStream());
                        chat = new Scanner(System.in);
                        ou.writeBytes(name + " says :" + chat.nextLine() + "\n");

                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        t.start(); 
    } 

    public static void main(String[] args) throws IOException {
        Client2 client = new Client2("Ahmed");
    }   
}

Upvotes: 2

Views: 11665

Answers (2)

Vince
Vince

Reputation: 15146

Your 'n' thread has no loop, meaning its gonna run once (accept a connection), then die off.

Thread n = new Thread(new Runnable() {
    public void run() {
        while(true) { //control this with your own boolean, or it will run forever
            try {
                addClient();
            }catch(IOException e) { }
        }
    }
});

Dont worry, your thread will be paused at 'ss.accept()', since it will wait until a socket is accepted to continue.

Thread thread = new Thread(new Runnable() {
    public void run() {
        while(running) {
             String input;

             while((input = inputstream.readUTF()) != null) {
                  //handle input
             }
         }
    }
});

Sorry, wrote that really quick, dont have much time. Ill come back and relate it to your code in a bit if it doesnt help.

You need some way of continuously retrieving input from the Server's input stream, in which you can then handle the input.

That loop loops each time you retrieve something from inputstream.readUTF() (and isnt null, of course). Hopefully that example will help you


After actually reading your code and testing it, i have noticed that your structure seems a bit off. First, your Thread n in your server is responsible for accepting connections (addClient()), but you also call it first thing in your Thread input?

Thread n is handling accepting connections, so with the loop, Thread n is fine. Thread input is handling input retrieved from clients. This is where you are getting confused.

Each client should have their own InputStream and OutputStream server-sided. Similar to how you have a list for Sockets (since you are gonna be creating multiple Sockets server-sided), you need multiple Streams aswell. For this, I suggest making a User class, then if anything, have your ArrayList for User, not Socket.

public class User extends Thread { //create a Thread for each connection to handle data seperately
    DataInputStream in;
    DataInputStream out;

    Socket socket;
    public User(Socket socket) { //creates a stream for the socket that the user connected with
        this.socket = socket;

        initStream(); 
    }

    public void initStream() { //inits streams after socket is initiated (through ss.accept())
        try {
            out = new DataOutputStream(socket.getOutputStream());
            in = new DataInputStream(socket.getInputStream());
        }catch(IOException e) { e.printStackTrace(); }
    }

    public void closeStream() throws IOException { //throws exception since i have a chance to catch it in my run() method
            out.close(); in.close();
            socket.close();
    }

    public void sendMessage(String string) {
        try {
            out.writeUTF(string);
        }catch(IOException e) { e.printStackTrace(); }
    }

    public void run() { //when thread starts, accept/handle data
        String input;

        try {
            while((input = in.readUTF()) != null) {
                //handle input
            }
            closeStream();
        }catch(IOException e) { e.printStackTrace(); }
    }
}

in.readLine() is depreciated, which means it's out-dated and shouldn't be used (to avoid bugs). Now that you have your User class set up, which handles the data, you can now create a new User each time a connection is received.

public class Server {
    private static boolean running = true;

    public static ArrayList<User> userlist;

    public static synchronized void stop() { //synchronized in-case another thread other than the main thread stops the server
        if(!running) return;
        running = false;
    }

    public static void main(String[] args) {
        /* uses the JVM thread (main thread for applications), so if you dont wanna
        ** dedicate your entire server to accepting connections only (since Users are in new thread)
        ** create a new thread for the code in this void method. */

        try {
            ServerSocket ss = new ServerSocket(8000);

            userlist = new ArrayList<User>();
            User user; //holds the user so we can put it in the list, then start
        while(running) {
            user = new User(ss.accept());

            list.add(user);
            user.start();
        }
        } catch(IOException e) {
            e.printStackTrace(); }
    }
}

Hopefully, that'll give you a better idea for how your server should run.

As for your client.. Well, there are a ton of things you should realize:

First, big problem, you're re-initializing your DataOutputStream each loop, which is gonna cause the stream server-side to close. Gotta take it out of the loop, possibly put it before (or look at my server example for a better idea of handling stream initialization).

Second, the names should be kept on the Server, rather than the Client. This way, its easy to iterate through the names, check if its taken, ect..

Third, nothing with networking, but seeing how you never refer to Thread n or Thread input, other than to start it, you should remove the reference variable. (even though im pretty sure local vars are picked up by the Garbage Collector, still looks nicer). Example:

public void method() {
    new Thread(new Runnable() {
        public void run() {
            //code
        }
    }).start();

   new Thread(new Runnable() {
        //run method
   }).start();
}

Hope this helps you understand how a multi-threaded server works! :)

PS: This is a very basic server, and really shouldn't be used for large scale applications. Creating new threads (new User()) can cause a lot of overhead, so its best to handle connections using an ExecutorService, or some other type of threadpool service. Good luck!

Upvotes: 1

JamoBox
JamoBox

Reputation: 766

I think you should rethink the logic of a lot of the server code. You should have one thread that handles connecting clients, and assigns them their own input threads. You can then have a method on that input reading thread that will iterate through the clients sending them the message.

When I first started writing my Client-Server chat program I found this little Oracle doc very useful. Hopefully it will help you too.

You should note this part of the document in particular:

Client connection requests are queued at the port, so the server must accept the connections sequentially. However, the server can service them simultaneously through the use of threads—one thread per each client connection.

The basic flow of logic in such a server is this:

while (true) { accept a connection; create a thread to deal with the client; }

Upvotes: 0

Related Questions