/** @file tools/sdk/java/Client.java @author Luke Tokheim, luke@motionnode.com @version 1.0 (C) Copyright GLI Interactive LLC 2007. All rights reserved. The coded instructions, statements, computer programs, and/or related material (collectively the "Data") in these files contain unpublished information proprietary to GLI Interactive LLC, which is protected by US federal copyright law and by international treaties. The Data may not be disclosed or distributed to third parties, in whole or in part, without the prior written consent of GLI Interactive LLC. The Data is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose. */ package MotionNode.SDK; import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** Implements a simple socket based data client and the MotionNode service binary message protocol. Provide a simple interface to develop external applications that can read real-time MotionNode data. This class only handles the socket connection and single message transfer. The {@link Format} class implements interfaces to the service specific data formats.
Example usage:
// Connect to port 32078 on localhost.
Client client = new Client(null, 32078);
while (true) {
if (client.waitForData()) {
while (true) {
// Block on the open connection until a message comes in.
{@link ByteBuffer} data = client.{@link Client#readData};
if (null == data) {
break;
}
// Process the message. Use the {@link Format} methods, for example.
}
}
}
*/
public class Client {
/**
A message is defined by an integer packet prefix field. Set this
maximum mostly as a safeguard. We do not want to allocate a huge
amount of memory at once.
*/
private static final int MaximumMessageLength = 65535;
/**
Default value, in seconds, for the socket receive time out
in the Client#waitForData method. Zero denotes blocking receive.
*/
private static final int TimeOutWaitForData = 5;
/**
Default value, in seconds, for the socket receive time out
in the Client#readData method.
*/
private static final int TimeOutReadData = 1;
/**
The low-level socket connection.
*/
private Socket socket = null;
/**
Stream input from the socket.
*/
private DataInputStream in = null;
/**
Store the remote service description string.
*/
private String description = null;
/**
Store the current value of the socket receive time out.
*/
private int time_out_second = 0;
/**
Open a MotionNode service client connection.
@param host the host name, or null for the loopback address.
@param port the port number
@see java.net.Socket#Socket(String, int)
*/
public Client(String host, int port)
throws UnknownHostException,
IOException,
SecurityException
{
socket = new Socket(host, port);
in = new DataInputStream(socket.getInputStream());
// Read the first message from the service. It is a
// string description of the remote service. Blocking
// call.
byte[] buffer = receive();
if (null != buffer) {
description = new String(buffer);
}
}
/**
Close this connection.
@see java.net.Socket#close()
@see java.io.DataInputStream#close()
*/
public void close()
throws IOException
{
in.close();
socket.close();
}
/**
Wait until there is incoming data on the open socket connection.
This method will time out and return false if no
data arrives after Client#TimeOutWaitForData seconds.
@return true when incoming data arrives, or
false on a time out.
*/
public boolean waitForData(int time_out)
throws IOException
{
boolean result = false;
if (-1 == time_out) {
time_out = TimeOutWaitForData;
}
if (time_out != time_out_second) {
socket.setSoTimeout(TimeOutWaitForData*1000);
time_out_second = TimeOutWaitForData;
}
byte[] buffer = receive();
if (null != buffer) {
result = true;
}
return result;
}
/**
Implements default parameter value.
*/
public boolean waitForData()
throws IOException
{
return waitForData(-1);
}
/**
Read a single sample of data from the open connection. This
method will time out and return null if no data
comes in after Client.TimeOutReadData seconds.
@return a single sample of data, or null if the
incoming data is invalid
*/
public ByteBuffer readData(int time_out)
throws IOException
{
ByteBuffer result = null;
if (-1 == time_out) {
time_out = TimeOutReadData;
}
if (time_out != time_out_second) {
socket.setSoTimeout(TimeOutReadData*1000);
time_out_second = TimeOutReadData;
}
byte[] buffer = receive();
if (null != buffer) {
// All service data is little-endian. Use the nifty
// ByteBuffer to implement cross platform byte swapping.
ByteBuffer nio_buffer = ByteBuffer.wrap(buffer);
nio_buffer.order(ByteOrder.LITTLE_ENDIAN);
result = nio_buffer;
}
return result;
}
/**
Implements default parameter value.
*/
public ByteBuffer readData()
throws IOException
{
return readData(-1);
}
/**
Receive a single binary message over the open socket
connection. Reads the message length header and then
the number of bytes in the incoming message. For
internal use only.
@return a single binary message, or null if the
incoming data is invalid
*/
private byte[] receive()
throws IOException
{
byte[] result = null;
try {
int length = in.readInt();
if ((length > 0) && (length < MaximumMessageLength)) {
byte[] buffer = new byte[length];
if (length == in.read(buffer)) {
result = buffer;
}
}
} catch (SocketTimeoutException e) {
// Catch any socket read time outs due to the current
// SO_TIMEOUT setting.
}
return result;
}
/**
Example usage and test function for the Client class.
*/
public static void main(String [] args)
throws UnknownHostException,
IOException,
SecurityException
{
// Connect to port 32079 on localhost.
Client client = new Client(null, 32079);
System.out.println("Connected to localhost:32079");
while (true) {
if (client.waitForData()) {
while (true) {
ByteBuffer data = client.readData();
if (null == data) {
break;
}
System.out.println("Incoming message, remaining bytes = " + data.remaining());
}
}
}
}
} // class Client