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.
A simple UML diagram for a standalone FTP application could be
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.
The server from the lecture on protocols nearly fits this design pattern. The changes are
FileTransferServerHOPP
String[] dir()
boolean chdir(String dir)
String getdir()
SocketHandler
in a new thread
for each new socket
SocketHandler
has a FileTransferServerHOPP
to
which it delegates all file calls.
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 "";
}
}
}
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
String[] dir()
boolean chdir(String dir)
String getdir()
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
RemoteException