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    }