Half Object plus Protocol

Introduction

The client and server code given so far is very procedural. In fact, the best way to express the server is as a finite-state machine, and this works well for the client too.

A finite-state machine isn't an O/O representation. How does this fit in with O/O design representations such as UML? It doesn't. So if you want to use UML, you have to hide the finite-state machine.

Standalone FTP application

A simple UML diagram for a standalone FTP application could be

Half Object plus Protocol (HOPP)

To change this to a client/server system, the FTP object needs to be split in two: one half in the client address space, the other half in the server address space. The two half-objects will communicate using a private protocol that is unknown to the rest of the application.

The private protocol will here be "message passing using sockets". Other implementations may use CORBA, RMI, JXTA, etc. This allows the implementation of the HOPP objects to be changed without affecting the rest of the application.

HOPP FTP Server

The server from the lecture on protocols nearly fits this design pattern. The changes are

The server classes are

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

public class FileTransferTextServer {
    
    public static void main(String argv[]) {
	ServerSocket s = null;
	try {
	    s = new ServerSocket(FileTransferTextConstants.PORT);
	} catch(IOException e) {
	    System.out.println(e);
	    System.exit(1);
	}

	while (true) {
	    Socket incoming = null;
	    try {
		incoming = s.accept();
	    } catch(IOException e) {
		System.out.println(e);
		continue;
	    }

	    new SocketHandler(incoming).start();
	}
    }
}

class SocketHandler extends Thread {

    Socket incoming;
    FileTransferServerHOPP fileServer = new FileTransferServerHOPP();

    BufferedReader reader;
    PrintStream out;

    SocketHandler(Socket incoming) {
	this.incoming = incoming;
    }

    public void run() {
	try {
	    reader = new BufferedReader(new InputStreamReader(
					incoming.getInputStream()));
	    out = new PrintStream(incoming.getOutputStream());
	    
	    while (true) {
		String line = reader.readLine();
		if (line == null) { 
		    break;
		}
		System.out.println("Received request: " + line);

		if (line.startsWith(FileTransferTextConstants.CD)) {
		    changeDirRequest(losePrefix(line, 
						FileTransferTextConstants.CD));
		} else if (line.startsWith(FileTransferTextConstants.DIR)) {
		    directoryRequest();
		} else if (line.startsWith(FileTransferTextConstants.GET)) {
		    // code omitted
		} else if (line.startsWith(FileTransferTextConstants.QUIT)) {
		    break;
		} else {
		    out.print(FileTransferTextConstants.ERROR + 
			      FileTransferTextConstants.CR_LF);
		}
		
	    }
	    incoming.close();
	} catch(IOException e) {
	    e.printStackTrace();
	}
    }


    /**
     * Given that the string starts with the prefix,
     * get rid of the prefix and any following whitespace
     */
    public String losePrefix(String str, String prefix) {
	int index = prefix.length();
	String ret = str.substring(index).trim();
	return ret;

    }

    public void changeDirRequest(String dir) {
	if (fileServer.chdir(dir)) {
	    out.print(FileTransferTextConstants.SUCCEEDED + " " +
		      fileServer.getdir() +
		      FileTransferTextConstants.CR_LF);
	} else {
	    out.print(FileTransferTextConstants.ERROR +
		      FileTransferTextConstants.CR_LF);
	}
    }

    public void directoryRequest() {
	String[] fileNames = fileServer.dir();
	if (fileNames == null) {
	    out.print(FileTransferTextConstants.ERROR +
		      FileTransferTextConstants.CR_LF);
	}
	for (int n = 0; n < fileNames.length; n++) {
	    out.print(fileNames[n] +
		      FileTransferTextConstants.CR_LF);
	}
	out.print(FileTransferTextConstants.CR_LF);
    }
}

class FileTransferServerHOPP {
    File clientDir = new File(".");
    
    public boolean chdir(String dir) {
	File newDir = new File(clientDir, dir);
	if (newDir.isDirectory()) {
	    clientDir = newDir;
	    return true;
	} else {
	    return false;
	}
    }

    public String[] dir() {
	String[] fileNames = clientDir.list();
	return fileNames;
    }

    public String getdir() {
	try {
	    return clientDir.getCanonicalPath();
	} catch(IOException e) {
	    return "";
	}
    }
}

HOPP FTP Client

The client in the lecture on protocols does not fit this design pattern and does need to be changed. It mixed up UI and protocol calls, and these should be separated.

