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
|