001    /*
002    * Created on Jul 19, 2004
003    *
004    * To change the template for this generated file go to
005    * Window>Preferences>Java>Code Generation>Code and Comments
006    */
007    package fang;
008    
009    import java.io.*;
010    import java.net.*;
011    import java.util.*;
012    
013    
014    /**
015     * This establishes incoming connections and
016     * directs them to the appropriate GameConnection.
017     * The Server can be run continually on a dedicated
018     * computer to serve all multiclient games.
019     * @author Jam Jenkins
020     */
021    public class HttpServer extends Thread
022    {
023        /**
024         * port on which to run the server
025         */
026        public static int PORT = 1554;
027    
028        /**
029         * map of currently running games to the GameConnections
030         */
031        private HashMap<String, GameConnections> games =
032            new HashMap<String, GameConnections>();
033    
034        /**
035         * true indicates still accepting new connections,
036         * false indicates accepting no more connections
037         */
038        private boolean connected = true;
039    
040        /**
041         * the output stream used for applets
042         */
043        private ObjectOutputStream pipedOut;
044    
045        /**
046         * the input stream used for applets
047         */
048        private ObjectInputStream pipedIn;
049    
050        /**
051         * the socket listening for new connections
052         */
053        private ServerSocket serverSocket;
054    
055        /**the constructor to use when using pipes*/
056        public HttpServer(Object dummy)
057        {}
058    
059        /**the constructor to use in applications
060         * @throws IOException */
061        public HttpServer() throws IOException
062        {
063            serverSocket = new HttpServerSocket(PORT);
064        }
065    
066        /**makes a piped input stream to use in applets
067         * @return the piped input stream connected to the server
068         */
069        public ObjectInputStream getPipedInput()
070        {
071            try
072            {
073                PipedOutputStream out = new PipedOutputStream();
074                PipedInputStream in = new PipedInputStream();
075                out.connect(in);
076                pipedOut = new ObjectOutputStream(out);
077                return new ObjectInputStream(in);
078            }
079            catch (IOException ioe)
080            {
081                ioe.printStackTrace();
082                pipedOut = null;
083                return null;
084            }
085        }
086    
087        /**makes a piped output stream to use in applets
088         * @return the piped output stream connected to the server
089         */
090        public ObjectOutputStream getPipedOutput()
091        {
092            try
093            {
094                PipedInputStream in = new PipedInputStream();
095                PipedOutputStream out = new PipedOutputStream();
096                in.connect(out);
097                ObjectOutputStream toReturn = new ObjectOutputStream(out);
098                pipedIn = new ObjectInputStream(in);
099                return toReturn;
100            }
101            catch (IOException ioe)
102            {
103                ioe.printStackTrace();
104                pipedIn = null;
105                return null;
106            }
107        }
108    
109        /**
110         * starts pipe communications
111         */
112        public void startPipes()
113        {
114            if (pipedOut != null && pipedIn != null)
115            {
116                new Dispatcher(pipedOut, pipedIn).start();
117            }
118            else
119                System.err.println("cannot start pipes");
120            // seems like a bug in the JDK, but this
121            // thread cannot end, or the Pipe breaks.
122            synchronized (this)
123            {
124                try
125                {
126                    wait();
127                }
128                catch (Exception e)
129                {
130                    e.printStackTrace();
131                }
132            }
133        }
134    
135        /**
136         * connects to a client and forwards the connections
137         * to the proper GameConnection
138         * 
139         * @throws Exception
140         *             if an error occurs while establishing the initial connections
141         */
142        public void connect()
143        {
144    
145            while (connected)
146            {
147                Socket socket = null;
148                try
149                {
150                    //java.lang.System.out.println("accepting connection");
151                    socket = serverSocket.accept();
152                    //java.lang.System.out.println("accepted connection");
153                }
154                catch (IOException ioe)
155                {
156                    ioe.printStackTrace();
157                    break;
158                }
159                try
160                {
161                    //java.lang.System.out.println("server making out");
162                    OutputStream oStream = socket.getOutputStream();
163                    oStream.write("?PNG".getBytes());
164                    oStream.write(13);
165                    oStream.write(10);
166                    oStream.write(26);
167                    oStream.write(10);
168                    oStream.flush();
169                    ObjectOutputStream out = new ObjectOutputStream(oStream);
170                    //out.writeObject("hello\n");
171                    out.flush();
172                    //java.lang.System.out.println("server making in");
173                    ObjectInputStream in = new ObjectInputStream(
174                                               new BufferedInputStream(socket.getInputStream(), 100));
175                    //java.lang.System.out.println("server dispatching");
176                    new Dispatcher(out, in).start();
177                }
178                catch (IOException ioe)
179                {
180                    ioe.printStackTrace();
181                }
182            }
183        }
184    
185        /**
186         * stops the loop from accepting new connections
187         */
188        public void disconnect()
189        {
190            connected = false;
191        }
192    
193        /**starts the pipes for applets, or the server for applications
194         * @see java.lang.Runnable#run()
195         */
196        public void run()
197        {
198            if (pipedIn == null && pipedOut == null)
199                connect();
200            else
201                startPipes();
202        }
203    
204        /**communicates with the client to determine which game
205         * to join
206         * @author Jam Jenkins
207         */
208        class Dispatcher extends Thread
209        {
210            /**
211             * the output stream to the client
212             */
213            ObjectOutputStream out;
214    
215            /**
216             * the input stream from the client
217             */
218            ObjectInputStream in;
219    
220            /**stores the streams for communication
221             * @param out the stream to the client
222             * @param in the stream from the client
223             */
224            public Dispatcher(ObjectOutputStream out, ObjectInputStream in)
225            {
226                this.out = out;
227                this.in = in;
228            }
229    
230            /**responds to a join command.  The format of the join command is:
231             * Join [gameName] [sessionName] [players]
232             * players is optional.  If not specified, 2 players is assumed.
233             * @param command the line in the format described above
234             * @return true if successful, false otherwise.  Joining can be
235             * unsuccessful if the format is not correct or if the game trying
236             * to join is full.
237             * @throws IOException if the communication chanel becomes corrupt
238             */
239            private boolean joinGame(String command) throws IOException
240            {
241                String[] parts = command.split(" ");
242                String gameName = parts[1];
243                String sessionName = parts[2];
244                String id = "Game: " + gameName + " Session: " + sessionName;
245                int players = 2;
246                if (parts.length > 3)
247                    players = Math.max(1, Integer.parseInt(parts[3]));
248                GameConnections connections = games.get(id);
249                if (connections == null)
250                {
251                    connections = new GameConnections(gameName, sessionName,
252                                                      players);
253                    games.put(id, connections);
254                }
255                if (connections.isFull())
256                {
257                    out.writeObject("Game already full");
258                    out.flush();
259                    return false;
260                }
261                boolean results = connections.addConnection(out, in);
262                if (connections.isFull())
263                    games.remove(id);
264                return results;
265            }
266    
267            /**sends a list of the games currently in progress to the client.
268             * The format of the list is:
269             * Game: [gameName] Session: [sessionName]
270             * @throws IOException
271             */
272            private void listGames() throws IOException
273            {
274                String[] gameIDs = games.keySet().toArray(new String[0]);
275                String sum = "happy\n";
276                for (String name : gameIDs)
277                {
278                    if (!games.get(name).isFull())
279                        sum += name + "\n";
280                }
281                //java.lang.System.out.println("writing " + sum);
282                out.writeObject(sum);
283                out.flush();
284                //java.lang.System.out.println("flushed " + sum);
285            }
286    
287            /**
288             * gets rid of games no longer in progress
289             */
290            private void cleanEmptyGames()
291            {
292                Set < Map.Entry < String, GameConnections >> gameSet = games.entrySet();
293                LinkedList<String> toRemove = new LinkedList<String>();
294                for (Map.Entry<String, GameConnections> game : gameSet)
295                {
296                    if (!game.getValue().isActive())
297                        toRemove.add(game.getKey());
298                }
299                for (String key : toRemove)
300                {
301                    games.remove(key);
302                }
303            }
304    
305            /**reads commands from the client and resonds appropriately
306             * the two commands are
307             * List Games
308             * and
309             * Join [gameName] [sessionName] [players]
310             * @see java.lang.Runnable#run()
311             */
312            public void run()
313            {
314                try
315                {
316                    String command;
317                    while (true)
318                    {
319                        //java.lang.System.out.println("awaiting command");
320                        command = (String) in.readObject();
321                        //java.lang.System.out.println("performing: " + command);
322                        cleanEmptyGames();
323                        if (command.startsWith("Join"))
324                        {
325                            if (joinGame(command))
326                                break;
327                        }
328                        else if (command.startsWith("List Games"))
329                        {
330                            listGames();
331                        }
332                        else if (command.startsWith("Quit"))
333                            return ;
334                    }
335                    // seems like a bug in the JDK, but this
336                    // thread cannot end, or the Pipe breaks.
337                    synchronized (this)
338                    {
339                        int players = Integer.parseInt(command.split(" ")[3]);
340                        if (players == 1)
341                        {
342                            wait();
343                        }
344                    }
345                }
346                catch (Exception e)
347                {
348                    e.printStackTrace();
349                }
350            }
351        }
352    
353        /**starts the server accepting new connections
354         * @param argv not used
355         * @throws IOException 
356         */
357        public static void main(String[] argv) throws IOException
358        {
359            new HttpServer().connect();
360        }
361    }