1  package com.palantir.blog.processspawner;
2  /*
3   * All source code and information in this file is made
4   * available under the following licensing terms:
5   *
6   * Copyright (c) 2009, Palantir Technologies, Inc.
7   * All rights reserved.
8   *
9   * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions are
11  * met:
12  *
13  *     * Redistributions of source code must retain the above copyright
14  *       notice, this list of conditions and the following disclaimer.
15  *
16  *     * Redistributions in binary form must reproduce the above
17  *       copyright notice, this list of conditions and the following
18  *       disclaimer in the documentation and/or other materials provided
19  *       with the distribution.
20  *
21  *     * Neither the name of Palantir Technologies, Inc. nor the names of its
22  *       contributors may be used to endorse or promote products derived
23  *       from this software without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36  *
37  *
38  */ 
39 import java.io.BufferedReader;
40 import java.io.IOException;
41 import java.io.InputStreamReader;
42 import java.io.OutputStreamWriter;
43 import java.io.Writer;
44 import java.net.InetAddress;
45 import java.net.ServerSocket;
46 import java.net.Socket;
47 import java.net.SocketException;
48 import java.util.concurrent.Executors;
49 
50 import javax.net.ServerSocketFactory;
51 import javax.net.SocketFactory;
52 
53 /**
54  * A server that implements a simple <a href='http://en.wikipedia.org/wiki/ACK_(computing)'>ACK</a>
55  * protocol: every message (a string matching the regex: /^.*$/) is answered with the string "ACK\n".
56  * If the message "SHUTDOWN" is encountered, the process will exit.
57  * 
58  * This server spawns a handler thread for every incoming connection.  A more robust implementation
59  * would use {@link Executors} to control the number of concurrent threads.
60  * @author regs
61  *
62  */
63 public class Server extends Thread {
64 
65    public static final String ACK   = "ACK";
66    public static final int SERVERPORT = 10001;
67    /**
68     * The only special command in this protocol.  If this message is received, the process will
69     * immediately exit, after ACKing, of course.
70     */
71    public static final String SHUTDOWN = "SHUTDOWN"; 
72 
73    public static void main(String[] args)  throws Exception {
74       
75       ServerSocketFactory socketFactory = ServerSocketFactory.getDefault();
76       ServerSocket serverSocket = socketFactory.createServerSocket(SERVERPORT);
77 
78       // infinite is exited by handler thread calling System.exit()
79       while(true) {
80          try {
81             System.out.println("Waiting for connection");
82             Socket connection = serverSocket.accept();
83             System.out.println("Spawning socket handler");
84             new Server(connection);
85          } catch (Exception e) {
86             e.printStackTrace();
87          }
88       }
89    }
90 
91    /**
92     * Thread-naming counter.
93     */
94    static int count = 0;
95    Socket connection;
96    int id = 0;
97 
98    /**
99     * Starts a handler thread to handle the passed connection.
100    * 
101    * @param connection
102    * @throws IOException
103    */
104   public Server(Socket connection) throws IOException {
105      synchronized (getClass()) {
106         id = ++count;
107      }
108      setName("Socket Handler" + id);
109      this.connection = connection;
110      this.start();
111   }
112
113   private void out(String message) {
114      System.out.println("[" + getName() + "]: " + message);
115   }
116
117   /**
118    * The handler routine that actually implements the protocol.
119    */
120   @Override
121   public void run() {
122      try {
123         BufferedReader r = new BufferedReader(new InputStreamReader(connection.getInputStream()));
124         Writer w = new OutputStreamWriter(connection.getOutputStream());
125
126         String line = null;
127         do {
128            line = r.readLine();
129
130            if(line != null) {
131               out("Got message: " + line);
132               w.write(ACK + "\n");
133               w.flush();
134               
135               // check for shutdown
136               if(SHUTDOWN.equals(line)) {
137                  // kill it
138                  System.exit(0);
139               }
140            }
141         } while ( line != null );
142      } catch(SocketException e) {
143         if("Connection Reset".equals(e.getMessage())) {
144            // ignore - this is produced by the 
145            // probing code to see if the server is up.
146         }
147      } catch (Exception e) {
148         System.out.println("Error while handling socket");
149         e.printStackTrace();
150      }        
151   }
152
153   /**
154    * Repeats a TCP connection check every <em>sleepTime</em> milliseconds until it either succeeds
155    * or times out after <em>timeout</em> milliseconds.
156    * 
157    * @see Server#checkServerIsUp(InetAddress, int) An explanation of the TCP checking mechanism.
158    * 
159    * @param timeout If no check is successful after this many milliseconds has passed, fail the 
160    * overall checking process.
161    * @param sleepTime How long to wait (in milliseconds) between checks of the service.
162    * @param server address of server to check.
163    * @param port port to check.
164    * @return true if a connection attempt succeeds, false in the case of error or 
165    * no connection attempt successful.
166    */
167   public static boolean checkServerIsUp(long timeout, long sleepTime,InetAddress server, int port ) {
168      long start = System.currentTimeMillis();
169      while((System.currentTimeMillis() - start) < timeout){
170         if(!checkServerIsUp(server, port)){
171            try {
172               Thread.sleep(sleepTime);
173            } catch (InterruptedException e) {
174               return false;
175            }
176         }
177         else{
178            return true;
179         }
180      }
181      return false;
182   }
183   
184   /**
185    * Performs a simple TCP connection check to the specified address and port.
186    * 
187    * @param server address of the server to contact.
188    * @param port TCP port to connect to on the specified server.
189    * @return true if that port is accepting connections, 
190    * false in all other cases: not listening and/or connection error.
191    */
192   public static boolean checkServerIsUp(InetAddress server, int port) {
193      Socket sock = null;
194      try {
195         sock = SocketFactory.getDefault().createSocket(server, port);
196         sock.setSoLinger(true, 0);
197         return true;
198      } catch (IOException e) { 
199         return false;
200      }
201      finally{
202         if(sock != null){
203            try {
204               sock.close();
205            } catch (IOException e) {
206               // don't care
207            }
208         }
209      }
210   }
211}
212