View Javadoc

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             // 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 }