The "Start" 
                  button puts the server program into a server state by starting 
                  a thread with a loop in the run() 
                  method. Clients requesting connections are detected via 
                  a ServerSocket 
                  and a new instance of DataWorker 
                  is created to handle each new client. When a connection is made, 
                  the program opens I/O streams to and from the client. 
                 
                  
                     
                      | ... 
                          The run() method in class DataServer ...   /** 
                          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
 | 
                  
                 
                 
                DataServer 
                  uses a Vector 
                  to keep a list of all the clients, and a limit on the number 
                  of clients is set with the fMaxClients 
                  variable. A DataWorker 
                  calls back to the clientPermit() 
                  method, shown below, to determine if it can join the list. If 
                  so, then it invokes clientConnected(), 
                  which adds the client to the list. 
                When the client disconnects, the DataWorker 
                  invokes the server's clientDisconnected() 
                  method to remove itself from the worker list. These methods 
                  are synchronized to avoid any interference if two or more threads 
                  are connecting/disconnecting at the same time.
                 
                  
                     
                      |  
                            ... continue in class DataServer 
                          ...  /*** 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__--;
 }
 ...
 | 
                  
                 
                 
                The DataServer 
                  hands off the client socket to a DataWorker, 
                  which then begins its job of communicating with the client and 
                  providing the requested services.
                DataWorker 
                   Class 
                  
                The job of the DataWorker 
                  is to tend to the needs of the client. The worker waits for 
                  a request for data from the client. When the request arrives, 
                  the worker tells the client how many data values will be included 
                  in the data set. It then generates a set integer values, each 
                  according to a Gaussian distribution (different data channels 
                  use different Gaussian widths and offsets) and sends the data 
                  set to the client. 
                As shown in the following code snippet, the first 
                  act by the run() 
                  method is to invoke the serviceSetup() 
                  method. This method sets up the streams for I/O with the client. 
                  The PrintWriter 
                  and BufferedReader 
                  wrappers are used to send and receive text to and from the client. 
                  A DataOutputStream 
                  wrapper is used to send numerical values. The read/write methods 
                  for these streams are put into some utility methods discussed 
                  later.
                 If the maximum number of clients has been reached, 
                  the worker sends a warning message to its client and breaks 
                  off the connection. That worker thread itself then signs off 
                  from the server and dies.
                 If there is space for the new client, the serviceSetup() 
                  method performs a simple log-in procedure with the client that 
                  consists of sending the string "Username:" 
                  to the client and waiting for a string in return. 
                 
                  
                     
                      |  
                            ... The run() and serviceSetup() methods 
                          in class DataWorker ...   /**  Send 
                          data to the client. **/public void run  () {
 
 // If setup fails, end thread 
                          processing.
 if  (!serviceSetup 
                          ()) return;
 
 ...
 
 } // 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
 ...
 | 
                  
                 
                 
                If the log-in procedure is successful, then a 
                  call back to the server informs it that the connection for this 
                  client is successful and it should be added to the client list.
                The stream methods to send or receive strings 
                  and data involve several lines of code since they can throw 
                  exceptions. Also, to insure that data does not get stuck in 
                  a buffer, a flush() 
                  method is invoked. So rather than repeating all of this code 
                  for each read or writer operation, the DataWorker 
                  uses the following utility methods.
                 
                  
                     
                      |  
                            ... Continue in DataWorker ...   /** 
                          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
 ...
 | 
                  
                 
                 
                Once the connection with the client has been established, 
                  the run() 
                  method (shown below) enters a loop where it waits for a request 
                  from the client, responds to the request, and then waits for 
                  the next request. For the simple situation here, any string 
                  received from the client is assumed to be a request for data. 
                
                The worker first tells the client how many data 
                  values are included the data set. It then generates an array 
                  of integer values, where the generation for each array element 
                  comes from a random Gaussian distribution with different widths 
                  and offsets. This data array is then sent to the client just 
                  as if it were a set of data readings from, say, an experimental 
                  apparatus. 
                 
                  
                     
                      |  
                            ... the run() method in DataWorker 
                          ...   /**  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
 | 
                  
                 
                 
                The DataWorker 
                  could be modified to obtain real data in various ways. For example, 
                  a sensor might generate a file that the worker could read and 
                  send to the client. In Chapter 
                  23 we will discuss how Java programs can communicate with 
                  devices via the serial ports. The worker might obtain data from 
                  a device that way and then send it to the client.