Home : Course Map : Chapter 15 :
The Data Client
JavaTech
Course Map
Chapter 15

Client/ServerDesign
Processing Steps
Data Server
Run Data Server
    Demo 1
Data Client
DataClient Applet
    Demo 2
Client/Server Sim
SimServer
    Demo 3
SimClient
Run SimClient
    Demo 4
Exercises

     About JavaTech
     Codes List
     Exercises
     Feedback
     References
     Resources
     Tips
     Topic Index
     Course Guide
     What's New

The DataClient, shown running as an applet on the next page, makes a socket connection to the server - DataServer - and then creates an instance of its helper class DataClientWorker, which grabs data from the server and displays it. (See Diagram 4 on the Client/Server Process page.)

We review the essential parts of the two classes. Refer to the complete code listings.

DataClient Class

The DataClient creates a graphical interface that allows the user to initiate and control communications with the server. The init() method builds the GUI. Note that we use GridBagLayout for this interface, which is more elaborate than for most of our programs so far. See the resulting applet.

  ... From the DataClient class ...
  /**
    * Create a User Interface with a textarea with sroll bars
    *  and a Go button to initiate processing and a Clear button
    * to clear the textarea.
   **/
  public void init () {
    Container content_pane = getContentPane ();
    // Histograms:
    // First hist used to show the values in the data set
    // that is obtained in each transfer from the server.
    fHistData = new Histogram ("Data set values",
                             "Channel Number",
                              10, 0.0, 10.0);
    fHistDataPanel = new HistPanel (fHistData);


    // Second histogram displays the distribution of values
    // for one of the data channels. Use an adaptable histogram
    // since different channels could have different data ranges.
    fHistChan = new HistogramAdaptR1 ("Channel",
                           "Data Units",
                           50, 0.0, 50.0,
                           fHistArraySize);
    fHistChanPanel = new HistPanel (fHistChan);

    // The two hists go onto a sub-panel.
    JPanel hists_panel = new JPanel (new GridLayout (2,1));
    hists_panel.add (fHistDataPanel);
    hists_panel.add (fHistChanPanel);

    // Control fields:

    // First provide the inputs field for the host IP
    // and for the user name.
    fHostField = new JTextField (fHost,16);
    JLabel host_label = new JLabel ("Host: ");
    host_label.setHorizontalAlignment (SwingConstants.RIGHT);
    fUserNameField    = new JTextField (fUserName,16);
    JLabel name_label = new JLabel ("User Name: ");
    name_label.setHorizontalAlignment (SwingConstants.RIGHT);

    // Top line of controls = host and name inputs
    JPanel ctrls_panel1 = new JPanel ();
    ctrls_panel1.add (host_label);
    ctrls_panel1.add (fHostField);
    ctrls_panel1.add (name_label);
    ctrls_panel1.add (fUserNameField);

    // Next line holds the buttons, and the data
    // channel number to monitor.
    fStartButton = new JButton ("Start");
    fStartButton.addActionListener (this);
    fClearButton = new JButton ("Clear");
    fClearButton.addActionListener (this);
    fExitButton = new JButton ("Exit");
    if (fInBrowser)
        fExitButton.setEnabled (false);
    else
        fExitButton.addActionListener (this);

    JPanel buttons_panel = new JPanel ();
    buttons_panel.add (fStartButton);
    buttons_panel.add (fClearButton);
    buttons_panel.add (fExitButton);

    JLabel chan_label = new JLabel ("Channel: ");
    chan_label.setHorizontalAlignment (SwingConstants.RIGHT);
    fChanField = new JTextField ("0",5);

    JPanel chan_panel = new JPanel ();
    chan_panel.add (chan_label);
    chan_panel.add (fChanField);

    fStatusLabel = new JLabel ("Disconnected");
    fStatusLabel.setForeground (Color.RED);
    fStatusLabel.setHorizontalAlignment (SwingConstants.CENTER);

    // Now pack the components of the second ctrls
    // line of components
    JPanel ctrls_panel2 = new JPanel ();
    ctrls_panel2.add (buttons_panel);
    ctrls_panel2.add (chan_panel);
    ctrls_panel2.add (fStatusLabel);

    // Put the 2 lines of controls into a sub-panel
    JPanel ctrls_panel12 = new JPanel ();
    ctrls_panel12.add (ctrls_panel1);
    ctrls_panel12.add (ctrls_panel2);

    fMessageArea = new JTextArea ();
    fMessageArea.setEditable (false);
    // Add to a scroll pane so that a long list of
    // computations can be seen.
    JScrollPane area_scroll_pane = new JScrollPane (fMessageArea);

    // Use a GridBagLayout to apportion space for the
    // controls, text area and histograms.
    JPanel main_panel = new JPanel (new GridBagLayout ());

    GridBagConstraints c = new GridBagConstraints ();
    c.fill = GridBagConstraints.BOTH;

    // Put ctrls at top
    c.gridx = 0;
    c.gridy = 0;
    c. weightx = 1.0;
    c. weighty = 0.05;
    main_panel.add (ctrls_panel12,c);

    // Put text area below the controls
    c.gridx = 0;
    c.gridy = 1;
    c. weightx = 1.0;
    c. weighty = 0.25;
    main_panel.add (area_scroll_pane, c);

    // Put histograms in rest of the vertical space
    c.gridx = 0;
    c.gridy = 2;
    c. weightx = 1.0;
    c. weighty = 0.70;
    c.insets = new Insets (2,2,10,2);
    main_panel.add (hists_panel, c);

    // Add text area with scrolling to the content_pane.
    content_pane.add (main_panel);

  } // init
  ...

 

