
// usage:  java ClntString remotehost command
// where command is w or ps

// like Clnt.java, but using strings

// sends the command to remotehost, which runs it there and sends us
// back the output of the command

// note:  at the bottom level, there are still the standard system
// calls, e.g. socket(), connect(), bind(), etc.; the difference is that
// in Java they are made by the underlying JVM, rather than by the
// programmer him/herself

import java.lang.*;
import java.io.*;
import java.net.*;

public class ClntString 

{
   private static final int port=3000;

   private static Socket socket=null;       
   private static DataInputStream input=null;  
   private static DataOutputStream output=null; 

   public static void main (String args[])
	
   {  try {

         try {
            // class Socket is for TCP; use class DatagramSocket
            // for UDP; note that the connect action is included
            socket = new Socket(args[0],port);  
         }
         // Socket(.,.) throws UnknownHostException, IOException
         catch (UnknownHostException e)  {
            System.out.println(e+":  check remote host address");
	    System.exit(1);
         }
         catch(IOException e)  {
            System.out.println(e+":  some other socket/connect problem");
            System.exit(1);
         }

         input = new DataInputStream(socket.getInputStream());
         output = new DataOutputStream(socket.getOutputStream());

         // see extended comments on readUTF() at the end of this
         //    source file
         // send the command to the remotehost
         output.writeUTF(args[1]); 

         // read the response from the remote host, and print it out
	 // on the screen
         String cmdresponse = new String("");
         // Svr has been written to generate an "end" record as an EOF 
         while (cmdresponse != null && !cmdresponse.equals("end"))  {
            cmdresponse = input.readUTF();  
            if (!cmdresponse.equals("end"))
               System.out.println(cmdresponse); 
         }
      }
      // catch exceptions which may arise during input.readUTF()
      catch(IOException e)  {
         System.out.println(e+":  broken connection with server");
         System.exit(1);
      }
   }
}

/* 

We will be working with text data, so it is convenient to
work in strings; UTF is a subspecification of Unicode, a
universal character encoding, enabling easy communication between
platforms.  

TThe general Unicode system allocates two bytes per character, but UTF
reduces one-byte ASCII if the original string coding was ASCII.  

If we were working with nontext files, say, we would use
FileOutputStream and write(), and FileInputStream and read().

Recall that TCP has the "It's all one long stream of bytes" property.
E.g. in an example in our notes, write() was called twice, once sending
20 bytes and the second time second 30, but with the effect that it was
just a stream of 50 bytes, with no visible "boundary" between the sets
of 20 and 30 bytes.

In the example here (which by the way was originally written by my TA
Narendra Singhal, then modified by me), the use of writeUTF() and
readUTF() gives you a way to retain "boundaries" even in TCP.  Here's
how:

The code in Svr.java reads data from "process" one line at a time.  Each
line is then sent to Clnt by using writeUTF().  If you check the Sun
"man page" for that function, you will see that what it sends is (a) a
count of the number of characters in the given string, and then (b) the
string itself (in this case the line read from "process").  So, among
all those bytes traveling from Svr to Clnt, there is not only the
original characters but also character counts embedded here and there.

Now, the TCP layer at the machine where Clnt is running will read all
those bytes (strings plus character counts).  Again, TCP will see no
"boundaries" here.  HOWEVER, readUTF() will pick through those bytes,
knowing that a character count comes first, then a string, then a
character count, then a string, etc.  Each time we call readUTF(),
therefore, we are saying, "Go to that TCP stream received at Clnt,
and get the next count and then the string following it."

In this way, with readUTF() and writeUTF() running on top of TCP, we can
retain "boundaries," in this case, boundaries between lines of output
from "process."

Among other things, this means that even though we are using TCP, our
code is almost "UDP-like."  For example, it means that we don't have to
put a call to some read function within a loop, like we did in our
"overview" unit in the printed lecture notes.  The reason is that the
internal code within the implementation of readUTF() is doing that for
us!

*/


