CS 4773 Object Oriented Systems
Network Communication

Start of Class Friday

Previous Topic: Grid Bag Layout

Introduction
The Client-Server Model
Server Methods
Client Methods
Sending and Receiving Data
Client and Server Code Example
Drivers for the Client and Server Code Example
The User Interface

Next Topic: Monopoly Design


Source code for the examples are in /usr/local/course/cs4773/spring2000/examples/set9

Introduction

Network communication is Java is fairly simple.
This topic will describe client-server connection-oriented communication using stream sockets.
If you took OS at UTSA recently you have seen the UICI interface.
Java network communication is very similar.

There are restrictions as far as what you can do from inside of an applet.
A client applet can only establish communication with a server running on the host that the applet was loaded from.
There is no such restriction on an application.

The Client-Server Model

In the connection-oriented client-server model of communication, a server waits for a connection request from a client.
When the request come in, a communication channel is set up which allows the server and client to communicate.

Communication requests to a server are distinguished by a port number.
A port is just an integer (usually a small one, but in Java, an int is used for a port number).
Certain port numbers are reserved.
For example, 21 is used by ftp and 2049 is reserved for nfs.

On a given machine, at most one process can act as a server for a given port, but any number of clients can use that port number.

Server Methods

A server sets up a ServerSocket by specifying what port number it will listen on.

ServerSocket s = new ServerSocket(port,queueLength);
This does the same thing as the UICI u_open or the socket calls socket, bind and listen.

A server waits for a connection using:
Socket connection = s.accept();
This blocks until a connection request comes in from a client.
It is the similar to the UICI u_listen or the socket accept.

A server that return from the accept can then create a thread to handle the connection while it does another accept.

Each of these server methods can throw an IOException.

Client Methods

The client is simpler than the server.
For a client to make a connection to a giver host on a given port, it is only necessary to execute:
Socket connection = new Socket("servername",port);
If this fails an IOException is thrown.

Sending and Receiving Data

Once a connection is made, the server and the client can communicate.
There are many ways to do this. This simplest is to use:
   DataInputStream input;
   DataOutputStream output;

   input = new DataInputStream(connection.getInputStream());
   output = new DataOutputStream(connection.getOutputStream());
Each of these can throw an IOException so you should enclose these in a try catch.

A DataInputStream or DataOutputStream represents Unicode strings. They allow for the transfer of information in a machine-independent way.

Here are some of the methods that can be used with a dataOutputStream

   public final void writeBoolean(boolean v);
   public final void writeByte(int v);
   public final void writeBytes(String s);
   public final void writeChar(int v);
   public final void writeChars(String s);
   public final void writeDouble(double v);
   public final void writeInt(int v);
   public final void writeLong(long v);
   public final void WriteShort(int v);
   public void write(int b);
   public void write(byte b[], int off, int len);
   public void flush();
All of these can throw an IOException.

There are corresponding methods for reading from a DataInputStream.

   public final boolean readBoolean();
   public final byte readByte();
   public final char readChar();
   public final double ReadDouble();
   public final int readInt();
   public final long readLong();
   public final short readShort();
   public final int read(byte b[]);
   public final int read(byte b[], int off, int len);

Client and Server Code Example

Problem: Have two users send text to each other so that they can each see what the other has sent.

The following pieces will be designed:

How does an obect receive a message?:
Using a method

In this case the method has just a String as a parameter, say

public boolean receiveText(String str);

The return value indicates whether the parameter was processed correctly.

How does the Network Listener know where to send its output?
It must know which textReceiver to send it to.

Since in Java you cannot pass a method as a parameter, you must pass an object which has a textReceiver method.

Question: What kind of objects have a textReceiver method?

Answer: Those that implement TextReceiver:

public interface TextReceiver {

   public boolean receiveText(String str);

}
So the Network Listener must be passed a socket and a TextReceiver.
The Network Listener class will be called SocketReceiver
import java.net.*;
import java.io.*;

public class SocketReceiver extends Thread {

   private final int BUFSIZE = 1024;
   private DataInputStream input;
   private TextReceiver tr;

   public SocketReceiver(Socket connection, TextReceiver tr) {

      this.tr = tr;
      try {
         input = new DataInputStream(connection.getInputStream());
         start();
      } catch (IOException e) {}
   }

