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