java로 데몬 형태의 프로그램을 작성할 경우가 있다.
이때 기동이야 java 커맨드로 실행을 하지만,
중지시키기 위해서는 Ctrl+C 를 하거나 백그라운드 실행시 Kill 을 시켜야 하거나 한다.
좀 더 있어보이기 위해 여타 다른 데몬처럼 start, stop 커맨드를 사용하도록 만들어 본다.
핵심은 shutdown 을 처리를 ServerSocket 을 열고 해당 포트에 대한 Socket 통신으로 이루어진다는 점이다. 만들고자 하는 데몬이 통신서버라면 기존 통신 포트 외에 컨트롤 포트를 하나 더 사용하는 것이다.
Tomcat 의 경우 server.xml 의 맨 처음에 나오는 것은 다음과 같다.
<Server port="8005" shutdown="SHUTDOWN" debug="0">
여기서 port 가 바로 shutdown 처리를 위한 포트이고, 이 포트에 shutdown 으로 설정된 SHUTDOWN을 소켓으로 보내면 Tomcat 이 중지된다.
아래는 톰캣과 유사한 형태로 작성한 소스이다.
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.security.AccessControlException; import java.util.Date; import java.util.Random; import java.util.Timer; import java.util.TimerTask; public class Server { private static final String COMMAND_SHUTDOWN = "shutdown"; private static final int COMMAND_PORT = 8099; private boolean started; private Random random; private Timer timer; private Server() { this.started = false; this.random = null; this.timer = null; } private void start() { // 여기서 서버가 수행하는 쓰레드를 start시킨다(반드시 쓰레드이어야 한다). // 에로서 Timer를 사용하여 1초마다 메시지 출력... this.timer = new Timer(); timer.scheduleAtFixedRate( new TimerTask() { public void run() { if ( started ) System.out.println("1초마다 메시지"); } } ,new Date(),1000 ); await(); } private void stop() { // 서버가 수행하던 쓰레드를 중지시킨다. // 예로써 Timer 중지... this.timer.cancel(); this.timer = null; System.out.println("중지되었습니다."); } /** * 서버소켓을 열고 shutdown 요청이 있을때까지 대기... */ protected void await() { try { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(COMMAND_PORT, 1, InetAddress.getByName("127.0.0.1")); started = true; } catch (IOException e) { e.printStackTrace(); } while ( started ) { Socket socket = null; InputStream stream = null; try { socket = serverSocket.accept(); socket.setSoTimeout(10*1000 ); // 타임아웃 10초... stream = socket.getInputStream(); } catch (AccessControlException e) { continue; } catch (IOException e) { System.exit(1); } StringBuffer command = new StringBuffer(); // Cut off to avoid DoS attack int expected = 1024; while ( expected < COMMAND_SHUTDOWN.length() ) { if ( random == null ) random = new Random(System.currentTimeMillis()); expected += (random.nextInt() % 1024); } while ( expected > 0 ) { int ch = -1; try { ch = stream.read(); } catch (IOException e) { ch = -1; } // EOF if (ch < 32) break; command.append((char)ch); expected--; } // 소켓을 닫는다. try { socket.close(); } catch (IOException ignore) {} String cmd = command.toString(); // shutdown 명령시 루프 중지 if ( COMMAND_SHUTDOWN.equals(cmd) ) { break; } } // 서버 소켓을 닫는다. try { serverSocket.close(); } catch (IOException ignore) {} } catch(Throwable t) { t.printStackTrace(); } finally { // 서버 종료 처리... try { stop(); } catch (Throwable ignore) {} } } /** * 소켓으로 shutdown 커맨드를 보낸다. */ private static void shutdown() { Socket socket = null; OutputStream os = null; try { socket = new Socket("127.0.0.1", COMMAND_PORT); os = socket.getOutputStream(); for (int i = 0; i < COMMAND_SHUTDOWN.length(); i++) { os.write(COMMAND_SHUTDOWN.charAt(i)); } os.flush(); } catch(Throwable t) { t.printStackTrace(); System.exit(1); } finally { try { os.close(); } catch(Throwable t) {} try { socket.close(); } catch(Throwable t) {} } } public static void main(String[] args) { try { if ( COMMAND_SHUTDOWN.equals(args[0]) ) { shutdown(); } else { Server server = new Server(); server.start(); } } catch (Throwable t) { t.printStackTrace(); } } }
이 프로그램을 실행시키면 다음과 같은 결과를 볼 수 있다.
다른 콘솔창에서 shutdown 시켰을때 결과...
SSISO Community