   public void run() {
      byte[] buffer;
      int bytesread;

      buffer = new byte[BUFSIZE];
      for ( ; ; ) {
         try {
            bytesread = input.read(buffer);
         } catch (IOException e) {
            try {
               input.close();
            } catch (IOException e1) {}
            return;
         }
         if (bytesread > 0) 
            tr.receiveText(new String(buffer,0,bytesread));
      }
   }
}

The Network Sender must be receive a String (implements TextReceiver) and must be given a Socket to send information out on.
It is also passed a TextReceiver for error messages.

The Network Sender is called SocketSender:

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

public class SocketSender implements TextReceiver {

   private TextReceiver tr;
   private DataOutputStream output;

   public SocketSender(Socket connection, TextReceiver tr) {

      this.tr = tr;
      try {
         output = new DataOutputStream(connection.getOutputStream());
      } catch (IOException e) {
         tr.receiveText("An IO error occurred setting up the output stream");
      }
   }

   public boolean receiveText(String str) {
      if (output == null) return false;
      try {
         output.writeBytes(str);
      } catch (IOException e) {
         try {
            output.close();
         } catch (IOException e1) {}
         return false;
      }
      return true;
   }
}
            

We will talk about the implementation of the User Inerface, called IOFrame later.
Except for this, we are ready to produce server and client classes.

