1 /***
2 * Jetrix TetriNET Server
3 * Copyright (C) 2001-2003 Emmanuel Bourg
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 package net.jetrix.clients;
21
22 import java.io.*;
23 import java.net.*;
24 import java.util.*;
25 import java.util.concurrent.*;
26 import java.util.logging.*;
27
28 import net.jetrix.*;
29 import net.jetrix.config.*;
30 import net.jetrix.messages.*;
31 import net.jetrix.messages.channel.*;
32
33 /***
34 * Layer handling communication with a tetrinet or tetrifast client. Incomming
35 * messages are turned into a server understandable format and forwarded to the
36 * apropriate destination for processing (the player's channel or the main
37 * server thread)
38 *
39 * @author Emmanuel Bourg
40 * @version $Revision: 857 $, $Date: 2010-05-04 19:55:19 +0200 (mar., 04 mai 2010) $
41 */
42 public class TetrinetClient implements Client
43 {
44 private String agent;
45 private String version;
46 private Protocol protocol;
47 private Channel channel;
48 private User user;
49 protected Date connectionTime;
50 protected long lastMessageTime;
51 protected boolean disconnected;
52 private boolean running;
53
54 protected InputStream in;
55 protected OutputStream out;
56 protected Socket socket;
57 protected String encoding = "Cp1252";
58 protected ServerConfig serverConfig;
59 protected BlockingQueue<Message> queue;
60
61 protected Logger log = Logger.getLogger("net.jetrix");
62
63 public TetrinetClient()
64 {
65 if (isAsynchronous())
66 {
67 queue = new LinkedBlockingQueue<Message>();
68 }
69 }
70
71 public TetrinetClient(User user)
72 {
73 this();
74 this.user = user;
75 }
76
77 public TetrinetClient(User user, Socket socket)
78 {
79 this();
80 setSocket(socket);
81 this.user = user;
82 }
83
84 /***
85 * Return the protocol used by this client.
86 */
87 public Protocol getProtocol()
88 {
89 return this.protocol;
90 }
91
92 /***
93 * Set the protocol.
94 */
95 public void setProtocol(Protocol protocol)
96 {
97 this.protocol = protocol;
98 }
99
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
115 new MessageSender("sender-" + user.getName()).start();
116 }
117
118 connectionTime = new Date();
119
120
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
132 Message message = receive();
133
134
135 if (message == null) continue;
136
137
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
156 channel.send(message);
157 }
158 else
159 {
160
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
183 ClientRepository.getInstance().removeClient(this);
184
185
186 if (channel != null)
187 {
188
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
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
220 queue.add(message);
221 }
222 else
223 {
224
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
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
281 Message message = getProtocol().getMessage(line);
282
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
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
441 Message message = queue.take();
442
443 if (disconnected)
444 {
445 return;
446 }
447
448
449 if (isRateExceeded(System.currentTimeMillis()))
450 {
451 sleep(10);
452 }
453
454
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 }