Title: Networking part 1
Course: CPS 261: Advanced Java Concepts ~ DL & MM MASTER

Networking part 1

Java Tutorial : Custom Networking - This Url talks about how to work with the Java Networking concepts (this is where I acquired my first knowledge on Java support for Sockets)

Socket Programming

ServerSockets:

Transmission Control Protocol and Internet Protocol:

- TCP/IP is the standard Protocol that almost everything uses.
- A Socket is a data structure or a Class that allows you to communicate with others using TCP/IP. Email, HTTP, FTP, etc. all use Sockets and TCP/IP.
- The concept of Sockets originated With Berkeley Unix with the C Programming language.
- The notion of Sockets has been implemented on many platforms including Java.
- Working with sockets is in many ways similar to an open file. You will be able to communicate with Sockets using classes like Scanner, PrintStream, BufferedReader, ObjectInputStream, ObjectOutputStream, etc.

The system needs to keep track of all of the connections that exist. The following depicts a mechanism used for providing each connection with a unique idenifier:

Java Programming Concepts:

- The ServerSocket class is used by Servers which must bind to a port that is not in use by any other server on the host machine.
- The Socket class is used by Clients to connect to their desired Servers. The Client must know the hostName as either a domain name (myplace.com) or as an IP Address (102.45.67.34)
- The InetAddress class can be used to find the domain name and IP address of the machine you are on.
- The Server task will call the "accept" method to wait for incomming connections
- The accept method returns a Socket when an incomming connection has arrived.
- Both the server task and the client task can call getInputStream() to get an InputStream, and call getOutputStream() to get an OutputStream. Once you have an InputStream and an OutputStream conversation can begin.
- A task can wrap the InputStream with any of your favorite buffering mechanisms: Scanner, BufferedReader, ObjectInputStream, etc.
- A task can wrap the OutputStream with any of your favorite buffering mechanisms: PrintStream, PrintWriter, BufferedWriter, BufferedOutputStream, ObjectOutputStream, etc.
- You must use the appropriate protocol for the Server you chose: Http, Ftp, Telnet, SMTP, EchoServer, RPS, etc.

World's Simplest Protocol: Echo Server

EchoServerSingleThreaded.java (very much brute force)


import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class EchoServerSingleThreaded {

 public static void main(String[] args) throws IOException {
     int portNum = 4444;
     InetAddress iaddr=null;

     ServerSocket serverSocket = null;

     try {
            serverSocket = new ServerSocket(portNum);

            iaddr = InetAddress.getLocalHost();
            System.out.println("Host:" + iaddr.getHostName()+
                 " ipaddr=" + iaddr.getHostAddress()+
                 " portnum="+ portNum);
        }
     catch (IOException e)
        {
            System.err.println("Socket Err:" + e);
            System.exit(1);
        }

     Socket clientSocket = null;

     try{
            clientSocket = serverSocket.accept();
        }
     catch (IOException e) {
            System.err.println("Accept failed." + e);
            System.exit(1);
        }

     PrintStream out = new PrintStream(
                           clientSocket.getOutputStream(), true);


     InputStream is = clientSocket.getInputStream();
     Scanner scanStream = new Scanner(is);


     while (scanStream.hasNextLine()) // Read from socket
       {
             out.println(scanStream.nextLine()); // Echo data read
       }
     out.close();
     scanStream.close();
     clientSocket.close();
     serverSocket.close();
     System.out.println("Exitting ");
    }
}



Note that it is helpful to setup 2 instances of Eclipse for most of these notes.

It's best to run the Server code in one instance of eclipse, and the Client code in another instance of eclipse.
Otherwise, it gets confusing looking at the output from both sides of the conversation.
You will need 2 separate workspaces to simultaneously run 2 eclipses. See the video on this.

Client Sockets:

When we are the Client, we want to use the Socket class instead of the ServerSocket class. The constructor to the Socket class takes an address and a port number. The address can be something like "gettysburg.wccnet.org", or "198.111.176.24".

Also just like before, we can call getOutputStream and getInputStream methods to obtain OutputStream and InputStream classes to communicate with out server.