The Server class needs a port number to listen on and a place to put what is read from the network (TextReceiver).
It must be able to accept text to be sent to the network (implements TextReceiver.
It creates a SocketSender for sending and a SocketReceiver for receiving.

Here is the Server class:

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

public class Server implements TextReceiver {

   private TextReceiver tr;
   private DataOutputStream output;
   SocketSender sender = null;
   private Socket connection;

   public Server(int port, TextReceiver tr) {

      this.tr = tr;
      ServerSocket s;

      try {
         s = new ServerSocket(port,5);
         connection = s.accept();
      } catch (IOException e) {
         tr.receiveText("An IO error occurred in the server");
         return;
      }
      sender = new SocketSender(connection,tr);
      new SocketReceiver(connection,tr);
   }

   public String getHostName() {
      InetAddress inet;
      String remote;
      if (connection == null) return "None";
      inet = connection.getInetAddress();
      remote = inet.getHostName();
      return remote;
   }

   public boolean receiveText(String str) {
      if (sender == null) return false;
      return sender.receiveText(str);
   }
}

The Client is similar to the Server, except that it needs a host name as well as a port:

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

public class Client implements TextReceiver {

   private SocketSender sender = null;

   public Client(String host, int port, TextReceiver tr) {
      Socket connection;

      try {
         connection = new Socket(host,port);
      } catch (IOException e) {
         tr.receiveText("An IO error occurred in the client");
         return;
      }  
      sender = new SocketSender(connection,tr);
      new SocketReceiver(connection,tr);
   }   

   public boolean receiveText(String str) {
      if (sender == null) return false;
      return sender.receiveText(str);
   }
}

Drivers for the Client and Server

We now have all of the utility classes (except for the User Interface. Here are some applications to test this out.

The ServerMain is an application which puts up an IOFrame and waits on a given port. Note that this class does not have to know anyting about how the network connection actually works.

import java.awt.*;

class ServerMain {

   public static void main(String args[]) {
      int port;
      IOFrame io;
      Server server;

      if (args.length != 1) {
         System.out.println("Usage: ServerMain portnumber");
         return;
      }

      port = Integer.parseInt(args[0]);
      System.out.println("Using port number "+port);
      io = new IOFrame("Server waiting on port "+port,400,400,null);
      io.setBackgrounds(Color.cyan,Color.yellow);
      server = new Server(port,io);
      io.setReceiver(server);
      io.setTitle("Server connected to "+server.getHostName()+" on port "+port);

   }
}

The ClientMain is similar. It takes two command line arguments, a port number and a host name.

import java.awt.*;

class ClientMain {

   public static void main(String args[]) {
      int port;
      IOFrame io;
      Client client;

      if (args.length != 2) {
         System.out.println("Usage: ClientMain host portnumber");
         return;
      }

      port = Integer.parseInt(args[1]);
      System.out.println("Using port number "+port);
      io = new IOFrame("Client",400,400,null);
      io.setBackgrounds(Color.yellow,Color.cyan);
      client = new Client(args[0],port,io);
      io.setReceiver(client);
      io.setTitle("Client connected to "+args[0]+" on port "+args[1]);

   }
}


The User Interface

The only thing left is the User Interface.

Here is what it will look like when a client and server are running:


Here is the IOFrame code:
import java.awt.*;
import java.awt.event.*;

public class IOFrame extends Frame implements TextReceiver, ActionListener {

   private TextReceiver tr;
   private TextField input;
   private TextArea sent;
   private TextArea received;
   private Label receivedLabel;
   private Label sentLabel;
   private int bytesReceived = 0;
   private int bytesSent = 0;
   private int linesReceived = 0;
   private int linesSent = 0;

   public IOFrame(String title, int w, int h, TextReceiver tr) {
      super(title);
      setSize(w,h);
      this.tr = tr;

      setupLayout();
      setVisible(true);

   }

   private void setupLayout() {
      Panel p;
      Panel ReceivedPanel;
      Panel SentPanel;
      receivedLabel = new Label("Received:");
      sentLabel = new Label("Sent:");
      setLayout(new BorderLayout());
      add(input = new TextField(),BorderLayout.SOUTH);
      ReceivedPanel = new Panel();
      ReceivedPanel.setLayout(new BorderLayout());
      ReceivedPanel.add(receivedLabel,BorderLayout.NORTH);
      SentPanel = new Panel();
      SentPanel.setLayout(new BorderLayout());
      SentPanel.add(sentLabel,BorderLayout.NORTH);
      p = new Panel();
      p.setLayout(new GridLayout(2,1));
      ReceivedPanel.add(received = new TextArea(),BorderLayout.CENTER);
      SentPanel.add(sent = new TextArea(),BorderLayout.CENTER);
      p.add(ReceivedPanel);
      p.add(SentPanel);
      received.setEditable(false);
      sent.setEditable(false);
      add(p,BorderLayout.CENTER);
      input.addActionListener(this);
      validate();
   }

   private void setReceivedLabel() {
      receivedLabel.setText("Received bytes: "+bytesReceived+
                         "   Received lines: "+linesReceived);
   }

   private void setSentLabel() {
      sentLabel.setText("Sent bytes: "+bytesSent+
                         "   Sent lines: "+linesSent);
   }

   private int countLines(String str) {
      int count = 0;
      for (int i=0;i < str.length();i++)
         if (str.charAt(i)=='\n') count++;
      return count;

   }

   public void setReceiver(TextReceiver tr) {
      this.tr = tr;
   }

   public void setBackgrounds(Color c1, Color c2) {
      sent.setBackground(c1);
      received.setBackground(c2);
   }

   public boolean receiveText(String str) {
      received.append(str);
      bytesReceived += str.length();
      linesReceived += countLines(str);
      setReceivedLabel();
      return true;
   }

   public void actionPerformed (ActionEvent e) {
      String str;

      str = input.getText() + "\n";
      input.setText("");
      sent.append(str);
      bytesSent += str.length();
      linesSent += countLines(str);
      setSentLabel();
      if (tr != null)
         tr.receiveText(str);
   }
}


We can also use these to create a client applet which can access a server running on the host that the applet was loaded from.

Here is what it might look like:

import java.awt.*;
import java.applet.*;
import java.awt.event.*;

public class ClientApplet extends Applet implements ActionListener {

    String host;
    TextField tf;
    IOFrame io;

   public void init() {

      setLayout(new GridLayout(2,1));
      host = getCodeBase().getHost();
      add(new Label("Enter Port Number for Connection to "+host));
      add(tf = new TextField());
      tf.addActionListener(this);
      validate();
   }

   public void stop() {
      if (io != null)
         io.setVisible(false);
   }

   public void actionPerformed (ActionEvent e) {
      String str;
      int port;
      Client client;
      
      port = Integer.parseInt(tf.getText());
      System.out.println("Connecting to host "+host+" on port "+port);

      io = new IOFrame("Client",400,400,null);
      io.setBackgrounds(Color.yellow,Color.cyan);
      client = new Client(host,port,io);
      io.setReceiver(client);
      io.setTitle("Client connected to "+host+" on port "+port);
   }
}

Next topic: Monopoly Design