blob: e6cd5ddcdfd8081c32ead41f6e3faada93f64310 [file] [log] [blame]
danielp64c4e052013-02-23 07:21:41 +00001/**
2 *
3 */
4package org.frc971;
5
6/**
7 * @author daniel
8 * Accepts clients for data server
9 */
10
11import java.io.IOException;
12
13import java.nio.ByteBuffer;
14import java.nio.channels.ServerSocketChannel;
15import java.nio.channels.SocketChannel;
16
17import java.util.ArrayList;
18import java.util.List;
19import java.util.Map;
20import java.util.logging.Logger;
21
22public class AccepterThread extends Thread {
23
24 private final static Logger LOG = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
25
26 private ServerSocketChannel sock;
27
28 private List<SocketChannel> connected = new ArrayList<SocketChannel>();
29
30 /* Holds overflow data when socket's send buffer gets full, so that
31 * thread can continue running.
32 */
33 private Map<SocketChannel, ByteBuffer> toSend;
34 /* Keeps track of how many times a write operation on a socket
35 * has failed because it's buffer was full.
36 */
37 private Map<SocketChannel, Integer> failedAttempts; //doesn't like primitive types
38
39 /** Helper function to completely erase a peer from
40 * all three lists and maps that might contain it.
41 */
42 private void erasePeer(SocketChannel peer) {
43 connected.remove(peer);
44 toSend.remove(peer);
45 failedAttempts.remove(peer);
46 }
47
48 /** Constructor
49 *
50 * @param sock is the ServerSocketChannel that you want to monitor
51 */
52 public AccepterThread(ServerSocketChannel sock) {
53 super("Accepter Thread");
54 setPriority(3); //lowish priority so Image Processor overrides it
55 this.sock = sock;
56 start();
57 }
58
59 /** Runs in separate thread. Continually accepts new connections. */
60 public void run() {
61 SocketChannel clientSock;
62 while (true) {
63 try {
64 clientSock = sock.accept();
65 //our writes must not block
66 clientSock.configureBlocking(false);
67 connected.add(clientSock);
68 }
69 catch (IOException e) {
70 LOG.warning("Socket accept failed.");
71 }
72 }
73 }
74
75 /** Sends a message to all currently connected clients.
76 *
77 * @param message is the message that you want to send.
78 */
79 public void sendtoAll(ByteBuffer message) {
80 /* Copy our connected list, so we don't have
81 * to hold our lock forever if the writes block.
82 */
83 List<SocketChannel> connectedTemp = new ArrayList<SocketChannel>();
84 for (SocketChannel channel : connected) {
85 connectedTemp.add(channel);
86 }
87
88 int result;
89 for (SocketChannel conn : connectedTemp) {
90 try {
91
92 /** If this socket has data from the
93 * last send operation still waiting to be
94 * sent, send this instead of our original
95 * message. Since we generally want only
96 * current data, our original message will
97 * not be missed. However, it is imperative
98 * that we finish our pending transmission,
99 * because an incomplete transmission could
100 * leave a client thread somewhere blocking
101 * indefinitely.
102 */
103 if (toSend.containsKey(conn)) {
104 message = toSend.get(conn);
105 }
106
107 result = conn.write(message);
108
109 /*if our send buffer is full, store our message away
110 * so we can try again later without halting the thread.
111 */
112 if (message.remaining() > 0) {
113 toSend.put(conn, message);
114 //check and update our count of failed send attempts
115 if (failedAttempts.containsKey(conn)) {
116 int failures = failedAttempts.get(conn);
117 ++failures;
118 if (failures >= 100) {
119 //Socket has become dysfunctional
120 LOG.info("Write would have blocked 100 times. Assuming peer disconect.");
121 erasePeer(conn);
122 }
123 failedAttempts.put(conn, failures);
124 }
125 else {
126 failedAttempts.put(conn, 1);
127 }
128 }
129
130 if (result == -1) {
131 //The write failed. This is probably because the client disconnected.
132 LOG.info("Write returned -1. Client has probably disconnected.");
133 erasePeer(conn);
134 }
135 }
136 catch (IOException e) {
137 //The write failed. This is probably because the client disconnected.
138 LOG.info("Write threw IOException. Client has probably disconnected.");
139 erasePeer(conn);
140 }
141 }
142 }
143
144 /** Overloaded sendtoAll method for byte arrays. */
145 public void sendtoAll(byte[] message) {
146 sendtoAll(ByteBuffer.wrap(message));
147 }
148
149 /** Overloaded sendtoAll method for Strings. */
150 public void sendtoAll(String message) {
151 sendtoAll(message.getBytes());
152 }
153}