/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Client - basic network client implementation Part of the Processing project - http://processing.org Copyright (c) 2004-2007 Ben Fry and Casey Reas The previous version of this code was developed by Hernando Barragan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package processing.net; import processing.core.*; import java.io.*; import java.lang.reflect.*; import java.net.*; /** * ( begin auto-generated from Client.xml ) * * A client connects to a server and sends data back and forth. If anything * goes wrong with the connection, for example the host is not there or is * listening on a different port, an exception is thrown. * * @webref net * @brief The client class is used to create client Objects which connect to a server to exchange data. * @instanceName client any variable of type Client * @usage Application * @see_external LIB_net/clientEvent */ public class Client implements Runnable { protected static final int MAX_BUFFER_SIZE = 1 << 27; // 128 MB PApplet parent; Method clientEventMethod; Method disconnectEventMethod; volatile Thread thread; Socket socket; int port; String host; public InputStream input; public OutputStream output; final Object bufferLock = new Object[0]; byte buffer[] = new byte[32768]; int bufferIndex; int bufferLast; boolean disposeRegistered = false; /** * @param parent typically use "this" * @param host address of the server * @param port port to read/write from on the server */ public Client(PApplet parent, String host, int port) { this.parent = parent; this.host = host; this.port = port; try { socket = new Socket(this.host, this.port); input = socket.getInputStream(); output = socket.getOutputStream(); thread = new Thread(this); thread.start(); parent.registerMethod("dispose", this); disposeRegistered = true; // reflection to check whether host sketch has a call for // public void clientEvent(processing.net.Client) // which would be called each time an event comes in try { clientEventMethod = parent.getClass().getMethod("clientEvent", Client.class); } catch (Exception e) { // no such method, or an error.. which is fine, just ignore } // do the same for disconnectEvent(Client c); try { disconnectEventMethod = parent.getClass().getMethod("disconnectEvent", Client.class); } catch (Exception e) { // no such method, or an error.. which is fine, just ignore } } catch (IOException e) { e.printStackTrace(); dispose(); } } /** * @param socket any object of type Socket * @throws IOException */ public Client(PApplet parent, Socket socket) throws IOException { this.parent = parent; this.socket = socket; input = socket.getInputStream(); output = socket.getOutputStream(); thread = new Thread(this); thread.start(); // reflection to check whether host sketch has a call for // public void clientEvent(processing.net.Client) // which would be called each time an event comes in try { clientEventMethod = parent.getClass().getMethod("clientEvent", Client.class); } catch (Exception e) { // no such method, or an error.. which is fine, just ignore } // do the same for disconnectEvent(Client c); try { disconnectEventMethod = parent.getClass().getMethod("disconnectEvent", Client.class); } catch (Exception e) { // no such method, or an error.. which is fine, just ignore } } /** * ( begin auto-generated from Client_stop.xml ) * * Disconnects from the server. Use to shut the connection when you're * finished with the Client. * * @webref client:client * @brief Disconnects from the server * @usage application */ public void stop() { if (disconnectEventMethod != null && thread != null){ try { disconnectEventMethod.invoke(parent, this); } catch (Exception e) { Throwable cause = e; // unwrap the exception if it came from the user code if (e instanceof InvocationTargetException && e.getCause() != null) { cause = e.getCause(); } cause.printStackTrace(); disconnectEventMethod = null; } } if (disposeRegistered) { parent.unregisterMethod("dispose", this); disposeRegistered = false; } dispose(); } /** * Disconnect from the server: internal use only. *
* This should only be called by the internal functions in PApplet, * use stop() instead from within your own applets. */ public void dispose() { thread = null; try { if (input != null) { input.close(); input = null; } } catch (Exception e) { e.printStackTrace(); } try { if (output != null) { output.close(); output = null; } } catch (Exception e) { e.printStackTrace(); } try { if (socket != null) { socket.close(); socket = null; } } catch (Exception e) { e.printStackTrace(); } } @Override public void run() { byte[] readBuffer; { // make the read buffer same size as socket receive buffer so that // we don't waste cycles calling listeners when there is more data waiting int readBufferSize = 1 << 16; // 64 KB (default socket receive buffer size) try { readBufferSize = socket.getReceiveBufferSize(); } catch (SocketException ignore) { } readBuffer = new byte[readBufferSize]; } while (Thread.currentThread() == thread) { try { while (input != null) { int readCount; // try to read a byte using a blocking read. // An exception will occur when the sketch is exits. try { readCount = input.read(readBuffer, 0, readBuffer.length); } catch (SocketException e) { System.err.println("Client SocketException: " + e.getMessage()); // the socket had a problem reading so don't try to read from it again. stop(); return; } // read returns -1 if end-of-stream occurs (for example if the host disappears) if (readCount == -1) { System.err.println("Client got end-of-stream."); stop(); return; } synchronized (bufferLock) { int freeBack = buffer.length - bufferLast; if (readCount > freeBack) { // not enough space at the back int bufferLength = bufferLast - bufferIndex; byte[] targetBuffer = buffer; if (bufferLength + readCount > buffer.length) { // can't fit even after compacting, resize the buffer // find the next power of two which can fit everything in int newSize = Integer.highestOneBit(bufferLength + readCount - 1) << 1; if (newSize > MAX_BUFFER_SIZE) { // buffer is full because client is not reading (fast enough) System.err.println("Client: can't receive more data, buffer is full. " + "Make sure you read the data from the client."); stop(); return; } targetBuffer = new byte[newSize]; } // compact the buffer (either in-place or into the new bigger buffer) System.arraycopy(buffer, bufferIndex, targetBuffer, 0, bufferLength); bufferLast -= bufferIndex; bufferIndex = 0; buffer = targetBuffer; } // copy all newly read bytes into the buffer System.arraycopy(readBuffer, 0, buffer, bufferLast, readCount); bufferLast += readCount; } // now post an event if (clientEventMethod != null) { try { clientEventMethod.invoke(parent, this); } catch (Exception e) { System.err.println("error, disabling clientEvent() for " + host); Throwable cause = e; // unwrap the exception if it came from the user code if (e instanceof InvocationTargetException && e.getCause() != null) { cause = e.getCause(); } cause.printStackTrace(); clientEventMethod = null; } } } } catch (IOException e) { //errorMessage("run", e); e.printStackTrace(); } } } /** * ( begin auto-generated from Client_active.xml ) * * Returns true if this client is still active and hasn't run * into any trouble. * * @webref client:client * @brief Returns true if this client is still active * @usage application */ public boolean active() { return (thread != null); } /** * ( begin auto-generated from Client_ip.xml ) * * Returns the IP address of the computer to which the Client is attached. * * @webref client:client * @usage application * @brief Returns the IP address of the machine as a String */ public String ip() { if (socket != null){ return socket.getInetAddress().getHostAddress(); } return null; } /** * ( begin auto-generated from Client_available.xml ) * * Returns the number of bytes available. When any client has bytes * available from the server, it returns the number of bytes. * * @webref client:client * @usage application * @brief Returns the number of bytes in the buffer waiting to be read */ public int available() { synchronized (bufferLock) { return (bufferLast - bufferIndex); } } /** * ( begin auto-generated from Client_clear.xml ) * * Empty the buffer, removes all the data stored there. * * @webref client:client * @usage application * @brief Clears the buffer */ public void clear() { synchronized (bufferLock) { bufferLast = 0; bufferIndex = 0; } } /** * ( begin auto-generated from Client_read.xml ) * * Returns a number between 0 and 255 for the next byte that's waiting in * the buffer. Returns -1 if there is no byte, although this should be * avoided by first cheacking available() to see if any data is available. * * @webref client:client * @usage application * @brief Returns a value from the buffer */ public int read() { synchronized (bufferLock) { if (bufferIndex == bufferLast) return -1; int outgoing = buffer[bufferIndex++] & 0xff; if (bufferIndex == bufferLast) { // rewind bufferIndex = 0; bufferLast = 0; } return outgoing; } } /** * ( begin auto-generated from Client_readChar.xml ) * * Returns the next byte in the buffer as a char. Returns -1 or 0xffff if * nothing is there. * * @webref client:client * @usage application * @brief Returns the next byte in the buffer as a char */ public char readChar() { synchronized (bufferLock) { if (bufferIndex == bufferLast) return (char) (-1); return (char) read(); } } /** * ( begin auto-generated from Client_readBytes.xml ) * * Reads a group of bytes from the buffer. The version with no parameters * returns a byte array of all data in the buffer. This is not efficient, * but is easy to use. The version with the byteBuffer parameter is * more memory and time efficient. It grabs the data in the buffer and puts * it into the byte array passed in and returns an int value for the number * of bytes read. If more bytes are available than can fit into the * byteBuffer, only those that fit are read. * *