EchoClient.java

This client can actually be used to talk to more than the Echo Server.
This client will take one line of input and send it to the Server, and then read one line of output and print it to the screen.
This client can be used for other simple protocols like RPS: Rock-Paper-Scissors


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class EchoClient {
    Socket socket=null;

    void communicate(InputStream is, OutputStream os)
    {
        Scanner scanSocket = new Scanner(is);

        PrintStream psSocket = new PrintStream(os, true);

        Scanner keyboard = new Scanner(System.in);

        System.out.println("Start entering text to echo ....");
        String fromKeyboard= keyboard.nextLine();
        while (!fromKeyboard.equals("quit"))
        {
            psSocket.println(fromKeyboard); // write to socket
            String sFromSocket = scanSocket.nextLine(); // read from socket
            System.out.println("received from server: "+sFromSocket);
            fromKeyboard = keyboard.nextLine();
        }
        scanSocket.close();
        psSocket.close();
        keyboard.close();
        System.out.println("Exitting communicate");
    }

public void startup(String[] args){

        String hostName="localhost";
        int portNum=4444;
        InputStream is = null;
        OutputStream os = null;

        if (args.length > 0)
            hostName = args[0];
        if (args.length > 1)
        {
            // An exception can occur here if bad data is entered
            portNum = Integer.parseInt(args[1]);
        }

        try {
            socket = new Socket(hostName, portNum);
            is = socket.getInputStream();
            os = socket.getOutputStream();

            communicate(is,os);

            is.close();
            os.close();
        }
        catch (UnknownHostException e) {
            System.out.println("startup.UnknownHostException: "+e);
        }
        catch (IOException e) {
            System.out.println("startup.IOException: "+e);
        }
        System.out.println("Exitting startup");
    }

    public static void main(String[] args) {


        EchoClient echoClient= new EchoClient();
        echoClient.startup(args);
        System.out.println("Exitting main");

    }

}





A Better Echo Server

We need a more functional Server that has a better structure. Here are the things we will accomplish:

1. We want the server to run in a thread that is not the main thread.
2. We want the main thread to have a way to terminate the server.
3. We want the Server to pass off the actual connection to another thread to allow the Server to immediately go back to the accept call.
4. Once an accept has occurred we want a generic way to pass off the connection socket to some other process. To make a flexible approach, we use the Interface ConnectionListener.
5. Using the above technique we can reuse this code for many protocol handlers.

ConnectListener.java


import java.net.Socket;

public interface ConnectListener {
    public void handleConnect(Socket s);

}


MotherServer.java


import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

public class MotherServer implements Runnable {

    ServerSocket serverSocket = null;
    int portNum=-1;
    ConnectListener connectListener= null;
    boolean continueListening = true;
    Thread myThread = null;

    boolean startServer(int portNum, ConnectListener connectListener)
    {
        this.portNum = portNum;
        try {
            serverSocket = new ServerSocket(portNum);
        } catch (IOException e) {
            System.out.println("ServerSocket connect error: " + e);
            return false;
        }
        this.connectListener = connectListener;
        myThread = new Thread(this);
        myThread.start();
        return true;
    }

    public void run() // This is where the Server executes
    {
        continueListening = true;
        while(continueListening)
        {
            try {
                Socket sock = serverSocket.accept();
                connectListener.handleConnect(sock);
            } catch (IOException e) {
                System.out.println("ServerSocket accept error: " + e);
                continueListening = false;
            }
        }
        System.out.println("MyServer run thread exitting");
    }

    public void stopServer()
    {
        continueListening = false;
        try {
            serverSocket.close();
        } catch (IOException e) {
            System.out.println("stopServer error: " + e);
        }
    }

    static String getHostInfo()
    {
        String retStr="";
        try {
            InetAddress iaddr = InetAddress.getLocalHost();
            retStr= String.format("Host:%s ipaddr=%s",
                    iaddr.getHostName(), iaddr.getHostAddress());
        }
        catch (UnknownHostException e) {
            retStr = e.toString();
        }

        return retStr;
    }
}



