Week 14 - April 20, 1999

Exception handling : try/capture/finally
Client/Server Architecture
Implementing the Server
Implementing the Client

Exception handling : try/capture/finally

Sometime we need to use some functions that may generate errors: translating a String into a number, opening a file or a network connection, downloading an image, etc. Since these errors are generated inside the methods we are calling, we can not check them or capture them always just by the return code. This is why in Java we have a mechanism to capture the errors and to process them in the way we want, without letting our program to crash. The universal error "Fatal Exception Error at Module: FA019CA" is "copyrighted" by the little (micro) and soft ones, thus I do not want to see it generated by your code J . You should write your code to take care of its own errors, if any, and give the user plenty of information so he an understand why your program has crashed.

The way we capture the errors is very easy. First tell the JVM to try our code and see if it works, and if not, we want to process ourselves the errors, therefore we will catch them one by one before the operating system will notice and finally we will do the exit code.

For example:

...
try {
        // put here the code that
        // may cause you problems
}
catch (SomeException e1) {
        // do what you have to do
        // to correct the problem 
        // and eliminate the error e1
}
catch (AnotherException e2) {
        //some as above, but for
        //the type of error which is e2
}
finally {
        //this code will be execute no matter if
        // the exit from try was normal or any of the 
        // error capture above
}
Note: Do not use ; after catch if you want to use another catch or finally.

To learn which exception you have to capture, look at the class descriptions in the Java in a Nutshell book. If you are using such methods inside your methods, and you want to be able to capture the exceptions from outside the method, declare the exceptions thrown by your method when you declare it in your class. For example, a method that may throw exceptions of types IOException and UnsatisfiedLinkError should be declare like:

public void myFunction( … ) throws IOException, UnsatisfiedLinkException {
        ...
}

Client/Server Architecture

The server side in a Client/Server architecture is the part that a) manages the network connection accepting or rejecting calls on a given TCP/IP port (or socket) and b) does all the computations for the whole application. The client part asks for the connection and then displays data and creates the GUI for the server application. One can find examples of client/server applications all over the place: your Internet browser is the client side of the HTTP server, you ftp application (Fetch or WS-FTP) is the client side of the FTP server, etc. We will focus today on the networking part of this problem.

First, we have to understand how this whole thing works. The server has a given TCP/IP port (do not make a confusion between this port and the IP number; a computer has a unique IP number, but it can have many ports) where it listens for connection requests. The client willing to start a connection will go to this port and will ask for a connection. The server check and authenticates the client and opens a new set of ports (one for the input stream and one for the output stream) and tells the client which ports to use. Then it creates a new process (thread) that will manage the given pair of ports. In the mean time, the client closes the connection with the server on the main port and opens a new one on the specified pair of ports. The client will write to the port opened by the server for input and will read from the port opened for output. Now, the two sides can communicate and the main port is free for another client to come and ask for connection.

Implementing the Server

The servers uses two classes to listen and create the connection with the client. These two classes are ServerSocket and Socket. The ServerSocket class listens for a connection request and creates a new Socket class once the connection was approved. After that, the server goes back in listening mode.

Note: The Server program has to be a multithread program and it can not be an applet. It has to be a stand-alone application running on a machine that has the TCP/IP protocol configured.

Once the Socket connection was created, the application will create a new thread (in this example a new Connection object, which is an extension of the Thread class). This new thread will take care of the communication between the server and the client.

Let's take a look at the code for the server first.

import java.io.*;
import java.net.*;

public class Server extends Thread {
        public final static int DEFAULT_PORT=6789;
        protected int port;
        protected ServerSocket listen_socket;
        public Connection[] c = new Connection[10];
        public int numc=0;

        //Exit with an error message when an exception occurs
        public static void fail(Exception e, String msg) {
                System.err.println(msg+":"+e);
                System.exit(1);
        }

        
        //Create a ServerSocket to listen for connections on
        public Server (int port) {
                if (port==0) 
                        port=DEFAULT_PORT;
                this.port=port;
                try {listen_socket = new ServerSocket(port);}
                catch (IOException e) 
                        {fail(e,"Exception creating server socket");};
                System.out.println("Server: listening on port"+port);
                this.start();
        }

