001 /**
002 * Jetrix TetriNET Server
003 * Copyright (C) 2001-2003 Emmanuel Bourg
004 *
005 * This program is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU General Public License
007 * as published by the Free Software Foundation; either version 2
008 * of the License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program; if not, write to the Free Software
017 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
018 */
019
020 package net.jetrix.clients;
021
022 import java.io.*;
023 import java.net.*;
024 import java.util.*;
025 import java.util.concurrent.*;
026 import java.util.logging.*;
027
028 import net.jetrix.*;
029 import net.jetrix.config.*;
030 import net.jetrix.messages.*;
031 import net.jetrix.messages.channel.*;
032
033 /**
034 * Layer handling communication with a tetrinet or tetrifast client. Incomming
035 * messages are turned into a server understandable format and forwarded to the
036 * apropriate destination for processing (the player's channel or the main
037 * server thread)
038 *
039 * @author Emmanuel Bourg
040 * @version $Revision: 857 $, $Date: 2010-05-04 19:55:19 +0200 (mar., 04 mai 2010) $
041 */
042 public class TetrinetClient implements Client
043 {
044 private String agent;
045 private String version;
046 private Protocol protocol;
047 private Channel channel;
048 private User user;
049 protected Date connectionTime;
050 protected long lastMessageTime;
051 protected boolean disconnected;
052 private boolean running;
053
054 protected InputStream in;
055 protected OutputStream out;
056 protected Socket socket;
057 protected String encoding = "Cp1252";
058 protected ServerConfig serverConfig;
059 protected BlockingQueue<Message> queue;
060
061 protected Logger log = Logger.getLogger("net.jetrix");
062
063 public TetrinetClient()
064 {
065 if (isAsynchronous())
066 {
067 queue = new LinkedBlockingQueue<Message>();
068 }
069 }
070
071 public TetrinetClient(User user)
072 {
073 this();
074 this.user = user;
075 }
076
077 public TetrinetClient(User user, Socket socket)
078 {
079 this();
080 setSocket(socket);
081 this.user = user;
082 }
083
084 /**
085 * Return the protocol used by this client.
086 */
087 public Protocol getProtocol()
088 {
089 return this.protocol;
090 }
091
092 /**
093 * Set the protocol.
094 */
095 public void setProtocol(Protocol protocol)
096 {
097 this.protocol = protocol;
098 }
099
100 /**
101 * Main loop listening and parsing messages sent by the client.
102 */
103 public void run()
104 {
105 if (log.isLoggable(Level.FINE))
106 {
107 log.fine("Client started " + this);
108 }
109
110 running = true;
111
112 if (isAsynchronous())
113 {
114 // start the message sender thread
115 new MessageSender("sender-" + user.getName()).start();
116 }
117
118 connectionTime = new Date();
119
120 // get the server configuration if possible
121 Server server = Server.getInstance();
122 if (server != null)
123 {
124 serverConfig = server.getConfig();
125 }
126
127 try
128 {
129 while (!disconnected && serverConfig.isRunning())
130 {
131 // fetch the next message
132 Message message = receive();
133
134 // discard unknown messages
135 if (message == null) continue;
136
137 // todo check the slot on channel messages
138
139 if (message.getDestination() != null)
140 {
141 message.getDestination().send(message);
142 }
143 else if (message instanceof ClientInfoMessage)
144 {
145 ClientInfoMessage info = (ClientInfoMessage) message;
146 setAgent(info.getName());
147 setVersion(info.getVersion());
148 if ("gtetrinet".equalsIgnoreCase(info.getName()))
149 {
150 encoding = "UTF-8";
151 }
152 }
153 else if (channel != null)
154 {
155 // send the message to the channel assigned to this client
156 channel.send(message);
157 }
158 else
159 {
160 // no channel assigned, the message is sent to the server
161 server.send(message);
162 }
163 }
164 }
165 catch (IOException e)
166 {
167 if (Server.getInstance().getConfig().isRunning())
168 {
169 log.log(Level.SEVERE, "Connexion error with the tetrinet client '" + getUser().getName() + "'", e);
170 }
171 }
172 finally
173 {
174 if (!socket.isClosed())
175 {
176 disconnect();
177 try { in.close(); } catch (IOException e) { e.printStackTrace(); }
178 try { out.close(); } catch (IOException e) { e.printStackTrace(); }
179 try { socket.close(); } catch (IOException e) { e.printStackTrace(); }
180 }
181
182 // unregister the client from the server
183 ClientRepository.getInstance().removeClient(this);
184
185 // remove the player from the channel
186 if (channel != null)
187 {
188 // todo: remove the client from all channels if it supports multiple channels (IRC clients)
189 DisconnectedMessage disconnect = new DisconnectedMessage();
190 disconnect.setClient(this);
191 channel.send(disconnect);
192 }
193 }
194 }
195
196 public void send(Message message)
197 {
198 // check the ignore list
199 if (message instanceof TextMessage)
200 {
201 Destination source = message.getSource();
202 if (source instanceof Client)
203 {
204 Client client = (Client) source;
205 if (getUser().ignores(client.getUser().getName()))
206 {
207 if (log.isLoggable(Level.FINEST))
208 {
209 log.finest("Message dropped, player " + client.getUser().getName() + " ignored");
210 }
211
212 return;
213 }
214 }
215 }
216
217 if (isAsynchronous() && running)
218 {
219 // add to the queue
220 queue.add(message);
221 }
222 else
223 {
224 // write directly
225 write(message);
226 }
227 }
228
229 /**
230 * Write the message on the output stream.
231 */
232 private void write(Message message)
233 {
234 String rawMessage = message.getRawMessage(getProtocol(), user.getLocale());
235
236 if (rawMessage != null)
237 {
238 try
239 {
240 synchronized (out)
241 {
242 out.write(rawMessage.getBytes(getEncoding()));
243 out.write(getProtocol().getEOL());
244 out.flush();
245 }
246
247 if (log.isLoggable(Level.FINEST))
248 {
249 log.finest("> " + rawMessage);
250 }
251 }
252 catch (SocketException e)
253 {
254 if (log.isLoggable(Level.FINE))
255 {
256 log.fine(e.getMessage());
257 }
258 }
259 catch (Exception e)
260 {
261 log.log(Level.INFO, getUser().toString(), e);
262 }
263 }
264 else
265 {
266 log.warning("Message not sent, raw message missing " + message);
267 }
268 }
269
270 public Message receive() throws IOException
271 {
272 // read raw message from socket
273 String line = protocol.readLine(in, getEncoding());
274 lastMessageTime = System.currentTimeMillis();
275 if (log.isLoggable(Level.FINER))
276 {
277 log.finer("RECV: " + line);
278 }
279
280 // build server message
281 Message message = getProtocol().getMessage(line);
282 //message.setRawMessage(getProtocol(), line);
283 if (message != null)
284 {
285 message.setSource(this);
286 }
287
288 return message;
289 }
290
291 public void setSocket(Socket socket)
292 {
293 this.socket = socket;
294 try
295 {
296 in = new BufferedInputStream(socket.getInputStream());
297 out = new BufferedOutputStream(socket.getOutputStream());
298 }
299 catch (IOException e)
300 {
301 e.printStackTrace();
302 }
303 }
304
305 public Socket getSocket()
306 {
307 return socket;
308 }
309
310 public InetAddress getInetAddress()
311 {
312 return socket.getInetAddress();
313 }
314
315 public void setChannel(Channel channel)
316 {
317 this.channel = channel;
318 }
319
320 public Channel getChannel()
321 {
322 return channel;
323 }
324
325 public boolean supportsMultipleChannels()
326 {
327 return false;
328 }
329
330 public boolean supportsAutoJoin()
331 {
332 return true;
333 }
334
335 public void setUser(User user)
336 {
337 this.user = user;
338 }
339
340 public User getUser()
341 {
342 return user;
343 }
344
345 public void setVersion(String version)
346 {
347 this.version = version;
348 }
349
350 public String getVersion()
351 {
352 return version;
353 }
354
355 public String getAgent()
356 {
357 return agent;
358 }
359
360 public void setAgent(String agent)
361 {
362 this.agent = agent;
363 }
364
365 public Date getConnectionTime()
366 {
367 return connectionTime;
368 }
369
370 public long getIdleTime()
371 {
372 return System.currentTimeMillis() - lastMessageTime;
373 }
374
375 public String getEncoding()
376 {
377 return encoding;
378 }
379
380 public void disconnect()
381 {
382 disconnected = true;
383
384 // notify the message sender thread
385 if (queue != null)
386 {
387 queue.add(new ShutdownMessage());
388 }
389
390 try
391 {
392 socket.shutdownOutput();
393 }
394 catch (Exception e)
395 {
396 e.printStackTrace();
397 }
398 }
399
400 /**
401 * Tells if the messages are sent asynchroneously to the client.
402 *
403 * @since 0.2
404 */
405 protected boolean isAsynchronous()
406 {
407 return true;
408 }
409
410 public String toString()
411 {
412 return "[Client " + getInetAddress() + " type=" + agent + " " + version + "]";
413 }
414
415 /**
416 * A thread sending the message to the client asynchroneously.
417 *
418 * @since 0.2
419 */
420 private class MessageSender extends Thread
421 {
422 private int index;
423 private long timestamp[];
424 private int capacity = 10;
425 private int delay = 100;
426
427 public MessageSender(String name)
428 {
429 super(name);
430
431 timestamp = new long[capacity];
432 }
433
434 public void run()
435 {
436 while (!disconnected)
437 {
438 try
439 {
440 // take the message
441 Message message = queue.take();
442
443 if (disconnected)
444 {
445 return;
446 }
447
448 // delay
449 if (isRateExceeded(System.currentTimeMillis()))
450 {
451 sleep(10);
452 }
453
454 // write the message
455 write(message);
456 }
457 catch (InterruptedException e)
458 {
459 log.log(Level.WARNING, e.getMessage(), e);
460 }
461 }
462 }
463
464 private boolean isRateExceeded(long t)
465 {
466 long t1 = timestamp[index];
467 timestamp[index] = t;
468 index = (index + 1) % capacity;
469
470 return (t - t1) < delay;
471 }
472 }
473
474 }