A revised client, made up of FileTransferClientHOPP and FileTransferClientUI follow. Points to look out for

The client HOPP is



/**
 * FileTransferTextClient.java
 *
 *
 * Created: Fri Jul 20 12:54:51 2001
 *
 * @author <a href="mailto: "Jan Newmarch</a>
 * @version
 */
import java.io.*;
import java.net.*;

public class FileTransferClientHOPP {

    protected Socket sock;
    protected BufferedReader reader;
    protected PrintStream writer;

    public FileTransferClientHOPP(String server)
	throws UnknownHostException, IOException {

	InetAddress address = InetAddress.getByName(server);

	sock = null;
	InputStream in = null;
	OutputStream out = null;

	sock = new Socket(address, FileTransferTextConstants.PORT);
	in = sock.getInputStream();
	out = sock.getOutputStream();

	reader = new BufferedReader(new InputStreamReader(in));
	writer = new PrintStream(out);
    }

    public void quit() {
	try {
	    writer.print(FileTransferTextConstants.QUIT +
			 FileTransferTextConstants.CR_LF);
	    reader.close();
	    writer.close();
	    sock.close();
	} catch(Exception e) {
	    // ignore
	}
    }

    public String[] dir() {
	// stub
	return null;
    }

    public boolean chdir(String dir) {
	writer.print(FileTransferTextConstants.CD + " " + dir + 
		     FileTransferTextConstants.CR_LF);

	String response = null;
	try {
	    response = reader.readLine();
	} catch(IOException e) {
	    return false;
	}
	if (response.startsWith(FileTransferTextConstants.SUCCEEDED)) {
	    return true;
	} else {
	    return false;
	}
    }

    public String get(String filename) throws IOException {
	// stub
	return "";
    }
} // FileTransferTextClient








The Client UI is



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

public class FileTransferClientUI {
    
    private static String DIR = "dir";
    private static String QUIT = "quit";
    private static String CD = "cd";
    private static String GET ="get";

    protected BufferedReader console;
    protected FileTransferClientHOPP clientHOPP;

    public static void main(String[] args){

	if (args.length != 1) {
	    System.err.println("Usage: Client address");
	    System.exit(1);
	}
	FileTransferClientUI ui = new FileTransferClientUI(args[0]);
	ui.loop();
    }

    public FileTransferClientUI(String server) {

	clientHOPP = null;
	try {
	    clientHOPP = new FileTransferClientHOPP(server);
	} catch(Exception e) {
	    e.printStackTrace();
	    System.exit(1);
	}

	console = new BufferedReader(new InputStreamReader(System.in));
    }

    public void loop() {
	while (true) {
	    String line = null;
	    try {
		System.out.print("Enter request: ");
		line = console.readLine();
		System.out.println("Request was " + line);
	    } catch(IOException e) {
		clientHOPP.quit();
		e.printStackTrace();
		System.exit(1);
	    }

	    if (line.equals(DIR)) {
		dir();
	    } else if (line.startsWith(CD)) {
		chdir(losePrefix(line, CD));
	    } else if (line.startsWith(GET)) {
		get(losePrefix(line, GET));
	    } else if (line.equals("QUIT")) {
		clientHOPP.quit();
		System.exit(0);
	    } else {
		System.out.println("Unrecognised command");
	    }
	}
    }

    /**
     * Given that the string starts with the prefix,
     * get rid of the prefix and any whitespace
     */
    public String losePrefix(String str, String prefix) {
	int index = prefix.length();
	String ret = str.substring(index).trim();
	return ret;
    }

    protected void dir() {
	String[] dirList = clientHOPP.dir();
	if (dirList == null) {
	    System.out.println("No dir list available");
	} else {
	    for (int n = 0; n < dirList.length; n++) {
		System.out.println(dirList[n]);
	    }
	}
    }

    public void chdir(String dir) {
	if (clientHOPP.chdir(dir)) {
	    System.out.println("Chdir okay");
	} else {
	    System.out.println("Chdir failed");
	}
    }

    public void get(String filename) {
	// omitted
    }
} // FileTransferClientUI










IP Address

Changing protocols


Jan Newmarch (http://jan.newmarch.name)
jan@newmarch.name
Last modified: Fri Aug 19 11:10:41 EST 2005
Copyright ©Jan Newmarch
Copyright © Jan Newmarch, Monash University, 2007
Creative Commons License This work is licensed under a Creative Commons License
The moral right of Jan Newmarch to be identified as the author of this page has been asserted.