자바 서블릿 컨테이너의 Comet 지원 1 - Jetty

이전글: Comet 에 대하여

Jetty 는 버전 6.0에서부터 Continuation 이라는 것을 도입해서 Comet 스타일의 웹 프로그래밍을 효과적으로 구현할 수 있게 되었다.  jetty의 Continuation 을 이용하면 웹 요청을 처리를 중지(suspend)시켜서 대기 상태로 만들었다가 후에 필요할 때 다시 재개(resume)시킬 수 있다.  웹 요청 처리를 중지(suspend)시키면 요청을 처리하던 스레드는 다시 스레드 풀(Thread Pool)로 되돌아가 다른 요청을 처리할 수 있게 된다.  이로서 장시간 유지되는 HTTP 연결을 더 적은 수의 스레드로 효과적으로 처리할 수 있게 된다.

Continuation 은 scheme 등의 언어에서 지원하던 개념인데, jetty 에서는 이와 비슷하긴 하지만 꼭 같지는 않다.  요청 처리를 중지(suspend)시킨 후 재개(resume)하면 바로 중지시킨 그 지점에서 다시 진행하는 것이 아니라 요청 처리 체인(FilterChain)을 다시 처음부터 진행한다. 이것 때문에 프로그래밍에 약간 신경써야 할 것들이 있다. 

실제 Jetty 의 Contiuation 을 이용하여 채팅을 구현한 예로 설명을 진행하겠다.

먼저 채팅 페이지를 보여주고 채팅 메시지를 받는 서블릿이다.

ChatServlet.java
...
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ... {
  request.getRequestDispatcher("/WEB-INF/jsp/chat.jsp").forward(request, response);
}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ... {
  String name = request.getParameter("name");
  String text = request.getParameter("text");

  String message = "<b>" + name + "</b>: " + text + "<br>";
  messageSender.sendMessage(message);

  response.setContentType("text/html");
  response.getWriter().println("OK");
}
...


채팅 페이지는 다음과 같다.

