Labels

Thursday, September 16, 2010

Basic Java Socket Programming: Creating a Simple IRC Service

Welcome to JavaLand.  Here I will attempt to explain about the basic Java socket programming by creating a simple client/server Chat project (SimpleIRC).

The basic idea for the server is as follow:


ChatServer.java
  • Listen on a port until a client connects using the SocketServer.accept()
  • Once, connected, create a ChatService and have it handle that connection in a separate thread
  • Now, go back to listening mode again for next connection.
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Basic Chat Server (SimpleIRC)
 * @author TA Nguyen
 */
public class ChatServer
{
  // Defines the port that this server will listen on
   final int SBAP_PORT = 80;
   
   public static void main(String[] args) throws IOException
   {
       // Create a new mediator
       Mediator mediator = new Mediator();

       // Create a new server object
       ServerSocket server = new ServerSocket(SBAP_PORT);
       System.out.println("");
       System.out.println("Press CTRL+C to terminate.");
       System.out.println("Waiting for clients to connect...");

       /**
        * Main loop of the application.
        */
       while (true) {
        // wait for client to connect
           Socket socket = server.accept();
           System.out.println("Client connected.");

           // ok, connected, 
           //now create ChatService to handle this connection
           ChatService service = new ChatService(socket, mediator);

           // tell the JVM to run it in a thread
           Thread thread = new Thread(service);
           thread.start();
     
           // now, we go back to wait for next connection.
       }
   }
}

ChatService.java
  • Listen for incoming message and tell the Mediator to send it to all connecting clients
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * ChatService: Executes BChat commands from a socket.
 * @author TA Nguyen
 */
public class ChatService implements Runnable
{
    private Socket socket;
    private Scanner in;
    private Mediator mediator;
    private PrintWriter writer;


    /**
     * Constructs a service object that processes commands
     * from a socket utilizing the mediator service.
     * @param socket the Socket
     * @param mediator the Mediator
     */
    public ChatService(Socket socket, Mediator mediator) {
        this.socket = socket;
        mediator = mediator;
    }


    /**
     * Add new i/o services and call doService()
     * which performs the primary chat functions.
     *
     * When doService() returns, cleans up and shuts down the thread.
     */
    public void run() {
        try {
            try {
                in = new Scanner(socket.getInputStream());
                writer = new PrintWriter(socket.getOutputStream());
                mediator.add(writer);
                doService();
            } finally {
                mediator.remove(writer);
                socket.close();
                System.out.println("Client disconnected.");
            }
        } catch (IOException exception) {
            System.out.println("Serverside error occured.");
            exception.printStackTrace();
        }
    }


    /**
     * Prints every command received.  If the user enters "QUIT",
     * then the thread terminates.
     */
    public void doService() throws IOException {
        while (true) {
            if (!in.hasNext()) {
                return;
            }

            String command = in.nextLine();
            if (command.equals("QUIT")) {
                return;
            } else {
                executeCommand(command);
            }
        }
    }


    /**
     * Executes a single command.
     * @param command the command to execute
     */
    public void executeCommand(String command) {
        mediator.writeMessage(command);
    }
}

Mediator.java
  • keep track of all the outgoing socket (clients)
  • loop and display received a message to all clients
import java.io.PrintWriter;
import java.util.LinkedList;

/**
 * Mediator:
 * 1. Stores and maintains a list of the clients 
 *    that are connected to the server.
 * 2. Provide a relay of received messages 
 *    to all connected clients.
 *
 * This list must be synchronized across all threads 
 * to maintain access to each connection.
 *
 * @author TA Nguyen
 */
public class Mediator {
    private LinkedList connections;


    /**
     * Constructor creates the list that maintains the connections
     */
    public Mediator() {
        connections = new LinkedList();
    }


    /**
     * Accepts a message and sends it out to all of the clients
     * @param Message the message to broadcast to all connections
     */
    public void writeMessage(String message) {
        System.out.println("Recieved: " + message);
        for (PrintWriter out : connections) {
            out.println(message);
            out.flush();
        }
    }


    /**
     * Adds a new connection
     * @param Out the new connection to add to the list
     */
    public void add(PrintWriter out) {
        connections.add(out);
    }


    /**
     * Removes a connection
     * @param Out the connection to remove
     */
    public void remove(PrintWriter out) {
        connections.remove(out);
    }
}

ChatClient.java
  • Establish a connection to the ChatServer
  • Create a DisplayService and run it in a thread to handle server income message
  • Loop and wait for a user to enter a message, then send it to the server.
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;


/**
 * Basic Chat Client (BChat)
 *
 * @author TA Nguyen
 */
public class ChatClient 
{
  // The port to connect to
    private static final int BCHAT_PORT = 80;
    private static final String BCHAT_IP = "localhost";
    
    public static void main(String[] args) throws IOException {

        // Create the socket and connect to the predefined server:port
        Socket socket = new Socket(WTSCS_IP, WTSCS_PORT);

        // Create the input and output streams, from this socket connection
        InputStream instream = socket.getInputStream();
        OutputStream outstream = socket.getOutputStream();

        // Create a scanner and print writer for local i/o
        Scanner socketIn = new Scanner(instream);
        PrintWriter socketOut = new PrintWriter(outstream);
        Scanner keyIn = new Scanner(System.in);

        // Define the display service and instruct it to listen 
        // to the socket input stream, display all messages the come in.
        DisplayService service = new DisplayService(socketIn);

        // Well, do it (display) in a thread so that 
        // we can type without being blocked by incoming messages.
        Thread thread = new Thread(service);
        thread.start();

        // first, get the client name and remember it.
        System.out.print("Enter your name: ");
        String name = keyIn.nextLine();

        while (true) {
            // read a line of message from (system/console) input stream 
            String line = keyIn.nextLine();
            
            // write the message with client name to the socket output stream
            socketOut.println(name + ": " + line);
            socketOut.flush();
            
            // if the last message is "QUIT" then stop.
            if ("QUIT".equals(line)) {
                break;
            }
        }
        socket.close();
    }
}

DisplayService.java
  • Wait for a message from the server and display it to the console.
import java.util.Scanner;

/**
 * DisplayService: 
 * Displays all input received.
 *
 * @author TA Nguyen
 */
public class DisplayService implements Runnable {
    private Scanner scanner;
    
    
    /**
     * Constructor receives a scanner object for i/o operations.
     *
     * @param scanner The source to read input from
     */
    public DisplayService(Scanner scanner) {
        this.scanner = scanner;
    }
    
    
    /**
     * Get input from the scanner and print the line to the output.
     */
    public void run() {
        while (true) {
            if(scanner.hasNext()) {
                String line = scanner.nextLine();
                System.out.println(line);
            } else {
                return;
            }
        }
    }
}

Now, that you have the basic understanding of client/server using the socket and a working chat client/server, I challenge you to take this to a step further and add a GUI to the client, along with the Avatar image for the client.  Hint you can use the java serialization to send complete Java object over the socket.

Note: this demo was originally written as part of the class exercise for CSC-251 class that I teach at WakeTech.edu
Enjoy,
T.A. Nguyen