EchoServer.java


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class EchoServer implements ConnectListener, Runnable, Cloneable{

    Socket clientSocket=null;
    Thread myThread = null;




    public void handleConnect(Socket s) {

        clientSocket = s;

        // Something very unique is happening here.
        // We want each connection to run on its own object
        // However we don't want to call the EchoServer constructor to get this.
        // We want this class to be the base of many other protocol handler classes.
        // By calling clone, we will make an instance that has the characteristics of the
        // derived class instead of the EchoServer.


        try {
            EchoServer es = (EchoServer)clone();

            Thread myThread = new Thread(es);
            System.out.println("Launching a new Child Thread");
            myThread.start();
        }
        catch (CloneNotSupportedException e) {
            System.out.println("EchoServer.handleConnect Clone error: "+e);
        }
    }
    public void run()
    {
        InputStream is=null;
        OutputStream os=null;
        try {
            is = clientSocket.getInputStream();
            os = clientSocket.getOutputStream();

            processStream(is, os);

        }
        catch (IOException e) {
            System.out.println("Echo Server Exception: "+e);
        }
        finally
        {
            try
            {
                if (is != null) is.close();
                if (os != null) os.close();
            }
            catch (IOException e)
            {
                System.out.println("run:  Stupid exception that shouldn't happen");
            }
        }
        System.out.println("Exitting Child Server Thread");
    }
    void processStream(InputStream is, OutputStream os)
    {
        System.out.println("EchoServer.processStream begins");
        Scanner input = new Scanner(is);
        PrintStream ps = new PrintStream(os);

        while (input.hasNextLine())
        {
            processNextLine(input, ps);
        }
        input.close();
        ps.close();
        System.out.println("Exitting processStream");
    }
    void processNextLine(Scanner input, PrintStream ps)
    {
        String s= input.nextLine();
        ps.println(s);
        System.out.println("EchoServer echoed: "+s);
    }

    void monitorServer()
    {
        MotherServer myServer = new MotherServer();
        System.out.println(MotherServer.getHostInfo());
        myServer.startServer(4444, this);

        Scanner keyboard = new Scanner(System.in);
        String s="";
        while (!s.equals("quit"))
        {
            System.out.println("Whenever you want to stop the server, enter \"quit\" ");
            s = keyboard.next();
        }
        keyboard.close();
        myServer.stopServer();
    }
    public static void main(String[] args) {

        EchoServer es = new EchoServer();
        es.monitorServer();
        System.out.println("Exitting EchoServer");

    }



}



Rock Paper Scissors Protocol

Here is our first example of another protocol handler that will make use of the EchoServer class features.

Note that the EchoServer class code will all be used except for the "processNextLine" method which we are overriding

RPS Game:

- You pick one of { r, p, s} for Rock Paper Scissors. Normally you play this against another player who does the same thing.
- In our case, the computer will generate the other players choice on {r,p, s}
- Results:
  - If both players pick the same thing, the game is a tie
  - Rock beats Scissors ( Rock breaks Scissors)
  - Scissors beats paper (Scissors can cut paper)
  - Paper beats Rock (Paper can cover Rock .... I never quit understood this logic??????)

RPS_Server.java


import java.io.PrintStream;
import java.util.Scanner;

public class RPS_Server extends EchoServer{
    static final char paper ='p';
    static final char rock = 'r';
    static final char scissors ='s';

//Overriding from EchoServer
    void processNextLine(Scanner input, PrintStream ps)
    {
        String s= input.nextLine().toLowerCase();
        if (s.length() == 0)
        {
            ps.println("Enter r,p,s for Rock, Paper, Scissors");
            return;
        }

        char clientChoice= s.charAt(0);
        char computerChoice = computerPick();

        System.out.println("You: "+ clientChoice+ " Computer: "+ computerChoice);
        String response = "You: " + rps_results(clientChoice,  computerChoice);

        ps.println(response);
        System.out.println("EchoServer echoed: "+response);
    }

