Real time Apps #4 - SSE ( Serve Sent Events )


참조 URL
  1. http://en.wikipedia.org/wiki/Server-sent_events - 위키피디아
  2. http://www.w3schools.com/html/html5_serversentevents.asp
  3. https://today.java.net/article/2010/03/31/html5-server-push-technologies-part-1#sse
  4. http://www.w3.org/TR/2012/WD-eventsource-20120426/
  5. http://techbrij.com/real-time-chart-html5-push-sse-asp-net-web-api
  6. http://m.mkexdev.net/71

  

목차
  1. Polling - 있어?
    [ASP.NET MVC] - Real time Apps - Polling
  2. LongPolling - 있으면 보내줘!
    [ASP.NET MVC] - Real time Apps - LongPolling
  3. SSE ( Server Send Events ) - 있으면 보내줘 기다릴께~ 
    [ASP.NET MVC] - Real time Apps - SSE ( Serve Sent Events )
  4. WebSocket - 어! 왔네~
    [ASP.NET MVC] - Real time Apps - WebSocket
  5. SignalR - over WebSocket ( MS Platform )
    [ASP.NET MVC] - Real time Apps - SignalR over WebSocket


 이 포스트에 있는 내용이 언제나 확실한 정답은 아닙니다. 진실이라고 생각해 왔던 전제가 시간의 지남에 따라 들어나지 않았던 다른 이면 때문에 좋은 방향으로 이끌어 낼 수 있는 역할로 변환 되는게 역사적으로도 많은 증명 있었습니다. 그렇지만 저는 현재 상황에서 최선의 답을 찾고자 노력하였으며 이 글을 읽는 다른 분들에게 다음 길을 갈 수 있도록 도와주는 디딤돌이 되고자 노력하고자 포스팅을 통해 공유하고자 하는 것입니다. 그리고 프로그래머라는 타이틀을 달고 살아야 한다면 "왜"라는 의문을 항상 가지고 다니면서 자신의 위치에 안주하지 않고 항상 노력하는 모습으로 살아 가고자 합니다. 언제든 지적이나 오류가 있으면 피드백 부탁 드리겠습니다.

ing™       


 이번에는 SSE(Server Sent Events)에 대해서 알아 보자. SSE는 HTML5의 스펙으로서 단방향 푸시를 구현하기 위해 제안된 표준 기술이다. 앞의 Polling, LongPolling은 묵시적인 표준과 같은 기법의 범주에 속한다 할 수 있겠다. 그렇지만 SSE가 완전한 푸시를 구현한 스펙은 아니다.  소켓 통신과 같이 서버에서 클라이언트로 임의의 시간에 데이터를 보내주는 사항은 아니며 이전 기술과 같은 폴링 기법과 같은 유사한 방법으로 커넥션을 유지해 구현되고 있다. '그림6'로 전체 동작 방식을 이해해 보자.


[그림 6] SSE 동작 방식


1. 브라우저의 javascript에서 EventSource('url') 객체를 이용하여 웹서버와 통신 세션을 맺는다.

2. 서버에서 이벤트가 발생하면 개발자가 정의한(여기서는 Message로 하겠다) 해당 정보를 브라우저에 보내주고 커넥션은 유지한다.

3. 서버에서 이벤트가 두번째로 발생이 되면 유지되고 있는 커넥션을 통해 Message를 보내준다.

4. 연결 종료시 까지 3단계를 수행 한다.

5. 만약 중간에 어떤한 이유로 종료가 되었다면 EventSource 객체는 다시 통신 세션을 맺는다.


 SSE는 지금까지 살펴본 유사 Polling 기법과는 다르게 서버와의 커넥션을 유지하여 서버에서 발생한 이벤트 정보를 브라우저에 보내주는 방식이다. 추가 이벤트 정보를 받을 때마다 유지된 커넥션을 통하므로 재 연결시 필요한 부가 정보(사용자 정보, 쿠키, 헤더,...) 없이 데이터를 받을 수 있게 된다.