The user interface provides fields to specify the host address, a user name, and which channel in the data set to histogram. For example, perhaps there are readings from 20 sensors in each data set. We refer to a particular reading in the set of 20 as a channel. You can choose to make a histogram of channel #5 or of any of the other 20 channels.

Another histogram displays the values in a single data set.. For example, a 20 bin histogram would display the value of each of the 20 sensors in the corresponding bin. That is, sensor #0 value goes to bin 0, sensor #1 goes to bin 1, etc. We are just taking advantage of the graphical display provided by the Histogram class to make a chart rather than to make a true "histogram", i.e. a distribution over many readings. We use the pack() method in the Histogram class to replace the histogram bin data array each time the data set arrives.

A click on the "Start" button on the interface leads to the invocation of the start() method, which in turn will invoke the connect() method that makes the socket connection to the server.

 ... continue in the DataClient class ...
  /**
    * Make the connection to the server. Set up the DataReader
    * and begin recording the data from the server.
   **/
  public void start (){

    if (fConnected) stop ();

    // Clear the histograms
    fHistData.clear ();
    fHistData.clear ();

    // Get the current values of the host IP address and
    // and the username
    fHost = fHostField.getText ();
    fUserName = fUserNameField.getText ();
    try {
       fChannelToMonitor = Integer.parseInt (fChanField.getText ());
    } catch (NumberFormatException ex) {
      println ("Bad channel value");
      return;
    }

    // Now try to connect to the DataServer
    try{
      if (connect () ) {

         // Successful so set flags and change button text
         fConnected = true;
         fStartButton.setText ("Stop");
         fStatusLabel.setText ("Connected");
         fStatusLabel.setForeground (Color.BLUE);

      } else {
        println ("* NOT CONNECTED *");
        fStatusLabel.setText ("Disconnected");
        fStatusLabel.setForeground (Color.RED);
      }
    }
    catch (IOException e) {
        println ("* NOT CONNECTED *");
        fStatusLabel.setText ("Disconnected");
        fStatusLabel.setForeground (Color.RED);
    }
  } // start


  /**
    *  Connect to the server via a socket. Throws IOException
    *  if socket connection fails.
   **/
  boolean connect () throws IOException {

    println ("Attempting to connect to server ...");

    try {
       // Connect to the server using the host IP address
       // and the port at the server location
       fServer = new Socket (fHost, fDataServerPort);
    }
    catch (SecurityException se) {
       println ("Security Exception:\n"+se);
       return false;
    }

    println ("Server connected - create worker");

    // Create the worker to tend to this server
    fDataClientWorker =
     new DataClientWorker (this, fServer, fUserName);
    fDataClientWorker.start ();

    return true;
  } // connect

 ...

 

If the client successfully connects to the server, it spins off an instance of the DataClientWorker (discussed below), which will handle communications with the server. After the connection is made, the text on the "Start" button becomes "Stop". If the user clicks on the Stop button, the stop() method will be invoked, via the actionPerformed() method, and this will tell the DataClientWorker to break the connection and die. (Remember that Thread classes can never be reused once they return from run().)

When the DataClientWorker receives data from the server it will call back to the DataClient via the setData() method to pass the data to it. This method packs the histogram with the data array.

  ... continue in the DataClient class ...
   /**
    *  The DataClientWorder passes the data array from the server.
    *  Display the data set by packing a histogram.
    *
    *  Also, plot the distribution of one of the channels of the data.
    *  The channel number is given in the text field.
   **/
   void setData (int [] data) {

      // Display each data set
      fHistData.pack (data, 0, 0, 0.0, (double) (data.length) );
      fHistDataPanel.getScaling ();

      // Plot the distribution of one of the channels in the data.
      if (fChannelToMonitor >= 0 && fChannelToMonitor < data.length) {
          fHistChan.setTitle ("Channel "+ fChannelToMonitor);
          fHistChan.add ((double)data[fChannelToMonitor]);
          // Adapt since data varies from channel to channel.
          fHistChan.rebin ();
          // Now rescale and draw.
          fHistChanPanel.getScaling ();
      }
      repaint ();
   } // setData
  ...

 

DataClientWorker Class

Communications with the server is handled primarily by a DataClientWorker object. This Thread subclass first carries out a simple log-in procedure with the server by passing the user name to it. (A more elaborate version would involve a password as well.)

