// 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! */