|
The DataServer
program runs only as an application. Below we show an image of
a typical session of the program in which it has connected to
a DataClient.
See the preceding section for a detailed discussion
of the code in the DataServer
class and in its DataWorker
helper class.
The DataServer
Application
DataServer
- creates a user interface that consists of a frame
with a text area to display messages, a text field to
set the servers port, and a start button. The drop down
File menu holds an Exit item to stop the program. It
uses a ServerSocket to make socket connections with
the clients. When
a connection is made ithands off the socket to an instance
of DataWorker to provide data to the client
+
Helper class:
DataWorker
- an instance of DataWorker tends to a client connected
to a socket obtained from the ServerSocket object. It
begins with a simple login procedure for the client.
It then enters a loop to respond to requests for data
from the client.
This
version of DataWorker just creates dummy data to send
to the client. The client periodically sends a request
for more data and the DataWorker sends a set of 6 integer
values, each generated according to Gaussian random
number generator of a different std. dev. and offset.
|
package
DataMonitor;
import java.net.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
/**
* A Prototype Remote Data Monitoring
System
*
* DataServer provides simulated
data to multiple instances of
* DataClient. A DataClientconnects
via a socket and
* executes a simple login procedue.
The client then sends a request
* for data from the Server. DataServer
responds by first sending an
* integer for the number of data
values in the set and then sends
* that number of simulated integer
data values. These might be a set
* of sensor values.
*
* DataServer runs standalone. It
provides a simple GUI for starting
* the server, displaying the status
of the communications with the
* clients, and stopping the server.
*
* This server is intended to allow
clients to monitor an experiment.
* It could run at the same time
that a separate data acquisition
* program is running.T he DAQ program,
for example, could be
* programmed to periodically update
a file with data of interest.
* This server could then read this
file and send the data to the
* client.
*
* An instance of the thread class
DataWorker is assigned to each
* client socket. It monitors the
socket for requests from the
* client and then obtains the
data (here it just creates some
* dummy data with random number
generators) and sends it to the
* client. The DataWorker includes
methods to read and write strings
* and numerical data with streams
to the client.
*
* Multiple clients can connect
simultaneously. The default max users
* value is set to 10.
*
**/
public class DataServer extends JFrame
implements ActionListener,
Runnable
{
// GUI setup attributes
JMenuItem fMenuClose
= null;
JTextArea fTextArea =
null;
JTextField fTextField = null;
JButton fStartButton
= null;
// Networking setup
// Use a Vector to keep track of the DataWorker
list.
Vector fWorkerList = null;
// Connect to clients
ServerSocket fServerSocket = null;
int fDataServerPort = 2222;// Default port
number
// Client counting and limit
static int fClientCounter__ = 0;
static int fMaxClients__ =
10;
// Number of data values per set or "event"
static int fNumDataVals__ = 6;
// Flag for the server socket loop.
boolean fKeepServing = true;
/** Open frame for the user interface.
**/
public static void main (String [] args) {
// Can pass frame title in command
line arguments
String title="DataServer";
if (args.length != 0) title =
args[0];
DataServer f = new DataServer
(title);
f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
f.setVisible (true);
} // main
/**
* Pass a title to the
frame via the constructor
* argument. Build the
GUI.
**/
DataServer (String title) {
super (title);
// Create the vector to list the
DataWorker objects
fWorkerList = new Vector ();
// Now build the GUI.
Container content_pane = getContentPane
();
// Create a user interface.
content_pane.setLayout ( new BorderLayout
() );
fTextArea = new JTextArea ("");
JScrollPane area_scroll_pane =
new JScrollPane (fTextArea);
content_pane.add ( area_scroll_pane,
"Center");
// Create a panel with a textfield
and two buttons
fTextField = new JTextField ("2222");
fStartButton = new JButton ("Start");
fStartButton.addActionListener
(this);
JPanel panel = new JPanel (new
GridLayout (1,2));
JPanel button_panel = new JPanel
();
panel.add (fTextField);
button_panel.add (fStartButton);
panel.add (button_panel);
content_pane.add ( panel, "South");
// Use the helper method makeMenuItem
// for making the menu items and
registering
// their listener.
JMenu m = new JMenu ("File");
// Use File menu to hold Quit
command.
m.add (fMenuClose = makeMenuItem
("Quit"));
JMenuBar mb = new JMenuBar ();
mb.add (m);
setJMenuBar (mb);
setSize (400,400);
} // ctor
/** Process events from the frame menu and
the chooser. **/
public void actionPerformed (ActionEvent e)
{
boolean status = false;
String command = e.getActionCommand
();
if (command.equals ("Start") )
{
try {
fDataServerPort
= Integer.parseInt (fTextField.getText ());
}catch
(NumberFormatException nfe){
println
("Bad port number");
return;
}
fStartButton.setEnabled
(false);
Thread
thread = new Thread (this);
thread.start
();
} else if (command.equals ("Quit")
){
fKeepServing = false;
dispose ();
}
} // actionPerformed
/** Create a ServerSocket and loop waiting
for clients. **/
public void run () {
// The server_socket is used to
make connections to
// DataClients at this port number
try {
fServerSocket
= new ServerSocket (fDataServerPort);
}
catch (IOException e) {
println
("Error in server socket");
return;
}
println ("Waiting for users...");
// Loop here to grab clients
while (fKeepServing) {
try {
//
accept () blocks until a connection is made
Socket
socket = fServerSocket.accept ();
//
Do the setup this socket and then loop
//
back around to wait for the next DataClient.
DataWorker
worker = new DataWorker (this, socket);
worker.start
();
}
catch (IOException
ioe) {
println
("IOException: <" + ioe + ">");
break;
}
catch (Exception
e) {
println
("Exception: <" + e + ">");
break;
}
}
} // run
/**
* When a DataWorker makes the
connection, it checks to see if
* there is room on the server
for it.
* We synchronize the method to
avoid any problems with multiple
* clients interfering with each
other.
**/
public synchronized boolean clientPermit ()
{
if (fWorkerList.size () < fMaxClients__)
return
true;
else
return
false;
}
/**
* A DataWorker will set up the
connection with the client. If it
* decides that the conditions
are OK, then it will invoke this
* method so that the parent server
will add the worker to its
* list.
* We synchronize the method to
avoid any problems with multiple
* clients interfering with each
other.
**/
public synchronized void clientConnected (DataWorker
worker) {
fWorkerList.add (worker);
fClientCounter__++;
}
/**
* When a client disconnects, the
DataWorker object will
* call back to this method to
remove itself from the list
* of workers.
* We synchronize the method to
avoid any problems with multiple
* clients interfering with each
other.
**/
public synchronized void clientDisconnected
(String user,
DataWorker worker) {
println ("Client: "+user+" disconneced");
fWorkerList.remove (worker);
fClientCounter__--;
}
/**
* This "helper method" makes a
menu item and then
* registers this object as a listener
to it.
**/
private JMenuItem makeMenuItem (String name){
JMenuItem m = new JMenuItem (
name );
m.addActionListener ( this );
return m;
}
/** Utility method to send messages to the
text area. **/
public void println (String str){
fTextArea.append (str +"\n");
repaint ();
}
} // class DataServer
|
//package
DataMonitor;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* DataServer uses an instance of this class
to service
* a DataClient connection. It waits for a data
request
* and then obtains and sends the
data.
*
* In actual use, the data could, for example
be obtained from a
* diskfile updated by a DAQ system. Here, though,
we simulate data
* taking by generating an array of random numberfDataServer.
**/
public class DataWorker extends Thread
{
// The parent server.
DataServer fDataServer;
// Name of the client
String fUser;
// Connection to the client.
Socket fSocket;
// I/O streams to the client
InputStream fNetInputStream;
OutputStream fNetOutputStream;
// Wrapped I/O streams
BufferedReader fNetInputReader;
DataOutputStream fDataOutputStream;
PrintWriter fPrintWriter;
boolean fKeepRunning = true ;
// Data production:
// Smear each data channel with the following
std.dev
// for the Gaussian.
double [] fStdDev = {10.0, 20.0, 30.0, 40.0,
50.0, 60.0};
Random fRan;
/**
* Pass reference to the
owner DataServer and the
* index number for this
DataSender instance.
**/
public DataWorker (DataServer s, Socket socket)
{
fDataServer = s;
fSocket = socket;
// Random number generator need
for dummy data creation.
fRan = new Random ();
}
/** Send data to the client. **/
public void run () {
// If setup fails, end thread processing.
if (!serviceSetup ())
return;
fDataServer.println (
"Client connection and login
OK - Begin service...");
// Lower tpriority to give main
parent and
// other threads some processor
time.
setPriority (MIN_PRIORITY);
String client_last_msg = "";
// Begin the loop for communicating
with the client.
while (fKeepRunning){
// Read a request from
the DataClient
String client_msg =
readNetInputLine ();
if (client_msg == null
) break;
// Only print message
if it changes. Avoids printing same
// message for each
data set.
if ( !client_msg.equals
(client_last_msg))
fDataServer.println
("Message from " + fUser + ": "
+ client_msg);
client_last_msg = client_msg;
// Could interpret the
request and do something accordingly
// but here we will
just send a set of data values.
// Send the number of
data values.
try {
writeNetOutputInt
(DataServer.fNumDataVals__);
}
catch (IOException
e) {
break;
}
// Creat dummy data
values and send them to the DataClient.
for (int i=0; i< DataServer.fNumDataVals__;
i++){
// Select
that range of Gaussian widths for the data
// values
for each channel of the data set. Add an offset
// to get
most negative values above zero.
int i_std_dev
= i%6;
double dat
= 3.0*fStdDev[i_std_dev] +
fStdDev[i_std_dev] * fRan.nextGaussian ();
if (dat
< 0.0) dat = 0.0;
// Pass
only integer values;
int idat
= (int) dat;
try {
writeNetOutputInt
(idat);
}
catch (IOException
e) {
break;
}
}
}
// Send message back to the text
area in the frame.
fDataServer.println (fUser + " has
disconnected.");
// Do any other tasks for ending
the worker.
signOff ();
} // run
/**
* Set up the connection to the client.
This requires obtaining the
* IO streams, carrying out the login
prototcol, and then starting
* a DataWorker thread to tend to
the client.
*
* The bookkeeping code is a bit
messy because we check both reads
* and writes for errors in case
the connection breaks down.
*
* The reads catch their own IOExceptions
and return a null, while
* string writes use a PrintWriter
that doesn't throw IOException. So
* we use the checkError () method
and throw it ourselvefDataServer.
**/
public boolean serviceSetup () {
fDataServer.println ("Client setup...");
// First get the in/out streams
from the socket to the client
try{
fNetInputStream =
fSocket.getInputStream ();
fNetOutputStream = fSocket.getOutputStream
();
}
catch (IOException e){
fDataServer.println
("Unable to get input/output streams");
return false;
}
// Create a PrintWriter class for
sending text to the client.
// The writeNetOutputLine method
will use this class.
try{
fPrintWriter
= new PrintWriter (
new OutputStreamWriter (fNetOutputStream, "8859_1"), true
);
}
catch (Exception e) {
fDataServer.println
("Fails to open PrintWriter to client!");
return false;
}
// Check if the server has room
for this client.
// If not, then send a message to
this client to tell it
// the bad news.
if ( !fDataServer.clientPermit
() ) {
try{
String msg=
"Sorry, We've reached maximum of clients";
writeNetOutputLine
(msg);
fDataServer.println
(msg);
return false;
}
catch (IOException
e){
fDataServer.println
("Connection fails during login");
return false;
}
}
// Get a DataInputStream wrapper
so we can use its
// readLine () methods.
fNetInputReader =
new
BufferedReader (new InputStreamReader (fNetInputStream));
// Do a simple login protocol. Send
a request for the users name.
// Note a password check could be
added here.
try{
writeNetOutputLine (
"Username: ");
}
catch (IOException e){
fDataServer.println
("Connection fails during login");
return false;
}
// Read the user name.
fUser = readNetInputLine ();
if (fUser == null ) {
fDataServer.println
("Connection fails during login");
return false;
}
// Send a message that the login
is OK.
try{
writeNetOutputLine ("Login
successful");
fDataServer.println
("Login successful for " + fUser);
} catch (IOException
e){
fDataServer.println
("Connection fails during login for "
+ fUser);
return false;
}
fDataServer.println (fUser + " connected!
");
fDataServer.println (fSocket.toString
());
// The login is successful so now
create a DataWorker to
// service this client. Pass it
an ID number
fDataServer.clientConnected (this);
// Get a data output stream for
writing numerical data to the client
fDataOutputStream = new DataOutputStream
(fNetOutputStream);
return true;
} // serviceSetup
/** Whenever this client disconnects tell the
parent. **/
public void signOff () {
try{
fSocket.close
();
}catch (Exception e){
fDataServer.println
("Socket close exception for " + fUser);
}
fDataServer.clientDisconnected (fUser,this);
} // signOff
/** Utility method to read a whole text line.**/
String readNetInputLine () {
try {
return fNetInputReader.readLine
();
}
catch (IOException e){
return null;
}
} // readNetInputLine
/**
* Output is wrapped with a PrintWriter,
which doesn't throw
* IOException. So we invoke the
checkError() method and then
* throw an exception if it detects
an error.
**/
void writeNetOutputLine (String string)
throws
IOException {
fPrintWriter.println (string);
if ( fPrintWriter.checkError
()) throw (new IOException ());
fPrintWriter.flush ();
if ( fPrintWriter.checkError
()) throw (new IOException ());
} // writeNetOutputLine
/** Utility to write integer values to the output
stream. **/
void writeNetOutputInt (int i)
throws IOException {
fDataOutputStream.writeInt (i);
fDataOutputStream.flush ();
} // writeNetOutputInt
/** Utility to write float values to the output
stream.**/
void writeNetOutputFloat (float f)
throws IOException {
fDataOutputStream.writeFloat (f);
fDataOutputStream.flush ();
} // writeNetOutputFloat
} // class DataWorker |
Last update: Dec. 11, 2004
|
|
|