<!-- Polling 하여 받은 데이터를 반영할 수 있는 DOM --> <ol id="messages"></ol> @section scripts{     <script type="text/javascript">         $(function () {             if (typeof (EventSource) !== "undefined") {                 // 이벤트 소스를 선언한다. EventSource('/ControllerName/GetMesages');                 var eventSrc = new EventSource("@Url.Action("GetMessages")");                 // 이벤트 소스 객체에서 데이터를 받는 이벤트 처리 핸들러 선언                 eventSrc.addEventListener("open", function (evt) {                     $("<li>").text('Opened!').appendTo("#messages");                 });                 // 이벤트 소스 객체에서 데이터를 받는 이벤트 처리 핸들러 선언                 eventSrc.addEventListener("message", function (evt) {                     $("<li>").text(evt.data).appendTo("#messages");                 });                 // 이벤트 소스 객체에서 에러 이벤트 처리 핸들러 선언                 eventSrc.addEventListener("error", function (evt) {                     console.log(evt);                 });             }             else {                 alert('EventSource 객체를 지웒하지 않는 브라우저 입니다.');             }         });     </script> }

[코드5] SSE - Browser의 HTML과 Javascript


 위 코드는 페이지가 로딩이 되면 EventSource 객체를 통해 웹 서버에 커넥션을 맺는다. 그리고 EventSource에서 제공하는 이벤트에 대해서 open, message, error에 대해 처리할 수 있는 핸들러를 각각 선언하여 연결한다. open 이벤트는 #messages 테그에 'Opened!'를 표시하도록 하고 message 이벤트는 서버에서 보내준 데이터 값을 표시하고, error 이벤트는 console에 표시하도록 하였다. 이제 아래 코드를 통해 서버쪽의 코드를 알아 보자.


/// <summary> /// 초기화시 중복 초기화가 되지 않도록 잠금 역할하는 객체 /// </summary> private static object _blockCollectionLock = new object(); /// <summary> /// 메세지 컨테이너 역할 /// </summary> private static BlockingCollection<string> _data = null; /// <summary> /// 생성자 /// </summary> public EventSource2Controller() { InitSSE(); } // // GET: /EventSource2/ public ActionResult Index() {     return View(); } /// <summary> /// 중간 정산 - 중간에 채워진 버퍼의 내용을 보낸다. /// </summary> public ActionResult GetMessages() { // EventSource로 통신할 때는 ContentType을 'text/event-stream'으로 해야 한다.     Response.ContentType = "text/event-stream";     Response.Expires = -1;     var message = string.Empty;     while (true)     {         Thread.Sleep(1000); if (_data.TryTake(out message, TimeSpan.FromMilliseconds(1000)))         {             // 종료하지 않고 일부 데이터만 웹 브라우저에 보냄             if (Response.IsClientConnected)             {                Response.Write("data: " + message + "\n\n");                 Response.Flush();             }         }         else         {

// 서버에 데이터가 없으면 종료하고 커넥션을 닫는다.             Response.Write("data: 종료\n\n");             Response.Flush();             Response.End();             break;         }    }

    return Content("""text/event-stream"); } /// <summary> /// 초기화 함수 /// </summary> [NonAction] // 웹에서 직접 호출해도 동작하지 않도록 해주는 Attribute protected void InitSSE() {     _data = new BlockingCollection<string>();     _data.Add("started");     for (int i = 0i < 10i++)     {         _data.Add("item" + i.ToString());     }     _data.Add("ended"); }

[코드6] SSE - Server side 코드



 '코드6'의 Server-side 코드는 이전 Polling 기법과는 다르게 다소 복잡하게 수행이 된다. 생성자에서 초기화 함수인 InitSSE()를 호출하여 초기화를 진행하고 Browser에서 EventSource를 통해 요청한 GetMessage()는 내부적으로 루프를 돌면서 수행하며 계산된 결과를 브라우저에 'Response.Write()'를 통해서 브라우저에 보내주고 있다. 이때 커넥션을 종료하지 않고 그대로 유지하며 지속적으로 클라이언트인 브라우저에 보내준다. 실제적인 비지니스 개발에서는 이 부분에서 DB를 폴링하여 적절한 값의 변화를 브라우저에 보내줘 클라이언트에서는 실시간으로 정보가 변경되는 것 같이 보여주도록 할 수 있다는 것이다. 이전 Polling과는 다른점은 EventSource를 통해 한번만 연결된 상태에서 보다 적은 통신 용량으로 같은 효과를 낼 수 있다는 것이다. 아래 '그림7'로 결과 화면을 확인해 보자.



[그림7] SSE - 결과 화면



소스 코드 자체에 주석과 직관적인 코딩으로 충분히 파악이 가능할 것으로 예상하므로 별도의 설명을 생략하도록 하겠습니다. 포스트의 내용이 장황한 설명 보다는 주석과 소스코드 자체 만으로도 이해할 수 있도록 하기 위해 노력하였습니다. 실 개발에서도 적용할 수 있도록 간단하면서도 현실적인 예제 프로그램을 통해 각 소스를 만들고 이해 시키고자 하였으며 실무에 필요한 개발요구 사항들을 해결 하는데 도움이 되고자 노력하였습니다. 그리고 소스와 같이 있는 주석을 이용해 nDoc이나 별도의 자동 Document 제작 유틸로 API 문서를 만드는 데에도 도움이 되었으면 한다. 
※ DOC에 대한 프로그램 정보 Util link

ing™       



+ Recent posts