package dk.itu.eyedroid.io.protocols; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; import dk.itu.eyedroid.Constants; import dk.itu.eyedroid.io.NetClientConfig; import dk.itu.spcl.jlpf.common.Bundle; import dk.itu.spcl.jlpf.io.IOProtocolWriter; /** * Generic TCP/IP output protocol implementation. Used to send processed bundle * results to a connected client. Sends X and Y gaze position coordinates as * result. */ public class OutputTCPNet implements IOProtocolWriter { private final int mPort; // Server port private ServerSocket serverSocket; // Server socket for new incomming connections private Socket mSocket; // Client socket private OutputStream mOutput; // Spcket output stream private AtomicBoolean isConnectionSet; // Client connection status private boolean isWaitingForConnection; // Server waiting for client status private boolean isSocketServerClosed; // Server socket was intentionally closed. private static final int SERVER_SOCKET_ACCEPT_TIME_OUT = 10; /** * Deafult constructor * * @param port * Server listener port */ public OutputTCPNet(int port) { mPort = port; isConnectionSet = new AtomicBoolean(); isConnectionSet.set(false); } /** * Initialize server and wait for incoming connections. When a client * connects, close server socket. If non-intentional error ocurrs, throw it * to a higher level. */ @Override public void init() throws IOException { try { if (!isWaitingForConnection && !isConnectionSet.get()) { isWaitingForConnection = true; serverSocket = new ServerSocket(mPort); serverSocket.setSoTimeout(SERVER_SOCKET_ACCEPT_TIME_OUT); } } catch (IOException e) { isWaitingForConnection = false; isConnectionSet.set(false); if (!isSocketServerClosed) throw e; else isSocketServerClosed = false; } } private void acceptClient() { try { mSocket = serverSocket.accept(); mOutput = mSocket.getOutputStream(); serverSocket.close(); isWaitingForConnection = false; isConnectionSet.set(true); } catch (IOException e) { // do nothing will be handled from cleanup } } /** * Send result to client. In case of error throw an exception in order to * restart the protocol. */ @Override public void write(Bundle bundle) throws IOException { if (bundle != null && isConnectionSet.get()) { int x = (Integer) bundle.get(Constants.PUPIL_COORDINATES_X); int y = (Integer) bundle.get(Constants.PUPIL_COORDINATES_Y); if (x != -1 && y != -1) { byte[] output = generateOutput(x, y); synchronized (mSocket) { mOutput.write(output); mOutput.flush(); } } } else { init(); acceptClient(); } bundle = null; } /** * Close server and connected client socket in case they are open. */ @Override public void cleanup() { isConnectionSet.set(false); try { if (serverSocket != null) { if (!serverSocket.isClosed()) { isSocketServerClosed = true; serverSocket.close(); } } if (mSocket != null) { synchronized (mSocket) { if (!mSocket.isClosed()) { mSocket.close(); } } } } catch (IOException e) { e.printStackTrace(); } } /** * Create byte[] output containing the gaze position coordinates. * @param x X coordinate * @param y Y coordinate * @return Byte array. */ private byte[] generateOutput(int x, int y) { ByteBuffer b = ByteBuffer.allocate(NetClientConfig.MSG_SIZE); if (NetClientConfig.USE_HMGT) b.putInt(0, NetClientConfig.TO_CLIENT_GAZE_HMGT); else b.putInt(0, NetClientConfig.TO_CLIENT_GAZE_RGT); b.putInt(4, x); b.putInt(8, y); return b.array(); } }