The code snippet here shows that the run() method invokes the doConnection() method, which sets up the streams with the server. It then invokes the login() method. If the log-in is successful, then the run() begins its data communications session with the server. When this is done, the server is disconnected by invoking the closerServer() method and then the parent DataClient is told of this by calling its setDisconnected() method.

  ... From the DataClientWorker class ...
  /** Remain in a loop to monitor the I/O from the server.
    * Display the data.
   **/
  public void run () {

    // The socket connection was made by the caller, now
    // set up the streams and do a login
    try {
        if (!doConnection ()){
            fDataClient.println ("  Connection/login failed");
            return;
        }
    }
    catch (IOException ioe) {
      fDataClient.println ("  I/O exception with serve:"+ ioe);
    }

   ... code for data communications with server ...

    if  (fServer != null) closeServer ();
    fDataClient.println ("disconnected");
    fDataClient.setDisconnected ();

  } // run

  /** Set up the streams with the server and then login. **/
  boolean doConnection () throws IOException {

    // Get the input and output streams from the socket
    InputStream in = fServer.getInputStream ();

    // Use the reader for obtaining text
    fNetInputReader = new BufferedReader (
        new InputStreamReader (in)) ;

    // User the DataInputStream for getting numerical values.
    fNetInputDataStream = new DataInputStream ( in );

    // Output stream for sending messages to the server.
    fNetOutputDataStream = fServer.getOutputStream ();

    // Write with a PrintWriter for sending text to the server.
    fPrintWriter = new PrintWriter (
      new OutputStreamWriter (fNetOutputDataStream, "8859_1"), true );

    // Now try the login procedure.
    if (!login ()) return false;

    return true;
  } // doConnection

  /** Here is a homemade login protocol. A password could
    * easily be added.
   **/
  boolean login () {

    fDataClient.println ("Waiting for login prompt...");

    String msg_line = readNetInputLine ();
    if (msg_line == null) return false;
    fDataClient.println (msg_line);
    if (!msg_line.startsWith ("Username:")) return false;

    fDataClient.println ("Send username " + fUserName);
    try {
      writeNetOutputLine (fUserName);
    }
    catch IOException e) {
      return false;
    }
    catch (Exception e) {
      fDataClient.println ("Error occurred in sending username!");
      return false;
    }

    fDataClient.println ("Waiting for response...");

    msg_line=readNetInputLine ();
    if (msg_line == null) return false;
    fDataClient.println (msg_line);

    return true;
  } // login

  /** Do all of the steps needed to stop the connection. **/
  public void finish (){

     // Kill the thread and stop the server
     fKeepRunning = false;
     closeServer ();
  }

  /** Close the socket to the server. **/
  void closeServer () {
    if (fServer == null) return;
    try {
      fServer.close ();
      fServer = null;
    }
    catch (IOException e)
    {}
  }
  ...

 

The data taking loop in the run() method, see below, begins by sending a request to the server for the data. The server returns a value indicating how many data values are in the data set that it will send.

The worker then reads the data set values from the input stream connected to the server. The data set is then passed to the parent DataClient via its setData() method. This version of the program only accepts integer data but this could be easily modified to obtain floating point data from the server. The loop pauses for a given period and then repeats the process.

  ... In the DataClientWorker class ...

  public void run () {
    ... the connection set up code ...

    int num_channels = -1;

    // This loops until either the connection is broken or the
    //stop button or stop key is hit
    while  (fKeepRunning) {

       // Ask the server to send data.
      try {
          writeNetOutputLine ("  send data");
      }
      catch  (IOException e){
        break;
      }

       // First number sent from server is an integer that gives
       // the number of data values to be sent.
      try {
          num_channels = readNetInputInt ();
      }
      catch  (IOException e) {
        break;
      }

      if (num_channels != fNumChannels) {
          fNumChannels = num_channels;
          fDataClient.println ("  Number data channels = "
                               + fNumChannels);
      }

      if (fNumChannels < 1){
         fDataClient.println ("  no data");
         break;
      }

      // Create an array to hold the data if not available
      if (fData == null || fNumChannels != fData.length)
          fData = new int[fNumChannels];


      for (int i=0; i < fNumChannels; i++) {
        try {
          fData[i] = readNetInputInt ();
          // Pass the data to the parent program
          fDataClient.setData (fData);
        }
        catch  (IOException e) {
          fDataClient.println ("IO Exception while reading data");
          break;
        }
      }

      // Ask for data every TimeUpdate
      try {
        Thread.sleep (fDataClient.fTimeUpdate);
      }
      catch (InterruptedException e)
      {}
    }

    if  (fServer != null) closeServer ();
    fDataClient.println ("disconnected");
    fDataClient.setDisconnected ();

  } // run
  ...

 

This client program illustrates the basics of a data monitor. Such a monitor provides for examining the data as it arrives during an experiment in a way that helps to spot anomalies or malfunctions. The data will be saved to disk files for full analysis later but the monitor helps to prevent the taking of flawed data.

 

Last update: Dec. 11, 2004

  
  Part I Part II Part III
Java Core 1  2  3  4  5  6  7  8  9  10  11  12 13 14 15 16 17
18 19 20
21
22 23 24
Supplements

1  2  3  4  5  6  7  8  9  10  11  12

Tech 1  2  3  4  5  6  7  8  9  10  11  12
Physics 1  2  3  4  5  6  7  8  9  10  11  12

Java is a trademark of Sun Microsystems, Inc.