Program: A Java Chat Server

This program implements a simple chat server (Example 16-10) that works with the chat applet from Section 15.11. It accepts connections from an arbitrary number of clients; any message sent from one client is broadcast to all clients. In addition to ServerSockets, it demonstrates the use of threads (see Chapter 24). And since there are interactions among clients, this server needs to keep track of all the clients it has at any one time. I use an ArrayList (see Section 7.4) to serve as an expandable list, and am careful to use the synchronized keyword around all accesses to this list to prevent one thread from accessing it while another is modifying it (this is discussed in Chapter 24).

Example 16-10. ChatServer.java

/** Simple Chat Server to go with our Trivial Chat Client.
 *
 * Does not implement any form of "anonymous nicknames" - probably
 * a good thing, given how a few people have abused anonymous 
 * chat rooms in the past.
 */
public class ChatServer {
    /** What I call myself in system messages */
    protected final static String CHATMASTER_ID = "ChatMaster";
    /** What goes between any handle and the message */
    protected final static String SEP = ": ";
    /** The Server Socket */
    protected ServerSocket servSock;
    /** The list of my current clients */
    protected ArrayList clients;
    /** Debugging state */
    private boolean DEBUG = false;

    /** Main just constructs a ChatServer, which should never return */
    public static void main(String[] argv) {
        System.out.println("DarwinSys Chat Server 0.1 starting...");
        ChatServer w = new ChatServer(  );
        w.runServer(  );            // should never return.
        System.out.println("**ERROR* Chat Server 0.1 quitting");
    }

    /** Construct (and run!) a Chat Service */
    ChatServer(  ) {
        clients = new ArrayList(  );
        try {
            servSock = new ServerSocket(Chat.PORTNUM);
            System.out.println("DarwinSys Chat Server Listening on port " +
                Chat.PORTNUM);
        } catch(IOException e) {
            log("IO Exception in ChatServer.<init>");
            System.exit(0);
        }
    }

