Appendix A: The System Implementation
The Server
The main server code is simple. It opens up a socket connection to the specified port. Once this port has been opened, it does two things: (1) it starts up a thread to read/write the port, and (2) continues to monitor for new client connections.
public DeviceServer(int port)
{
if (port == 0) port = DEFAULT_PORT;
this.port = port;
try
{
listen_socket = new ServerSocket(port);
}
catch (IOException e)
{
fail(e, "Exception creating server socket");
}
message("Listening on port " + port);
this.start();
};
// This is the thread that is spawned by DeviceServer
public void run()
{
try
{
message("Waiting for clients.");
// Loop forever.
while (true)
{
Socket client_socket = listen_socket.accept();
clientConnection = new ClientConnection(client_socket, this);
message("New client.");
}
}
catch (IOException e)
{
fail(e, "Exception while listening for connections");
}
};
Up until this point clients can connect to the server, but nothing else. Within the run thread a new ClientConnection object is created. This takes care of writing to the client (port) and reading from the client. ClientConnection is also multi-threaded to allow for concurrent reads and writes. The constructor method creates a data stream for reading and another for writing. The constructor then spawns a thread to read from the client. Since multiple threads are used, the read is implemented using blocking. Writing to the port is done via a separate method. The main part of the code follows:
protected Socket client;
protected DataInputStream in;
protected PrintStream out;
private DeviceServer parent;
// Initialize the streams and start the thread
public grConnection(Socket client_socket, DeviceServer s)
{
client = client_socket;
parent = s;
try
{
in = new DataInputStream(client.getInputStream());
out = new PrintStream(client.getOutputStream());
}
catch (IOException e)
{
message("Exception while getting socket streams: " + e);
return;
}
this.start();
};
// Thread used to read data from the client.
public void run()
{
try
{
// Loop forever.
while (true)
{
// Read in the next line, blocks on read.
String line;
line = in.readLine();
// Send the received data back up to the main server
parent.sendDataToParent(line);
}
}
catch (IOException e)
{
message("Exception while getting socket streams: " + e);
message("It is possible that client has disconnected.");
return;
}
};
// Send data to the client.
public void sendData(String s)
{
out.println(s);
};
As can be seen from the code, communications is done using strings. This simplifies the implementation. In the future, this can be implemented using binary communications to reduce the amount of information that is required for transmission. However, as the savings would be approximately a factor of two, it was not deemed to be important at this point in the development.
The implementation of the server was greatly simplified by the use of Java. Java has a set of class libraries that handle most of the implementation details.
The Client
The implementation of the client has two important parts: the connection to the server and the connection to the application. The connection to the server is also simple. The client connects to the server and proceeds to read messages over the socket. The client is written as a multi-threaded class to allow for a blocking read.
public void run()
{
String line;
while (true)
{
line = in.readLine();
//
// Parse the incomming data, send commands to application
//
}
};
As can be seen the client interface for receiving data is very simple. The only thing left to do at this point is to parse the incoming data and call the appropriate methods. This is also a trivial operation. This interface provides a very simple means for adding new devices as no new client code needs to be created. Instead the implementation is in the server and the client remains constant.