        //the body of the server thread; loops forever
        //accepting connections from clients
        public void run() {
           try {
                while(true) {
                  Socket client_socket=listen_socket.accept();
                  if (numc<9)
                    c[numc++]=new Connection(client_socket,this);
                  else
                    client_socket.close();
                }
           }
           catch (IOException e) 
                {fail(e,"Exception while listening for connetion");};
        }

        //start server up, listening on an optional port
        public static void main(String[] args) {
          int port=0;
          if (args.length ==1) {
            try {port = Integer.parseInt(args[0]);}
            catch (NumberFormatException e) {port=0;};
          }
          new Server(port);
        }
}

class Connection extends Thread {
        protected Socket client;
        protected DataInputStream in;
        protected PrintStream out;
        public Server parent;

        //Initialize the stream and start the thread;
        public Connection(Socket client_socket, Server parent) {
          this.parent=parent;
          client=client_socket;
          try {
            in = new DataInputStream(client.getInputStream());
            out = new PrintStream(client.getOutputStream());
          }
          catch (IOException e) {
            try {client.close();} catch (IOException e2);
            System.err.println("Exception while getting socket streams:"+e);
            return;
          }
          this.start();
        }

        //Provide the service; this is what the server has to do 
        public void run() {
          String line;
          int len;
          try {
            for (;;) {
              //read in a line
              line=in.readLine();
              if (line==null) break;
              //print the line back
              this.SendMsg(line);
            }
          }
          catch (IOException e){}
          finally {
            try {client.close();} catch (IOException e2){};
            int i=0;
            while ((parent.c[i]!=this} && (i<parent.numc)) i++;
            if ((i!=parent.numc) && (i!=(parent.numc-1))) {
              parent.c[i]=parent.c[numc-1];
              parent.numc--;
            }
          }              
        }
        
        public void SendMsg(String msg) {
            for (int i=0;i<parent.numc;i++)
                parent.c[i].out.println(msg);
        }
}

Implementing the Client

Now, let's see what the client is doing. The client, when started, will contact the original site (since it is the only site that it can contact due to security restrictions inside java applets) and will ask for connection. Once the sever opens a port for it, it will open the two channels (in and out) and start communicating. We have a thread called StreamListener, which listens to all the incoming messages from the server and add them to the TextArea.
import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;

public class Client extends Applet {
        public static final int port = 6789;
        Socket s;
        DataInputStream in;
        PrintStream out;
        TextField inputfield;
        TextArea outputarea;
        StreamListener listener;

        public void init() {
           try{
                s= new Socket(this.getCodeBase().getHost(),port);
                in=new DataInputStream(s.getInputStream());
                out = new PrintStream(s.getOutputStream());
                inputfield= new TextField();
                outputarea = new TextArea();
                outputarea.setEditable(false);
                this.setLayout(new BorderLayout());
                this.add("South",inputfield);
                this.add("Center",outputarea);
                listener = new StreamListener (in, outputarea);
                this.showStatus("Connected to: "+s.getInetAddress().getHostName()+
                        ":"+s.getPort());
          }
          catch (IOException e) this.showStatus(e.toString());
        }

        public boolean action(Event e, Object what) {
                if (e.target==inputfield) {
                        out.println((String)e.arg);
                        inputfield.setText("");
                        return true;
                }
                return super.action(e,what);
        }
}

class StreamListener extends Thread {
        DataInputStream in;
        TextArea output;
        
        public StreamListener(DataInputStream in, TextArea output) {
                this.in = in;
                this.output=output;
                this.start();
        }

        public void run() {
                String line;
                try {
                        for (;;) {
                                line=in.readLine();
                                if (line==null) break;
                                output.setText(line);
                        }
                }
                catch (IOException e) output.setText(e.toString());
                finally output.setText("Connection closed by server.");
        }
}
Now, you can take a look at the applet itself. If you can not see the applet, it means that the server part is not running.