    private char computerPick()
    {
        switch((int)(3* Math.random()))
        {
        case 0:
            return scissors;
        case 1:
            return paper;
        case 2:
            return rock;
        }
        return '?'; // Bad input and shouldn't happen
    }

    String rps_results(char userInput,  char otherInput)
    {
        String response=" LOSE :-) !!";

        if ( userInput == otherInput)
        {
            response =  " Tied";
        }
        else
        {
            switch (userInput)
            {
            case scissors:
                if (otherInput == paper)
                    response= " WIN!!";
                break;
            case rock:
                if (otherInput == scissors)
                    response =  " WIN!!";
                break;
            case paper:
                if (otherInput == rock)
                    response = " WIN!!";
                break;
            default:
                response = " had bad input: " +userInput +
                            " -- Enter r,p,s for Rock, Paper, Scissors";
            }
        }
        return response;
    }

    public static void main(String[] args) {
        RPS_Server es = new RPS_Server();
        es.monitorServer();
        System.out.println("Exitting RPS_Server");

    }

}



Simple Http Server

- We will use EchoServer to make a simplified Http Server. For the sake of simplicity, we are only trying to respond to a very small subset of http functionality.
- However, there is nothing to prevent you from enhancing this into a full fledged Http Server if you wanted to. Not sure what you would do with it except that it would be a fun learning experience.
- This time we will override the processStream method to give us more options on what we can do.
- To use this code:
  - Put some html files at the Project level of your Project: I will be using the files home.html, like.html, gettysburg.html
  - My client will be a browser. Let hostName be the IpAddress of your HttpServer. If your server is running on the same machine as your browser, then you can use hostName=localhost
  - Assuming that your host is still using my default port number of 4444, then the possible URLs for this HttpServer are:
    - hostName:4444
    - hostName:4444/home.html
    - hostName:4444/like.html
    - hostName:4444/gettysburg.html - Note that when you run this, you will see some errors show up on the Server side, because the browser will be requesting some image files which don't exist. Also I haven't provided the code to deal with images. It's not difficult, but it is more than I want to do in this example.

HttpServer.java


import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Scanner;
import java.util.StringTokenizer;

public class HttpServer extends EchoServer{
    // Overriding processStream from EchoServer

    void processStream(InputStream is, OutputStream os)
    {
        System.out.println("HttpServer.processStream begins");

        PrintStream ps = new PrintStream(os);
        BufferedReader input = new BufferedReader(new InputStreamReader(is));

        String cmd;
        try
        {
            // We are only looking at GET requests:
            // We will be looking for something like:
            // GET /like.html HTTP/1.1

            cmd= input.readLine(); // We are only looking at the first line
            System.out.println(cmd);
            StringTokenizer parse = null;
            if (cmd != null)
                parse = new StringTokenizer(cmd);
            String fileName="home.html";
            if (parse!= null && parse.countTokens() >= 2)
            {
                String method = parse.nextToken();  // Should contain GET
                String tempFileName=parse.nextToken().trim();         // Could contain a filename
             // We expect either "/"  or "/someFileName"
                if (!tempFileName.equals("/"))
                    fileName = tempFileName.substring(1); // strip off leading "/"

                System.out.println("method="+method);
                System.out.println("fileName="+fileName);
            }


            ps.println("HTTP/1.1 200 OK");
            ps.println("content-type: text/html");

// This blank line says we are done with HTTP headers and ready to send the data
            ps.println();

// Send out our html file to the browser
            File f = new File(fileName);
            Scanner readIt = new Scanner(f);
            while (readIt.hasNextLine())
            {
                String s = readIt.nextLine();
                ps.println(s);
            }
// closing the TCP/IP connection tells the browser we are done.
            input.close();
            ps.close();
            is.close();
            os.close();
        }
        catch (IOException e)
        {
            System.out.println("HttpServer.processStream IOException: "+e);
        }
        System.out.println("Exitting processStream");
    }
    public static void main(String[] args) {
        HttpServer es = new HttpServer();
        es.monitorServer();
        System.out.println("Exitting HttpServer");

    }

}



Last Modified: July 22, 2014 8:51 PM