이 포스트에 있는 내용이 언제나 확실한 정답은 아닙니다. 진실이라고 생각해 왔던 전제가 시간의 지남에 따라 들어나지 않았던 다른 이면 때문에 좋은 방향으로 이끌어 낼 수 있는 역할로 변환 되는게 역사적으로도 많은 증명 있었습니다. 그렇지만 저는 현재 상황에서 최선의 답을 찾고자 노력하였으며 이 글을 읽는 다른 분들에게 다음 길을 갈 수 있도록 도와주는 디딤돌이 되고자 노력하고자 포스팅을 통해 공유하고자 하는 것입니다. 그리고 프로그래머라는 타이틀을 달고 살아야 한다면 "왜"라는 의문을 항상 가지고 다니면서 자신의 위치에 안주하지 않고 항상 노력하는 모습으로 살아 가고자 합니다. 언제든 지적이나 오류가 있으면 피드백 부탁 드리겠습니다.
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 --><olid="messages"></ol>@section scripts{<scripttype="text/javascript">$(function(){if(typeof(EventSource)!=="undefined"){// 이벤트 소스를 선언한다. EventSource('/ControllerName/GetMesages');vareventSrc=newEventSource("@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>privatestaticobject_blockCollectionLock=newobject();
///<summary>/// 메세지 컨테이너 역할///</summary>privatestaticBlockingCollection<string>_data=null;
///<summary>/// 생성자///</summary>publicEventSource2Controller()
{
InitSSE();
}
//// GET: /EventSource2/publicActionResultIndex()
{
returnView();
}
///<summary>/// 중간 정산 - 중간에 채워진 버퍼의 내용을 보낸다.///</summary>publicActionResultGetMessages()
{
// EventSource로 통신할 때는 ContentType을 'text/event-stream'으로 해야 한다.Response.ContentType="text/event-stream";
Response.Expires=-1;
varmessage=string.Empty;
while (true)
{
Thread.Sleep(1000);
if (_data.TryTake(outmessage, 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;
}
}
returnContent("", "text/event-stream");
}
///<summary>/// 초기화 함수///</summary>
[NonAction] // 웹에서 직접 호출해도 동작하지 않도록 해주는 AttributeprotectedvoidInitSSE()
{
_data=newBlockingCollection<string>();
_data.Add("started");
for (inti=0; i<10; i++)
{
_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™
이번에는 LongPolling에 대해서 알아 보자. 좀전에 Polling에 대해서 알게 되었다면 별로 어렵지 않게 이해를 할 수 있을 것으로 예상한다. 그냥 Polling은 주기적으로 서버에 요청하는 방식이지만 LongPolling 방식은 한번 요청하고 서버에 데이터가 있을때만 반환받는 방식이다. 아래 '그림3'으로 보면 좀더 쉽게 이해할 수 있을 것이다.
[그림3] LongPolling 방식
1. Request1로 서버에 요청하고 서버에서 Event( or Data )가 발생되면 해당 형식( 여기서는 Message라고 정의 )을 브라우저에 보내주고 브라우저 커넥션은 끊어진다.
2. 받은 데이터를 처리하고 곧바로 서버에 Request2를 요청하고 데이터가 발생할 때까지 대기한다.
3. 1단계를 다시 시작한다.
이와 같은 단계로 LongPolling 방식이 구동이 된다. Polling와 LongPolling 방식은 전체적으로 비슷한 방식을 개발할 수 있으나 다른점은 'LongPolling'이라는 이름처럼 길게 대기하여 서버에 응답요청을 한다는데 있다. 길게 기다리다가 "있으면 보내줘!"와 같은 컨셉이다. 이제 실제 코드로 알아가 보자.
<!-- Polling 하여 받은 데이터를 반영할 수 있는 DOM --><olid="messages"></ol>@using (Ajax.BeginForm("PostMessage", newAjaxOptions())) {
<p>
Say : @Html.TextBox("messageText")
<inputtype="submit"/></p>
}
<!--
위 코드는 아래의 HTML Tag로 변환된다.
<form action="/LongPolling/PostMessage" data-ajax="true" id="form0" method="post"> <p>
Say : <input id="messageText" name="messageText" type="text" value="" />
<input type="submit" />
</p>
</form>
-->
@section scripts{<scripttype="text/javascript">$(function(){// Begin the LongPolling loopgetNextMessage();// Form의 Submit 이벤트 통제$("form").submit(function(){varform=$(this);
// messageText의 Html tag에 입력된 값을 가져온다.
vartxtMessageText=$(form.find('#messageText'));
// Ajax로 서버에 입력한 값을 넘겨준다.$.post('@Url.Action("PostMessage")',{'messageText':txtMessageText.val()},function(){txtMessageText.val('');txtMessageText.focus();});// HTML의 Submit가 일어나지 않도록 한다.returnfalse;});});// 서버에 메시지가 있는지 문의 요청functiongetNextMessage(){$.post("@Url.Action("GetNextMessage")",function(message){$("<li>").text(message).appendTo("#messages");// 응답받은 즉시 다시 요청getNextMessage();});}</script>}
[코드3] LongPolling - Browser의 javascript 코드
위 코드에서는 '그림3'과 같이 사용자의 입력을 받는 부분이 있다.
[그림4] LongPolling 테스트 대기 화면
위 '코드3'의 전체적인 흐름을 파악해 보자
- HTML이 완성되면 getNextMessage()를 호출하여 서버에 요청을 한다.
- Html의 form에서 submit 버튼을 눌렀을 때 페이지 이동이 일어나지 않도록 처리한다.
- getNextMessage() 함수는 서버에 GetNextMessage()를 호출하고 반환 받을 때까지 대기한다.
- 사용자에게 데이터 입력을 받고 제출 버튼을 누르면 ajax의 Post 방식으로 PostMessage 함수를 호출하여 넘겨준다.
이제 서버측 '코드4'에 대해서 알아 보자
///<summary>/// 요청이 오면 임의의 시간 대기 후 응답///</summary>///<returns></returns>publicasyncTask<string>GetNextMessage()
{
returnawait_nextMessage.Task;
}
///<summary>/// Lock object///</summary>staticobject_nextMessageLock=newobject();
///<summary>/// for LongPolling object///</summary>staticTaskCompletionSource<string>_nextMessage=newTaskCompletionSource<string>();
publicvoidPostMessage(stringmessageText)
{
lock (_nextMessageLock)
{
// Concurrent 해결 하기 위해 임시 변수에 옮긴다.varoldNextMessage=_nextMessage;
_nextMessage=newTaskCompletionSource<string>();
oldNextMessage.SetResult(messageText);
}
}
[코드4] LongPolling - Server side 코드
1. GetNextMessage 함수가 호출이 되면 return await _nextMessage.Task 구문에서 리턴값이 있을 때까지 비 동기로 대기 한다.
2. PostMessage 함수가 호출 될 때 위에서 대기하고 있던 객체인 _nextMessage 객체에 SetResult(msg)에 값을 넘겨주면 1에서 대기하던 객체는 해당 값을 반환하도록 해주며 다시 대기하도록 한다. 이때 Lock를 통해 쓰레드 안정성을 확보하도록 하였다.
3. TaskCompletionSource<string> 객체는 입력값이 string이고 출력 값이 string인 객체로 선언되었으며 비 동기로 동작할 수 있도록 .Net Framework 4에서 부터 제공하는 클래스다.
4. public async Task<string> GetMessage()는 비 동기로 수행된다고 알려주는 C# 5.0 예약어다. 이 함수 안에는 반드시 await 예약어를 사용하여 비 동기로 사용하는 구문이 있어야 한다. - 이 부분은 node.js의 특징인 비 동기 방식을 .Net에서 지원하는 것이라 이해해도 되겠다.
이제 LongPolling에 대해 전체적인 흐름을 이해했으리라 생각이 든다. 지금까지 살펴보았다면 꼭 실습으로 한 번씩 테스트를 해 보기를 바라며 정 여건이 되지 않는다면 마지막 편에 제공되는 전체 소스파일을 다운바다 실행해 보기 바란다.
마지막으로 결과 화면으로 확인해 보자 아래 '그림5'를 통해 확인 할 수 있다.
[그림5] LongPolling 결과 화면
위 화면에서 제출을 누르면 “5.Step #5" 밑에 "6. Step #6"이 추가 됨을 확인 할 수 있을 것이다.
소스 코드 자체에 주석과 직관적인 코딩으로 충분히 파악이 가능할 것으로 예상하므로 별도의 설명을 생략하도록 하겠습니다. 포스트의 내용이 장황한 설명 보다는 주석과 소스코드 자체 만으로도 이해할 수 있도록 하기 위해 노력하였습니다. 실 개발에서도 적용할 수 있도록 간단하면서도 현실적인 예제 프로그램을 통해 각 소스를 만들고 이해 시키고자 하였으며 실무에 필요한 개발요구 사항들을 해결 하는데 도움이 되고자 노력하였습니다. 그리고 소스와 같이 있는 주석을 이용해 nDoc이나 별도의 자동 Document 제작 유틸로 API 문서를 만드는 데에도 도움이 되었으면 한다. ※ DOC에 대한 프로그램 정보 Util link