chat.jsp
...
<script type="text/javascript">
    function pollMessage() {
        new Ajax.Updater({ success: 'panel' }, '/broadcaster', {
            method: 'get',
            insertion: 'bottom',
            onComplete: pollMessage
        });
    }
    function sendMessage() {
        new Ajax.Request('/chat', {
            method: 'post',
            parameters: { nick: $F(name'), text: $F('text') },
        });
        $('text').clear();
        return false;
    }
</script>
</head>
<body onload="pollMessage()">
<h1>채팅 페이지</h1>
<div id="panel"></div>
<form action="" method="POST" onsubmit="return sendMessage()">
    Nick <input type="text" name="name" id="name" size="10">
    <input type="text" name="text" id="text" size="40">
    <input type="submit" value="보내기">
</form>
...


ajax 를 이용해서 채팅 메시지를 ChatServlet 으로 보낸다.  그리고 BroadcasterServlet 에 long polling 으로 접속해서 메시지를 받아 이를 패널에 출력한다.  pollMessage 함수가 Comet long polling 을 하는 코드이다.

ChatServlet 은 chat 메시지를 받으면 이를 messageSender 객체로 보낸다. messageSender 객체는 MessageSender 클래스의 인스턴스로 미리 ServletContext 에 저장해 놓고 최초 context 기동시에 기동시킨다.

다음은 Long Polling 으로 접속된 클라이언트에 채팅 메시지를 뿌려주는 BroadcasterServlet 이다. 이부분에 jetty의 Continuation 이 사용되었다.

BroadcasterServlet.java
...
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ... {
    Continuation continuation = ContinuationSupport.getContinuation(request, null);
    if (! continuation.isPending()) {
        // new session start
        response.setContentType("text/html; charset=utf-8");
        messageSender.addSession(continuation);
    }
    continuation.suspend(0); // request suspend
    // resumed
    String message = (String) continuation.getObject();
    PrintWriter out = response.getWriter();
    out.println(message);
    out.flush();
    response.flushBuffer();
}
...


여기서 messageSender 객체는 위의 ChatServlet 의 messageSender 객체와 같은 객체이다.

최초로 요청이 처리될 때에는 continuation.isPending() 메서드가 false 가 된다.  그리고 Contiunation 이 suspend 되었다가 resume 되어 다시 요청 처리 체인이 진행될 때에는 isPending() 메서드가 true 가 된다(다시 suspend 메서드가 호출될 때까지).  이를 이용하여 suspend 호출 이후 부분만을 다시 실행시키는 것처럼 할 수 있다.

continuation.suspend() 가 호출되면 이 요청 처리는 여기서 끝나게 된다.  그렇지만 HTTP 커넥션은 그대로 유지되고 있으며 request, response 객체도 여전히 유효한 상태이다.  이후 continuation.resume() 이 호출되면 이 요청 처리 체인은 다시 실행된다(resume 메서드는 messageSender 에서 호출한다).  이 때 continuation 객체는 이전과 같은 객체이며, 다시 suspend() 호출을 만날 때까지 isPending() 메서드는 true 가 된다(반면 최초 suspend() 호출 이전에 isPending() 메서드는 false 이다). 이를 이용하여 suspend() 이후를 진행시키는 것과 같은 효과를 낼 수 있는 것이다.

MessageSender 클래스는 다음과 같다.

MessageSender.java
public class MessageSender implements Runnable {
  private volatile boolean running = true;
  private final BlockingQueue<String> messages =
                             new LinkedBlockingQueue<String>();
  private final Set<Continuation> sessions =
                             new CopyOnWriteArraySet<Continuation>();

  public void stop() {
    this.running = false;
  }

  public void addSession(Continuation cont) {
    sessions.add(cont);
  }

  public void sendMessage(String message) {
    try {
      messages.put(message);
    } catch (InterruptedException ignore) {
      // 종료 처리를 해야 하지만 생략
    }
  }

  public void run() {
    while (running) {
      String message = null;
      try {
        message = messages.take();
      } catch (InterruptedException ignore) {
        // 종료 처리를 해야 하지만 생략
      }
      for (Continuation continuation : sessions) {
        continuation.setObject(message);
        continuation.resume();
        sessions.remove(continuation);
      }
    }
  }
}


이 클래스는 ServletContextListener 에서 최초 context 가 기동될 때 인스턴스를 생성하여 새 스레드로 시작한다.

ChatServlet 에서 sendMessage 를 호출하면 메시지가 Queue 에 쌓이게 되고 이 메시지는 메시지 처리 스레드에서 받아 continuation 객체에 저장한 다음 continuation 을 resume 한다.  그러면 BroadcasterServlet 에서 다시 실행이 되는 것이다.  continuation suspend() 호출과 resume 호출 그리고 resume 후의 진행은 모두 다른 스레드에서 진행된다.  따라서 이 사이의 데이타 교환은 Continuation 객체의 setObject 와 getObject 메서드를 통해서만 해야 한다.

Jetty 의 Continuation 방식의 Comet 지원은 기존 Servlet API 가 크게 바뀌지 않아도 된다는 장점이 있다.  그래서인지 Servlet 3.0 의 Comet 지원도 이와 비슷한 방식 - ServletRequest 를 suspend, resume 하는 방식 - 으로 논의되고 있는 것 같다.  그렇지만 약간은 억지같은 Continuation 방식으로 최초 suspend 시 이곳에서 처리가 끝난다는 점이 직관적이지 못하며 resume 을 위해 isPending 과 같은 메서드로 체크를 해야 한다는 번거로움이 있다.

Tomcat 6.0, Resin 3.1 의 방식은 훨씬 이해하기 쉬운 방식인데 이는 다음 번에 다루겠다.

by Corund | 2008/07/30 16:52 | 트랙백(1) | 핑백(2) | 덧글(0)

트랙백 주소 : http://corund.egloos.com/tb/1909837
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Tracked from 나는 해피엔딩이 좋아 at 2009/06/08 17:49

제목 : 자바 서블릿 컨테이너의 Comet 지원 1 - Je..
자바 서블릿 컨테이너의 Comet 지원 1 - Jetty Corund 님의 글을 보고 테스트한 샘플코드를 올립니다. * pom.xml파일 build.plubings.plugin.configurations 을 적절히 수정해 사용해 해 주세요. (예제는 http://localhost:8000/comet_jetty )...more

Linked at 점프와 쉼없는 나아감 : 자바.. at 2008/07/30 19:31

... 이전글 Comet 에 대하여 자바 서블릿 컨테이너의 Comet 지원 1 - Jetty Tomcat 에서는 버전 6.0의 Advanced IO 지원을 통해 Comet 모델을 효과적으로 구현할 수 있게 되었다. Tomcat 6.0 에서는 ... more

Linked at 점프와 쉼없는 나아감 : 자바.. at 2008/08/01 09:35

... 이전글 Comet 에 대하여 자바 서블릿 컨테이너의 Comet 지원 1 - Jetty자바 서블릿 컨테이너의 Comet 지원 2 - Tomcat Resin 은 버전 3.1에서 Comet 모델을 지원한다. Tomcat 과 마찬가 ... more

:         :

:

비공개 덧글

◀ 이전 페이지다음 페이지 ▶