| 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 |