blob: e6cd5ddcdfd8081c32ead41f6e3faada93f64310 [file] [log] [blame]
/**
*
*/
package org.frc971;
/**
* @author daniel
* Accepts clients for data server
*/
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
public class AccepterThread extends Thread {
private final static Logger LOG = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
private ServerSocketChannel sock;
private List<SocketChannel> connected = new ArrayList<SocketChannel>();
/* Holds overflow data when socket's send buffer gets full, so that
* thread can continue running.
*/
private Map<SocketChannel, ByteBuffer> toSend;
/* Keeps track of how many times a write operation on a socket
* has failed because it's buffer was full.
*/
private Map<SocketChannel, Integer> failedAttempts; //doesn't like primitive types
/** Helper function to completely erase a peer from
* all three lists and maps that might contain it.
*/
private void erasePeer(SocketChannel peer) {
connected.remove(peer);
toSend.remove(peer);
failedAttempts.remove(peer);
}
/** Constructor
*
* @param sock is the ServerSocketChannel that you want to monitor
*/
public AccepterThread(ServerSocketChannel sock) {
super("Accepter Thread");
setPriority(3); //lowish priority so Image Processor overrides it
this.sock = sock;
start();
}
/** Runs in separate thread. Continually accepts new connections. */
public void run() {
SocketChannel clientSock;
while (true) {
try {
clientSock = sock.accept();
//our writes must not block
clientSock.configureBlocking(false);
connected.add(clientSock);
}
catch (IOException e) {
LOG.warning("Socket accept failed.");
}
}
}
/** Sends a message to all currently connected clients.
*
* @param message is the message that you want to send.
*/
public void sendtoAll(ByteBuffer message) {
/* Copy our connected list, so we don't have
* to hold our lock forever if the writes block.
*/
List<SocketChannel> connectedTemp = new ArrayList<SocketChannel>();
for (SocketChannel channel : connected) {
connectedTemp.add(channel);
}
int result;
for (SocketChannel conn : connectedTemp) {
try {
/** If this socket has data from the
* last send operation still waiting to be
* sent, send this instead of our original
* message. Since we generally want only
* current data, our original message will
* not be missed. However, it is imperative
* that we finish our pending transmission,
* because an incomplete transmission could
* leave a client thread somewhere blocking
* indefinitely.
*/
if (toSend.containsKey(conn)) {
message = toSend.get(conn);
}
result = conn.write(message);
/*if our send buffer is full, store our message away
* so we can try again later without halting the thread.
*/
if (message.remaining() > 0) {
toSend.put(conn, message);
//check and update our count of failed send attempts
if (failedAttempts.containsKey(conn)) {
int failures = failedAttempts.get(conn);
++failures;
if (failures >= 100) {
//Socket has become dysfunctional
LOG.info("Write would have blocked 100 times. Assuming peer disconect.");
erasePeer(conn);
}
failedAttempts.put(conn, failures);
}
else {
failedAttempts.put(conn, 1);
}
}
if (result == -1) {
//The write failed. This is probably because the client disconnected.
LOG.info("Write returned -1. Client has probably disconnected.");
erasePeer(conn);
}
}
catch (IOException e) {
//The write failed. This is probably because the client disconnected.
LOG.info("Write threw IOException. Client has probably disconnected.");
erasePeer(conn);
}
}
}
/** Overloaded sendtoAll method for byte arrays. */
public void sendtoAll(byte[] message) {
sendtoAll(ByteBuffer.wrap(message));
}
/** Overloaded sendtoAll method for Strings. */
public void sendtoAll(String message) {
sendtoAll(message.getBytes());
}
}