    public void runServer(  ) {
        try {
            while (true) {
                Socket us = servSock.accept(  );
                String hostName = us.getInetAddress().getHostName(  );
                System.out.println("Accepted from " + hostName);
                ChatHandler cl = new ChatHandler(us, hostName);
                synchronized (clients) {
                    clients.add(cl);
                    cl.start(  );
                    if (clients.size(  ) == 1)
                        cl.send(CHATMASTER_ID, 
                    else {
                        cl.send(CHATMASTER_ID, "Welcome! you're the latest of " +
                            clients.size(  ) + " users.");
                    }
                }
            }
        } catch(IOException e) {
            log("IO Exception in runServer: " + e);
            System.exit(0);
        }
    }

    protected void log(String s) {
        System.out.println(s);
    }

    /** Inner class to handle one conversation */
    protected class ChatHandler extends Thread {
        /** The client socket */
        protected Socket clientSock;
        /** BufferedReader for reading from socket */
        protected BufferedReader is;
        /** PrintWriter for sending lines on socket */
        protected PrintWriter pw;
        /** The client's host */
        protected String clientIP;
        /** String handle */
        protected String login;

        /* Construct a Chat Handler */
        public ChatHandler(Socket sock, String clnt) throws IOException {
            clientSock = sock;
            clientIP = clnt;
            is = new BufferedReader(
                new InputStreamReader(sock.getInputStream(  )));
            pw = new PrintWriter(sock.getOutputStream(  ), true);
        }

        /** Each ChatHandler is a Thread, so here's the run(  ) method,
         * which handles this conversation.
         */
        public void run(  ) {
            String line;
            try {
                while ((line = is.readLine(  )) != null) {
                    char c = line.charAt(0);
                    line = line.substring(1);
                    switch (c) {
                    case Chat.CMD_LOGIN:
                        if (!Chat.isValidLoginName(line)) {
                            send(CHATMASTER_ID, "LOGIN " + line + " invalid");
                            log("LOGIN INVALID from " + clientIP);
                            continue;
                        }
                        login = line;
                        broadcast(CHATMASTER_ID, login + 
                            " joins us, for a total of " + 
                            clients.size(  ) + " users");
                        break;
                    case Chat.CMD_MESG:
                        if (login == null) {
                            send(CHATMASTER_ID, "please login first");
                            continue;
                        }
                        int where = line.indexOf(Chat.SEPARATOR);
                        String recip = line.substring(0, where);
                        String mesg = line.substring(where+1);
                        log("MESG: " + login + "-->" + recip + ": "+ mesg);
                        ChatHandler cl = lookup(recip);
                        if (cl == null)
                            psend(CHATMASTER_ID, recip + " not logged in.");
                        else
                            cl.psend(login, mesg);
                        break;
                    case Chat.CMD_QUIT:
                        broadcast(CHATMASTER_ID, "Goodbye to " 
                        close(  );
                        return;        // END OF THIS CHATHANDLER
                        
                    case Chat.CMD_BCAST:
                        if (login != null)
                            broadcast(login, line);
                        else
                            log("B<L FROM " + clientIP);
                        break;
                    default:
                        log("Unknown cmd " + c + " from " + login + "@" + clientIP);
                    }
                }
            } catch (IOException e) {
                log("IO Exception: " + e);
            } finally {
                // the sock ended, so we're done, bye now
                // Can NOT send a good-bye message, until we have
                // a simple command-based protocol in place.
                System.out.println(login + SEP + "All Done");
                synchronized(clients) {
                    clients.remove(this);
                    if (clients.size(  ) == 0) {
                        System.out.println(CHATMASTER_ID + SEP +
                            "Im so lonely I could cry...");
                    } else if (clients.size(  ) == 1) {
                        ChatHandler last = (ChatHandler)clients.get(0);
                        last.send(CHATMASTER_ID,
                            "Hey, you're talking to yourself again");
                    } else {
                        broadcast(CHATMASTER_ID,
                            "There are now " + clients.size(  ) + " users");
                    }
                }
            }
        }

        protected void close(  ) {
            if (clientSock == null) {
                log("close when not open");
                return;
            }
            try {
                clientSock.close(  );
                clientSock = null;
            } catch (IOException e) {
                log("Failure during close to " + clientIP);
            }
        }

        /** Send one message to this user */
        public void send(String sender, String mesg) {
            pw.println(sender + SEP + mesg);
        }

        /** Send a private message */
        protected void psend(String sender, String msg) {
            send("<*" + sender + "*>", msg);
        }
        
        /** Send one message to all users */
        public void broadcast(String sender, String mesg) {
            System.out.println("Broadcasting " + sender + SEP + mesg);
            for (int i=0; i<clients.size(  ); i++) {
                ChatHandler sib = (ChatHandler)clients.get(i);
                if (DEBUG)
                    System.out.println("Sending to " + sib);
                sib.send(sender, mesg);
            }
            if (DEBUG) System.out.println("Done broadcast");
        }

        protected ChatHandler lookup(String nick) {
            synchronized(clients) {
                for (int i=0; i<clients.size(  ); i++) {
                    ChatHandler cl = (ChatHandler)clients.get(i);
                    if (cl.login.equals(nick))
                        return cl;
                }
            }
            return null;
        }

        /** Present this ChatHandler as a String */
        public String toString(  ) {
            return "ChatHandler[" + login + "]";
        }
    }
}

I’ve used this code with a number of clients connected concurrently, and no difficulties were found.

See Also

The server side of any network mechanism is extremely sensitive to security issues. It is easy for one misconfigured or poorly written server program to compromise the security of an entire network! There are many books on network security, but two books stand out as worthy of mention: Cheswick and Bellovin’s Firewalls and Internet Security and the more recent Hacking Exposed, by McCLure et al.

This completes my discussion of server-side Java using sockets. I’ll next return to the client side to discuss applets and some useful client-side recipes. Later, in Chapter 22, I show an alternate technology that can be used to implement both sides of the chat program in a more object-oriented manner. Finally, a chat server could also be implemented using JMS (Java Message Service), a newer API that handles store-and-forward message processing. This is beyond the scope of this book, but there’s an example of such a chat server in O’Reilly’s book Java Message Service.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.249.42