/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Server - basic network server 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.*;
/**
* A server sends and receives data to and from its associated clients (other programs connected to it).
* When a server is started, it begins listening for connections on the port specified by the <b>port</b> parameter.
* Computers have many ports for transferring data and some are commonly used so be sure to not select one of these.
* For example, web servers usually use port 80 and POP mail uses port 110.
*
* @webref
* @brief The server class is used to create server objects which send and receives data to and from its associated clients (other programs connected to it).
* @instanceName server any variable of type Server
* @usage Application
*/
public class Server implements Runnable {
PApplet parent;
Method serverEventMethod;
Thread thread;
ServerSocket server;
int port;
/** Number of clients currently connected. */
public int clientCount;
/** Array of client objects, useful length is determined by clientCount. */
public Client[] clients;
/**
*
* @param parent typically use "this"
* @param port port used to transfer data
*/
public Server(PApplet parent, int port) {
this.parent = parent;
this.port = port;
try {
server = new ServerSocket(this.port);
//clients = new Vector();
clients = new Client[10];
thread = new Thread(this);
thread.start();
parent.registerDispose(this);
// reflection to check whether host applet has a call for
// public void serverEvent(Server s, Client c);
// which is called when a new guy connects
try {
serverEventMethod =
parent.getClass().getMethod("serverEvent",
new Class[] { Server.class,
Client.class });
} catch (Exception e) {
// no such method, or an error.. which is fine, just ignore
}
} catch (IOException e) {
e.printStackTrace();
thread = null;
//errorMessage("<init>", e);
}
}
/**
* Disconnect a particular client.
* @webref
* @param client the client to disconnect
*/
public void disconnect(Client client) {
//client.stop();
client.dispose();
int index = clientIndex(client);
if (index != -1) {
removeIndex(index);
}
}
protected void removeIndex(int index) {
clientCount--;
// shift down the remaining clients
for (int i = index; i < clientCount; i++) {
clients[i] = clients[i+1];
}
// mark last empty var for garbage collection
clients[clientCount] = null;
}
protected void addClient(Client client) {
if (clientCount == clients.length) {
clients = (Client[]) PApplet.expand(clients);
}
clients[clientCount++] = client;
}
protected int clientIndex(Client client) {
for (int i = 0; i < clientCount; i++) {
if (clients[i] == client) {
return i;
}
}
return -1;
}
// the last index used for available. can't just cycle through
// the clients in order from 0 each time, because if client 0 won't
// shut up, then the rest of the clients will never be heard from.
int lastAvailable = -1;
/**
* Returns the next client in line with a new message
* @webref
*/
public Client available() {
synchronized (clients) {
int index = lastAvailable + 1;
if (index >= clientCount) index = 0;
for (int i = 0; i < clientCount; i++) {
int which = (index + i) % clientCount;
Client client = clients[which];
if (client.available() > 0) {
lastAvailable = which;
return client;
}
}
}
return null;
}
/**
* Disconnects all clients and stops the server
* =advanced
* <p/>
* Use this to shut down the server if you finish using it while your applet
* is still running. Otherwise, it will be automatically be shut down by the
* host PApplet using dispose(), which is identical.
* @webref
*/
public void stop() {
dispose();
}
/**
* Disconnect all clients and stop the server: internal use only.
*/
public void dispose() {
try {
thread = null;
if (clients != null) {
for (int i = 0; i < clientCount; i++) {
disconnect(clients[i]);
}
clientCount = 0;
clients = null;
}
if (server != null) {
server.close();
server = null;
}
} catch (IOException e) {
e.printStackTrace();
//errorMessage("stop", e);
}
}
public void run() {
while (Thread.currentThread() == thread) {
try {
Socket socket = server.accept();
Client client = new Client(parent, socket);
synchronized (clients) {
addClient(client);
if (serverEventMethod != null) {
try {
serverEventMethod.invoke(parent, new Object[] { this, client });
} catch (Exception e) {
System.err.println("Disabling serverEvent() for port " + port);
e.printStackTrace();
serverEventMethod = null;
}
}
}
} catch (IOException e) {
//errorMessage("run", e);
e.printStackTrace();
thread = null;
}
try {
Thread.sleep(8);
} catch (InterruptedException ex) { }
}
}
/**
* Write a value to all the connected clients.
* See Client.write() for operational details.
*
* @webref
* @brief Writes data to all connected clients
* @param data data to write
*/
public void write(int data) { // will also cover char
int index = 0;
while (index < clientCount) {
clients[index].write(data);
if (clients[index].active()) {
index++;
} else {
removeIndex(index);
}
}
}
/**
* Write a byte array to all the connected clients.
* See Client.write() for operational details.
*/
public void write(byte data[]) {
int index = 0;
while (index < clientCount) {
clients[index].write(data);
if (clients[index].active()) {
index++;
} else {
removeIndex(index);
}
}
}
/**
* Write a String to all the connected clients.
* See Client.write() for operational details.
*/
public void write(String data) {
int index = 0;
while (index < clientCount) {
clients[index].write(data);
if (clients[index].active()) {
index++;
} else {
removeIndex(index);
}
}
}
/**
* General error reporting, all corraled here just in case
* I think of something slightly more intelligent to do.
*/
// public void errorMessage(String where, Exception e) {
// parent.die("Error inside Server." + where + "()", e);
// //System.err.println("Error inside Server." + where + "()");
// //e.printStackTrace(System.err);
// }
}