Real time Apps #6 - SignalR over WebSocket


참조 URL
  1. http://signalr.net/
  2. http://techbrij.com/jquery-flot-signalr-asp-net-mvc-chart
  3. http://www.asp.net/signalr/overview/getting-started/introduction-to-signalr
  4. http://brad.tistory.com/category/SignalR
  5. http://www.codeguru.com/csharp/.net/sending-notifications-using-asp.net-signalr.htm
  6. http://cafe.daum.net/aspdotnet/O57m/115?docid=8C6QO57m11520130325023137
  7. http://www.asp.net/signalr/overview/getting-started/supported-platforms
  8. http://testswarm.signalr.net/user/signalr


목차
  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™       



ASP.NET SignalR 소개


'http://signalr.net/' 에서는 SignalR을 아래와 같이 소개하고 있다.


ASP.NET SignalR is a new library for ASP.NET developers that makes it incredibly simple to add real-time web functionality to your applications. What is "real-time web" functionality? It's the ability to have your server-side code push content to the connected clients as it happens, in real-time. 


 말하자면 ASP.NET SignalR은 어플리케이션에 매우 간단하게 실시간 웹 기능을 만들 수 있도록 해주는 ASP.NET 개발자를 위한 라이브러리이다. 실시간 웹 기능이란? 서버 사이드에서 실시간으로 일어난 일을 연결된 클라이언트에게 내용을 능동적으로 전달하는 것이다. 즉 SignalR은 즉각적인 사용자와의 상호작용과 실시간 데이터 갱신이 필요한 웹 응용 프로그램 개발을 지원하기 위한 라이브러리이다. 예로 대시보드에서 다른 시스템을 모니터링을 할 때 즉각적으로 변하는 값을 브라우저의 그래프에 즉시 반영하는 시나리오에 적합하다.


 SignalR은 실시간 응용 프로그램 개발 과정을 WebSocket를 이용한 개발보다 더 단순화시켜 줍니다. ASP.NET의 서버 라이브러리와 SignalR에서 같이 제공되는 자바스크립트 클라이언트 라이브러리를 통해서 WebSocket을 직접 사용하는 것보다 더 추상화 시킨 라이브러리이다. '그림8'과 같이 추상화된 라이브러리로 API를 사용하면 WebSocket을 지원하는 클라이언트 브라우저에서는 WebSocket를 통해 서버와 통신하고, 그렇지 않을 경우에는 대체되는 통신 기법으로 바뀌어 서버와의 통신을 유지하도록 해준다. 




[그림11] SignalR 통신




본격적으로 SignalR을 살펴 보기 전에 시스템적인 요구 사항을 점검해 보도록 하자



서버 시스템 요구 사항


지원되는 서버 리스트


  • Windows Server 2012
  • Windows Server 2008 r2.
  • Windows 8
  • Windows 7
  • Windows Azure


지원되는 IIS (웹 컨테이너)


  • IIS 8 or IIS 8 Express.
  • IIS 7 and 7.5. Support for extensionless URLs is required.
  • IIS 통합모드


클라이언트 요구 사항


브라우저 버전


  • Microsoft Internet Explorer versions 8, 9 and 10. Modern, Desktop, and Mobile versions are supported.
  • Mozilla Firefox: current version - 1, both Windows and Mac versions.
  • Google Chrome: current version - 1, both Windows and Mac versions.
  • Safari: current version - 1, both Mac and iOS versions.
  • Opera: current version - 1, Windows only.
  • Android browser

브라우저에서 jQuery version 1.6.4, 1.7.2, 1.8.2, or 1.9.1


자신의 브라우저가 지원이 되는지 알아 볼 수 있는 사이트가 있으니 자세한 정보는 이곳에서 알아 보기 바란다.

http://testswarm.signalr.net/user/signalr





[그림12] Nuget Package Manager에서 SignalR을 검색한 결과 화면


 이제 개발에 필요한 라이브러리를 추가 하자. Nuget을 통해 SignalR로 검색하면 '그림12'와 같은 화면을 확인할 수 있을 것이다. 이 중에서 'Microsoft ASP.NET SignalR'을 선택하고 설치하면 자동으로 Visual Studio에 필요한 DLL들이 추가가 될 것이다. 그런다음 Global.asax.cs 파일을 열어서 '코드12'와 같이 'RouteTable.Routes.MapHubs();'를 Application_Start에 추가 하고 저장 후 닫아 버리자

protected void Application_Start() {     // SignalR 세팅     RouteTable.Routes.MapHubs(); ... (기존 코드)

}

[코드12] Global.asax.cs 에 추가된 초기화 코드


 위 코드는 SignalR이 ASP.NET에서 초기화가 되도록 세팅하는 것이며 Global.asax.cx의 Application_Start 메소드는 웹 서버가 처음 기동될 때 단 한번만 실행이 된다. 그러므로 SignalR 관련 초기화는 한번만 이뤄지게 된다.


<!-- SignalR로 통신하여 받은 데이터를 반영할 수 있는 DOM -->
<ul id="messages"></ul>
 
@using (Ajax.BeginForm("PostMessage"new AjaxOptions())) { 
    <p>
        Say : @Html.TextBox("messageText")
        <input type="submit" />
    </p>
}
 
@section scripts {
    <script src="~/Scripts/jquery.signalR-1.0.1.js" type="text/javascript"></script>
    <script src="~/signalr/hubs" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            // Form의 Submit 이벤트  통제
            $("form").submit(function () {
                var text = $('#messageText').val();
                // Hub를 통해 SignalR 통신으로 서버에 보낸다.
                messagesHub.server.send(text);
                $('#messageText').val('').focus();
                return false;
            });
 
            // SiglalR/Hubs에서 Generated된 javascript 코드를 할당
            var messagesHub = $.connection.messages;
 
            // SignalR에서 Hub를 통해  메세지를 받는다.
            messagesHub.client.addMessage = function (messageFromServer) {
                $('<li>').text(messageFromServer).appendTo("#messages");
            }
 
            // SignalR 시작
            $.connection.hub.start();
        });
    </script>
}

[코드13] SignalR이 브라우저에서 실행될 클라이언트 사이드 코드


1. <script src="~/Scripts/jquery.signalR-1.0.1.js" type="text/javascript"></script> : SignalR 관련 라이브러리를 추가하면 jQuery를 확장한 자바스크립트 라이브러리가 추가되어 있을 것이다. 웹 페이지에서 사용할 수 있도록 추가하는 것이다.

2. <script src="~/signalr/hubs" type="text/javascript"></script> : 서버 코드상에서 Hub를 상속받은 Messages 클래스에 대해서 Generated하여 자바스크립트 코드를 만든었으며 서버에서 추가한 메소드가 자바스크립트에서 실행이 되도록 하고 있다. 자세한 사항은 밑에서 한번더 다루도록 하겠다.

3. var messagesHub = $.connection.messages : SignalR의 자바스크립트 라이브러리가 연결할 Hub 객체를 할당한다.

4. messagesHub.server.send(text) : text 메시지를 서버에 보내준다.

5. messagesHub.client.addMessage : 서버에서 보내준 메시지를 처리하는 이벤트를 설정한다. 여기서는 화면에 보여주는 로직만 코딩되어 있다.


서버측 코드에 대해서 작성할 차례인데 생각보다 간단한 코드로 이뤄진다.

/// <summary> /// 컨트롤러 클래스 /// </summary> public class SignalRTestController : Controller {     //     // GET: /SignalR/     public ActionResult Index()     {         @ViewBag.Message = "SignalR Test";         return View();     } } /// <summary> /// SignalR의 기능을 담당할 Messages, 클라이언트에서 올려준 메세지를 모든 클라이언트에 뿌려주는 역할을 한다. /// </summary> public class Messages : Hub {     public void Send(string message)     {         Clients.All.addMessage(message);     } }

[코드14] SignalR의 서버 사이드 코드


1. SignalRTestController : 단순히 웹 페이지를 보여주기 위한 코드 밖에 없다.

2. public class Messages : Hub : 이 코드가 SignalR의 핵심이다. 여기에서 추가로 코드가 자바스크립트로 Generated로 되어 브라우저에서도 사용할 수 있게 변환이 된다.

3. Clients.All.addMessage(message) : 받은 메시지를 연결된 모든 클라이언트에게 보내주는 코드다.



이제 코드 설명은 다 끝났지만 한간지 의문점이 남게 될 것이다. Generated되어 자동으로 실행이 되도록 한다고 하였지만 무엇이 어떻게 생성이 되고 실행이 되는지는 모른다. 일반적으로는 몰라도 SignalR에서 자동으로 뒷단에서 많은 일들을 하고 있기 때문에 편리하게 브라우저의 HTML5에 제한을 생각하지 않고 편리하게 사용만 하면 된다. 그렇지만 SignalR을 더 많이 이해하기 위해 자바스크립트 소스를 분석하게 되었다. 아래 '코드14'가 브라우저에서 Generated된 자바스크립트 코드다. 너무 길어 일괄적인 코드는 생략하고 중요한 코드만 남겨 두었다. 


[코드15] /signaR/Hubs에서 만들어진 자바스크립트 코드


 자동으로 생성된 코드는 서버에서 Hub를 상속 받은 Messages의 분석하여 Javascript를 만든 것이다. 서버의 'Send' 메소드가 자바스크립트의 send: function (message) { }로 변환된 것을 확인 할 수 있다. 이런 방식으로 서버의 코드를 자바스크립트에서 직접 사용할 수 있도록 Proxy를 만들어서 브라우저에 보내주는 것이다. 이제 모든 코드에 대해서 살펴 보았으니 결과를 확인해 보도록 하자.




[그림13] SingalR 실행 결과 화면 ( 위쪽이 Chrome, 아래쪽이 IE10 )



1. IE에서 메시지를 'IE10에서 보내는 SignalR 메시지' 입력하고 서버에 전송한다. - 실시간으로 크롬에서 확인된다.

2. 크롬에서 메시지를 'Chrome에서 보내는 SignalR 메시지' 입력하고 서버에 전송한다. - 실시간으로 IE에서 확인된다.


위와 같이 실시간으로 보내지는 메시지를 브라우저에서 받아 처리하도록 한다. 여기까지는 WebSocket와 별 다를게 없어 보인다. HTML5의 WebSocket를 지원하지 않는 브라우저를 재현하기 위해 IE의 '관리자 도구'를 이용하여 모드를 하위 버전으로 수정하여 테스트를 진행하여 서버와이 통신이 어떻게 변경이 되는지 확인해 보도록 하겠다.



IE에서 F12로 관리자 모드를 활성화 한다. 이곳에서 네트워크 탭으로 가서 캡춰 시작 버튼을 눌러 네트워크 트래픽이 잡아 내도록 한다. 그리고 브라우저 모드를 IE10에서 IE9으로 변경하면 아래와 같은 결과를 확인 할 수 있다.



[그림14] IE의 관리자 도구 화면


이제부터 브라우저는 IE10이지만 내부적으로는 IE9과 같은 방식으로 동작하게 된다. IE9은 WebSocket를 지원하지 않아 웹 소켓 통신을 하지 못하는데 어떻게 실시간으로 값을 받아 올 수 있을까? 이건 네트워크 트래픽에 잡힌 '/signalr/connect?transport=foreverFrame ...' Url 주소를 보면 해답을 찾을 수 있다. ForeverFrame으로 서버에 계속 연결되어 있는 상태가 되어 서버에서 Flush된 데이터를 브라우저에서 받는 것이다. 그래서 네트워크 결과에 보면 계속 '보류중' 이라고 나오는 것이다. 이렇게 SignalR을 사용하면 WebSocket만을 사용하여 개발하는 것 보다 브라우저의 제약에 좀더 자유로울 수 있게 되고 추상화된 API를 제공하므로 개발의 일관성을 유지할 수 있게 된다.


지금까지 브라우저에서 서버와 통신하는 여러가지 방법중에 대표적인 기술과 최신에 나온 WebSocket 그리고 통신 기술들을 아우르는 추상화 시킨 라이브러리인 SignalR이나 Socket.IO에 대해서 간략하게 나마 알아 보았다. 어떤 기술을 선택하여 개발할지는 각각의 개발 환경이나 제약 사항에 따라 취사 선택을 해야 할 것이다. 이제 부터는 각자 자신의 몫이다. 자신의 것으로 만들고 적용하여 보다 발전된 방향으로 가야하는 짐을 여러분에게 맡기는 것이다. 


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

ing™       




Real time Apps #5 - WebSocket


참조 URL
  1. http://dev.w3.org/html5/websockets/
  2. http://tools.ietf.org/html/rfc6455
  3. http://www.infoq.com/articles/Web-Sockets-Proxy-Servers
  4. http://en.wikipedia.org/wiki/Protocol_overhead
  5. http://html5please.com/
  6. https://github.com/gimite/web-socket-js - WebSocket polyfill library
  7. http://weblogs.asp.net/dwahlin/archive/2013/04/13/building-an-html5-web-sockets-server-with-asp-net-4-5.aspx
  8. http://jsperf.com/choosing-websocket-vs-mozwebsocket
  9. https://developer.mozilla.org/en-US/docs/WebSockets
  10. https://developer.mozilla.org/ko/docs/WebSockets

 

목차
  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™       


 이제까지 단 방향 통신에 대해서 살펴 봤다면 지금 부터는 양 방향 통신을 지원하는 WebSocket에 대해서 알아 보고자 한다. 표준 WebSocket의 API는 W3C에서, 프로토콜은 IETF(Internet Engineering Task Force)에서 HTML5의 하위로 재정을 하고 있으며 지금도 현재 진행형이다. 그리고 WebSocket은 HTTP와 마찬가지로 80번 포트를 통해 웹 서버에 연결한다. HTTP 프로토콜의 버전은 1.1이지만 아래 헤더에서 보듯이 Upgrade 헤더를 이용하여 웹 서버에 접속한다. 


[HTTP 헤더 1] Client to Server WebSocket 헤더 정보



[HTTP 헤더 2] Web Server to Client 헤더 정보


[HTTP 헤더 3] HTTP Connect 메소드 헤더 정보


 브라우저는 "Upgrade: WebSocket" 헤더와 함께 랜덤하게 생성한 키를 서버에 보내고 웹 서버는 이 키로 토큰을 생성하여 브라우저에 돌려준다. 위 헤더 정보는 WebSocket지원 버전에 따라 헤더 정보는 달라 질 수 있다. 이런 방식으로 handshaking을 하여 WebSocket을 연결하면 Protocol Overhead 방식으로 웹 서버와 브라우저가 WebSocket 통신을 하게 된다. Protocol Overhead 방식은 메타 데이터와 네트워크 라우팅 기법으로 여러 TCP 커넥션을 생성하지 않고 하나의 80번 포트의 TCP 커넥션을 사용하여 연결하고, 별도의 헤더로 논리적인 데이터 흐름 단위를 사용하여 여러 개의 가상 커넥션을 사용하는 효과를 가져오는 방식이다. ( Protocal Overhead는 일종의 공유기라 생각하면 쉬울것이다. 하나의 공인 IP를 가지고 내부적으로 여러대의 내부 IP에서 나눠 사용하듯이 말이다.) 이런 방식으로 HTTP와 같은 포트를 사용하여 방화벽이 있는 환경에서도 별도의 설정 없이도 WebSocket을 사용 할 수 있도록 지원하고 있다.


 이제까지의 내용을 간단한 도식으로 WebSocket이 웹 서버와 통신하는 흐름을 '그림6'를 통해 간략적으로 살펴 보자.

[그림6] WebSocket 통신 방법


1. Client에서 서버에 연결 시도 한다. 

2. 연결 시 헤더에 Upgrade정보를 보내며 서버와 Handshaking을 하여 연결 한다.

3. 서버에서 발생된 이벤트가 있으면 WebSocket을 통해 브라우저에 보낸다.

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


위 순서의 2번에 대해서  '그림7'과 같이 도해식으로 좀더 자세히 살펴 보자.

[그림7] WebSocket 도해식


1. Client가 서버에게 요청을 하면서 헤더에 Upgrade, WebSocket.Version, Key... 등을 같이 보내준다.

2. 서버는 자신이 응답할 수 있는 웹소켓이고 버전이 맞으면 Token을 내려 보낸다.

3. 클라이언트는 이 토큰을 가지고 웹 서버와 Protocal Overhead 방식으로 통신을 하게 된다.


 이런 방식으로 브라우저와 웹 서버가 뒷단에서 수행하여 웹 소켓 연결이 되도록 한다. 이제 준비가 되었으니 가볍고 빠른 방식으로 웹 서버에서 필요한 데이터를 주고 받으면 될 것이다.


 이제 클라이언트를 담당하는 브라우저에 대해서 점검해 봐야 할 것이다. 그렇지만 아직까지 모든 브라우저에서 지원하는 방식은 아니다. 아래 '그림8'와 같이 WebSocket를 지원하는 브라우저를 확인 해 보자.


[그림8] WebSocket 지원 브라우저

http://caniuse.com/#feat=websockets 참조 )


 IE는 10 이상부터 지원이 되고 있으며 파이어 폭스(19+)나, 크롬(25+)도 지원하고 있다. IE는 비교적 가장 최신 버전에서만 지원하고 있으므로 개발할 때 염두에 두고 진행해야 할 것이다. 한국의 제한된 시장에서는 아직도 IE 10 이하 버전을 사용하고 있는 사용자들이 많기 때문에 개발시 검토를 잘 하고 진행해야 할 것이다. 하지만 후에 검토할 signalR( 또는 Socket.IO)을 통해서 브라우저 호환성을 극복하여 브라우저마다 다른 호환 범위를 하나의 프로그래밍 개발 접근 방식으로도 해결할 수 있는 방법이 있으니 너무 일찍 좌절감을 맞보지 않기를 소망한다. 희망을 가지고 코드 사이드에서 WebSocket를 진행하도록 하겠다.


 아래 '코드7'는 브라우저에서 WebSocket의 지원하는 메소드 및 간단한 코드 조각이다. (이 코드를 확장하여 개발 범위에 맞게 가감하여 사용하면 될 것이다.)


[코드7] 브라우저에서 WebSocket을 사용하는 메소드 예제


 클라이언트에서의 웹 소켓과 관련된 코드는 '코드7'이 전부라고 알고 있으면 되겠다. 이 작은 코드가 웹 소켓에서 발생하는 모든 이벤트를 보여주고 있다. 이제 이 작은 코드 조각으로 웹 서버를 통해 어떻게 응용이 되는지 살펴 보자. 아래 '코드8'은 .Net 진영에서 제공되는 ASP.NET MVC와 Razor 뷰 문법으로 구성되었으며 서버 사이드의 자체 문법 보다는 전체적인 흐름을 파악하는데 중점을 두고 코드를 살펴 보기를 바란다.


<!-- WebSocket으로 받은 데이터를 반영할 수 있는 DOM --> <ol id="messages"></ol> @using (Ajax.BeginForm("PostMessage"new AjaxOptions())) {      <p>         Say : @Html.TextBox("messageText")         <input type="submit" />      </p> } @section scripts{     <script type="text/javascript">         $(function () {             $("form").submit(function () {                 var form = $(this);                 var txtMessageText = $(form.find('#messageText'));                 $.post('@Url.Action("PostMessage")', { 'messageText': txtMessageText.val() }, function () {                     txtMessageText.val('');                     txtMessageText.focus();                 });                 return false;             });             start();         });         var start = function () {             var wsImpl = window.WebSocket || window.MozWebSocket;             // 웹 소켓 초기화             window.ws = new wsImpl('ws://localhost:8181/', 'my-protocol');             ws.onmessage = function (evt) {                 $('<li>').text(evt.data).appendTo('#messages');             };             ws.onopen = function () {                 $('<li>').text('웹 소켓 연결 되었습니다.').appendTo("#messages");             };             ws.onclose = function () {                 $('<li>').text('웹 소켓이 닫혔습니다.').appendTo("#messages");             }         }     </script> }

[코드8] WebSocket의 브라우저에서 실행될 클라이언트 사이드 코드


1. @using (Ajax.BeginForm("PostMessage", new AjaxOptions())) : form 테그를 완성하는 Razor 문법이다. 여기에서 '@' 구문은 Razor 문법의 시작을 알리는 예약어다.

2. $(function () { $("form").submit(function () { : 는 1번에서 선언된 테그에서 발생하는 submit 이벤트를 받아서 페이지 이동을 하지 못하도록 하고 사용자가 입력한 값을 ajax로 통신하여 서버에 submit하도록 한다.

3. var wsImpl = window.WebSocket || window.MozWebSocket : 웹 소켓 객체를 생성한다. ( https://developer.mozilla.org/en-US/docs/WebSockets 사이트에서 보면 Gecko 6.0에서 개명되어 WebSocket와 MozWebSocket이 일부 브라우저와 버전에 따라서 공전할 수 있기 때문에 사용한 코드다 ) 

4. window.ws = new wsImpl('ws://localhost:8181/', 'my-protocol') : 서버에서는 8181 포트로 초기화를 할 것이기에 이포트를 설정하였고 뒤에 인자는 임의의 값으로 설정하면 된다. 이 코드를 통해 웹 서버와 통신할 수 있도록 초기화를 한것이다. 이 부분에서 서버와의 통신은 80번으로 하지만 내부적으로는 8181 포트를 사용하는 것처럼 동작하게 되는 것이다.

5. onmessage, onopen, onclose : 이름으로 유추할 수 있듯이 서버에서 보내주는 메세지가 있거나 상태가 변경이 되면 해당 function이 실행이 되도록 이벤트를 연결한 것이다. 이벤트가 발생되어 실행이 되면 messages의 id를 가진 '<ol id="messages"></ol>' 테그의 하위 노드에 li가 추가되면서 메시지가 보여질 것이다.




var wsImpl = window.WebSocket || window.MozWebSocket;


여담이지만 위의 작은 코드에도 성능 향상을 기법이 들어가 있다. 전역 객체로 할당 받는 것과 지역 객체로 할당받는 것에 대해서 속도 측정을 해 놓은 사이트가 있다. 'http://jsperf.com/choosing-websocket-vs-mozwebsocket'에서 Performance를 직접 측정할 수 있다.


아래 코드에 대해서 측정할 수 있으며 'Run Test' 버튼을 통해 측정 한다.


1. window.WebSocket = window.WebSocket || window.MozWebSocket;

2. var WebSocket = window.WebSocket || window.MozWebSocket;


Chrome에서 1번 코드와 2번 코드의 성능에 대해서 측정을 하면 아래와 같은 결과가 나오고 있다.
(측정값은 브라우저와 컴퓨터 성능에 따라 다른 결과가 도출 될 수도 있다.)






 이제 클라이언트인 브라우저 코드를 살펴 보았다면 서버측 코드를 살펴 보자. 살펴 보기에 앞서 아래 코드는 MVC 패턴의 Controller 역할을 맡은 소스 코드라는 것을 염두에 두고 살펴보기 바란다.



코드를 작성하기 전에 프로젝트 솔루션에 추가해야 하는 외부 참조 Dll이 있다. 

'https://github.com/statianzo/Fleck'에서 진행되는 웹 소켓 프로젝트이며 관련 소스를 다운 받아 분석할 수 있다. 그렇지만 간단하게 사용하거나 아래 소스코드를 실행해 보기를 원한다면 NuGet를 통해서 'Fleck'로 검색 후 추가하면 된다.


#region Fields
/// <summary>
/// 웹 소켓 초기화시 중복 초기화가 되지 않도록 잠금 역할하는 객체
/// </summary>
private static object webSocketLock = new object();
 
/// <summary>
/// 연결된 클라이언트들의 커넥션 리스트 객체
/// </summary>
private static List<IWebSocketConnection> allSockets = null;
 
/// <summary>
/// 웹 소켓 호스트 역할 객체
/// </summary>
private static WebSocketServer server = null;
#endregion
 
/// <summary>
/// 생성자
/// </summary>
public WebSocketController()
{
    #region WebSocket 초기화
    if (allSockets == null)
    {
        lock (webSocketLock)
        {
            if (allSockets == null)
            {
                allSockets = new List<IWebSocketConnection>();
                server = new WebSocketServer("ws://localhost:8181");
 
                InitWebsocket();
            }
        }
    }
    #endregion
}
 
public ActionResult Index()
{
    @ViewBag.Message = "WebSocket Test";
    return View();
}
 
/// <summary>
/// 메세지 받아서 waiting 풀어 주기
/// </summary>
/// <param name="messageText">입력 받은 값</param>
public void PostMessage(string messageText)
{
    // 웹 소켓에 받은 메세지를 보내주기
    foreach (var socket in allSockets)
    {
        socket.Send(messageText);
    }
}
 
/// <summary>
/// 웹 소켓 초기화
/// </summary>
[NonAction]
public void InitWebsocket()
{
    server.Start(socket =>
    {
        // 웹 소켓 연결 할 때
        socket.OnOpen = () =>
        {
            allSockets.Add(socket);
        };
 
        // 웹 소켓이 닫을 때
        socket.OnClose = () =>
        {
            Console.WriteLine("Close!");
            allSockets.Remove(socket);
        };
 
        // 웹 소켓에서 메세지 전송 할 때
        socket.OnMessage = message =>
        {
            allSockets.ToList().ForEach(s => s.Send("Echo: " + message));
        };
    });
}

[코드9] WebSocket의 서버 사이드 코드


1. private static object webSocketLock = new object() : 쓰레드에서 동기화를 구현하기 위한 객체이며 여러개의 쓰레드에서 하나의 자원을 동시 접근시 순차적으로 접근이 가능하도록 Lock를 걸어주는데 사용한다.

2. private static List<IWebSocketConnection> allSockets = null : 하나의 WebSocket 연결을 관리 하는 리스트 객체다. 이 객체에는 클라이언트를 구분하고 그룹을 짓고 메세지를 보내는 역할을 하는 리스트를 나타낸다.

3. private static WebSocketServer server = null : 실질적으로 WebSocket의 핵심 로직이 들어간 서버 객체다. 이 객체가 클라이언트와 WebSocket 방식으로 통신을 한다.

4. new WebSocketServer("ws://localhost:8181") : 웹 소켓을 localhost의 8181 포트로 동작하도록 한다. - 프로토콜 오버헤드 방식으로 동작하므로 외부에서는 80으로 접속하지만 내부적으로는 8181포트로 동작한다.

5. PostMessage(string messageText) : 브라우저에서 submit시 받아 처리하는 코드로 메시지를 받으면 접속된 모든 클라이언트에 동일한 메시지를 전달하도록 되어 있다.

6. [NonAction] InitWebsocket() : NonAction은 브라우저에서 직접적으로 InitWebsocket를 접근 하지 못하도록 하는 속성이다. 

7. socket.OnOpen = () => { allSockets.Add(socket); } : WebSocket이 브라우저와 handshaking이 이뤄지고 나서 연결이 될 때 발생되는 이벤트다. 접속한 브라우저의 연결된 정보를 관리 하도록 리스트에 저장한다.

8. socket.OnClose = () => { allSockets.Remove(socket); } : 연결된 WebSocket이 닫히면 관리되는 리스트에서도 제거한다.

9. socket.OnMessage = message => {  } : 관리되는 리스트에게 같은 메세지를 일괄적으로 클라이언트에게 웹소켓으로 보내준다.



 이제 전체 소스를 살펴 보았으니 '그림9'을 통해 결과를 확인 해 보도록 하자.



[그림9] WebSocket 결과 화면 ( 위쪽이 Chrome, 아래쪽이 IE10 )



1. 처음 Chrome 브라우저를 통해 접속을 하고 '첫번째 웹 소켓 통신'을 입력하고 버튼을 눌러 서버에 전송 하였다.

2. IE10으로 같은 주소를 접속하고 'IE에서 보내는 웹 소켓 메시지'를 입력하고 버튼을 눌러 서버에 전송 하였다.

    - Chrome에서도 동일한 메시지가 실시간으로 확인 할 수 있다.

3. Chrome에서도 'Chrome에서 보내는 웹 소켓 메시지'를 입력하고 버튼을 눌러 서버에 전송 하였다.

    - IE에서도 동일한 메시지가 실시간으로 확인 할 수 있다.



 이번에는 WebSocket의 통신 흐름 잡아 내 보도록 하자.  아래 '그림10'처럼 F12를 눌러 개발자 도구를 활성화 시키고 'WebSocket'를 선택하고 브라우저에서 값을 입력하여 서버에 보내면 웹 소켓 통신으로 받은 패킷을 아래처럼 잡을 수 있다. 


[그림10] Chrome에서 WebSocket의 패킷을 잡아낸 그림


 이 방법으로 웹 소켓과의 통신을 추적할 수 있게 되었다.



이제 웹 소켓에 대해서 대략적으로나마 살펴 보았다. 생각보다 코드의 양은 많지 않았지만 생소할 수도 있어서 코드 주석과는 별개로 밑에 세부 설명을 추가 하였으니 좀더 쉽게 이해가 되었으면 하는 바램입니다. 눈으로만 지나치시지 마시고 한번씩이라도 실습을 통해 나의 경험으로 체득하심을 극히 권해 드리며 프로그램 만으로도 행복할 수 있는 세상이 오기를 바라며 이번 포스트를 마칩니다.



 다음에 소개해 드릴 signalR은 지금보다 더 쉽고 편한 방법으로 개발 할 수 있는 라이브러리에 대해서 알아 보도록 하겠습니다. 흥미로운 signalR을 기대해 주세요~



 Guillermo Rauch가 만든 Socket.IO( http://socket.io/ )는 WebSocket, FlashSocket, Ajax Long Polling, Ajax Multipart streaming, Forever Iframe, JSONP Polling을 하나의 API로 추상화 해서 제공하고 있다. 즉 브라우저와 웹 서버의 종류를 파악하여 가장 적합한 기술을 자동 선택하여 사용하는 방식을 제공한다. 간다히 케이스를 정하자면 WebSocket를 지원하지 않는 브라우저에서도 상관 없이 같은 개발 방식으로 개발할 수 있도록 지원한다는 것이다. 그렇지만 아직까지는 Node.js 플랫폼 위에서만 동작하는 제약이 있다.


[코드10] Socket.IO in Browser


[코드11] Socket.IO on Node.js


 이로써 Socket.io를 사용할 준비는 모두 끝났다. 브라우저에서 페이지를 열면 콘솔에는 "{ server : 'hello world!' }"가서버 콘솔에는 "{ browser : 'data' }"가 출력되는 것을 확인할 수 있을 것이다. Socket.io에 대해서 깊게 알고 싶다면 "http://socket.io/"에서 가셔서 확인할 수 있을 것이다. 물론 Windows에서도 IISNodehttps://github.com/tjanczuk/iisnode ) 모듈을 통해서 node에 대한 개발 및 테스트를 할 수 있다.






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

ing™       



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™       



Real time Apps #3 - LongPolling


참조 URL
  1. http://techoctave.com/c7/posts/60-simple-long-polling-example-with-javascript-and-jquery
  2. http://tkjeon.tistory.com/entry/Jquery-Jquery-폴링Polling-기법
  3. http://msdn.microsoft.com/ko-kr/library/dd449174.aspx - TaskCompletionSource<TResult>


목차
  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™       


 이번에는 LongPolling에 대해서 알아 보자. 좀전에 Polling에 대해서 알게 되었다면 별로 어렵지 않게 이해를 할 수 있을 것으로 예상한다. 그냥 Polling은 주기적으로 서버에 요청하는 방식이지만 LongPolling 방식은 한번 요청하고 서버에 데이터가 있을때만 반환받는 방식이다. 아래 '그림3'으로 보면 좀더 쉽게 이해할 수 있을 것이다.


[그림3] LongPolling 방식


 1. Request1로 서버에 요청하고 서버에서 Event( or Data )가 발생되면 해당 형식( 여기서는 Message라고 정의 )을 브라우저에 보내주고 브라우저 커넥션은 끊어진다.

 2. 받은 데이터를 처리하고 곧바로 서버에 Request2를 요청하고 데이터가 발생할 때까지 대기한다.

 3. 1단계를 다시 시작한다. 


 이와 같은 단계로 LongPolling 방식이 구동이 된다. Polling와 LongPolling 방식은 전체적으로 비슷한 방식을 개발할 수 있으나 다른점은 'LongPolling'이라는 이름처럼 길게 대기하여 서버에 응답요청을 한다는데 있다. 길게 기다리다가 "있으면 보내줘!"와 같은 컨셉이다. 이제 실제 코드로 알아가 보자.


<!-- Polling 하여 받은 데이터를 반영할 수 있는 DOM -->
<ol id="messages"></ol>
 
@using (Ajax.BeginForm("PostMessage"new AjaxOptions())) { 
    <p>
        Say : @Html.TextBox("messageText")
        <input type="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{ <script type="text/javascript">     $(function () {         // Begin the LongPolling loop         getNextMessage();         // Form의 Submit 이벤트  통제         $("form").submit(function () {             var form = $(this);

            // messageText의 Html tag에 입력된 값을 가져온다.              var txtMessageText = $(form.find('#messageText'));

            // Ajax로 서버에 입력한 값을 넘겨준다.             $.post('@Url.Action("PostMessage")', { 'messageText': txtMessageText.val() }, function () {                 txtMessageText.val('');                 txtMessageText.focus();             });                              // HTML의 Submit가 일어나지 않도록 한다.             return false;         });     });     // 서버에 메시지가 있는지 문의 요청     function getNextMessage() {         $.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>
public async Task<string> GetNextMessage()
{
    return await _nextMessage.Task;
}
 
/// <summary>
/// Lock object
/// </summary>
static object _nextMessageLock = new object();

/// <summary>
/// for LongPolling object
/// </summary>
static TaskCompletionSource<string> _nextMessage = new TaskCompletionSource<string>();
 
public void PostMessage(string messageText)
{
    lock (_nextMessageLock)
    {
        // Concurrent 해결 하기 위해 임시 변수에 옮긴다.
        var oldNextMessage = _nextMessage;
        _nextMessage = new TaskCompletionSource<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

ing™       




Real time Apps #2 - Polling


참조 URL
  1. http://reallifejs.com/brainchunks/repeated-events-timeout-or-interval/ - setTimeout vs setInterval
  2. http://en.wikipedia.org/wiki/Comet_(programming) - COMET programming
  3. http://blog.falafel.com/blogs/basem-emara/2012/06/06/polling-ajax-requests-in-javascript

  

목차
  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™       


 Polling에 대해서 알아보기 전에 우선은 Ajax와 인터랙티브 웹 페이지에 대해 간략히 알아 보자. Ajax 통신은 브라우저에서 제공해주는 특별한 객체를 통해(IE - new ActiveXObject("Microsoft.XMLHTTP"), Other - new XMLHttpRequest()) 사용자의 행위에 따라 페이지의 새로 고침 없이 Background에서 서버에서 원하는 데이터를 가져와  페이지의 일부분만을 수정할 수 있는 메커니즘을 말한다. 이런 개발 방식으로 개발된 웹 페이지를 접한 사용자들은 기존의 페이지에서는 느낄 수 없었던 사용자 경험을 얻을 수 있었다.  이 후 수많은 사이트에서 이 개발 방식으로 웹 페이지를 제공하게 되었다. 이런 개발 방식이 조금 더 응용되어 Polling 기법으로 사용자의 행위 없이도 서버에서 변경된 정보를 실시간 - 사용자의 입장에서는 실시간이나 개발자 입장에서는 실시간 지향적 이라 하겠다 - 적으로 업데이트를 할 수 있게 되었다. 간단하게 예를 들면 브라우저에서 서버에게 주기적으로 "데이터가 있어?"라고 물어 보고 서버에서 데이터의 여부에 따라 다르게 대답 하는 것이라 하겠다. 브라우저에서는 받은 데이터로 웹 페이지의 일부분만을 업데이트하여 사용자에게 보여주므로 빠른 응답을 사용자에게 경험하게 할 수 있다. 대표적인 애플리케이션은 웹 채팅이다. 상대방에서 보낸 메시지 여부를 주기적인 Polling으로 알아내어 사용자에게 보여주는 것이다.


Polling의 사전적인 의미를 알아보자. N사이트의 국어사전의 뜻을 보면 다음 표와 같이 풀이하고 있다.


<컴퓨터> 컴퓨터 또는 단말 제어 장치 따위에서 여러 개의 단말 장치에 대하여 순차적으로 송신 요구의 유무를 문의하고요구가 있을 경우에는  단말 장치에 송신을 시작하도록 지령하며없을 에는 다음 단말 장치에 대하여 문의하는 전송 제어 방식. 

[표3] 폴링에 대한 국어사전 풀이


 이 뜻풀이처럼 데이터가 필요한 곳에서 제공자에게 송신을 요구하는 패턴을 Polling 방식이라고 할 수 있겠다. 즉 원하는 곳에서 당겨가는 방식이다. 그렇다면 웹 페이지와 서버 간에는 어떻게 Poll이 이루어지고 있는지를 아래 '그림1'을 통해서 전체적인 흐름을 알아 가 보자.

[그림1] Polling 방식


 웹 페이지에서 Polling을 구현하는 방식은 setTimeout() 함수를 이용해 주기적인 시간 간격으로 서버에 요청하여 원하는 정보를 얻어오도록 하고 있다. 

  1. '그림1'에서와 같이 첫번째 요청처럼 브라우저에서 Ajax로 'Request1'을 요청하면 서버에서는 유효한 데이터가 있는지 조회하고 없으면 개발자가 정의한 형식(여기서는 Empty라고 정의)으로 리턴받고 브라우저 커넥션은 끊어진다.

  2. setTimeOut()함수로 'Request2'요청이 발생할 당시 서버에서 이벤트가 발생하여 유효한 데이타가 있으면 Empty와 대신 개발자가 정의한 형식(여기서는 Message라고 정의)으로 리턴받고 브라우저 커넥션은 끊어진다.

  3. 1단계를 다시 시작하여 서버에서 반영된 유효한 값을 브라우저에 업데이트 한다.

 위와 같은 방식으로 서버의 변경된 데이터를 브라우저에 지속적으로 반영하여 실시간 반응을 지향하는 웹 페이지를 만들었다. 


 이제 예제를 통해서 실제적인 코드로 살펴 볼 것이다. ( 실행해 볼 수 있는 소스코드를 마지막 포스트에서 첨부할 것 이니 다운받아 확인할 수 있습니다. ) 

<!-- Polling 하여 받은 데이터를 반영할 수 있는 DOM -->
<ol id="messages"></ol>

@section scripts{ <script type="text/javascript">     $(function () {         // Begin the Polling loop         getNextMessage();     });     function getNextMessage() {         // Http post method request

//@Url.Action("GetNextMessage") -> /현재 페이지/GetNextMessage로 치환된다         $.post("@Url.Action("GetNextMessage")", function (message) {             $("<li>").text(message).appendTo("#messages");         });         // Http get method request         //$.get("@Url.Action("GetNextMessage")", function (message) {         //    $("<li>").text(message).appendTo("#messagees");         //});         setTimeout(getNextMessage, 1000);     } </script> }

[코드1] Polling - Browser의 HTML과 javascript 코드

( ASP.NET MVC4의 Razor 문법으로 작성된 코드다. )


 위 코드는 페이지가 로딩이 되면 getNextMessage() 함수를 호출하여 서버에 데이터를 요청하고 <ol> 노드에 <li> 노드를 추가하는 코드다. ( 서버 요청을 HttpPost 또는 HttpGet으로 할 수 있다 ) 그리고 setTimeout를 통해 1초후에 다시 getNextMessage()를 호출하도록 되어 있다. 별도의 문제나 행위가 발생하지 않는 한 무한 호출이 되는 단순한 구조로 구성 되어 있다. 이제 아래 코드를 통해 서버쪽의 코드를 알아 보자.

 

/// <summary>
/// 요청이 오면 즉시 응답
/// </summary>
/// <returns></returns>
public string GetNextMessage()
{
    int waitingCount = 0;
    var now = DateTime.Now;
    return "Response at " + now.ToString() + "." + now.Millisecond + " 서버 대기 시간 : " + waitingCount + "ms";
}

[코드2] Polling - Server side 코드

( 서버 사이트 코드는 ASP.NET MVC4 기반에서 작성이 되었다. )


 위 코드는 따로 설명을 할 필요도 없을 정도로 간단하여 짧게 설명을 하도록 하겠다. GetNextMessage()함수는 호출이 되면 현재 시간을 알아내 string으로 반환하도록 되었다. 이제 위 결과 화면을 아래 그림을 통해 확인해 보자.


[그림2] Realtime Apps - Polling 실행 결과 화면

( 위 화면은 Visual studio에서 ASP.NET MVC4 템플릿에서 기본 제공되었다.)



  '그림2'의 결과 화면을 보면 1초마다 호출된 결과를 화면에 뿌려주는 것을 확인할 수 있다. 이제 코드와 결과 화면까지 확인을 하였다. 이제 Polling이 어떤 방식으로 구성되고 개발되는지 대략적으로 알 수 있을 것이라고 기대한다. 위 예제는 MS 진영에서 개발 및 테스트를 할 수 있지만 Java나 PHP, 기타 Ruby와 같은 곳에서도 비슷한 방식으로 구현이 될 것이므로 이해 하는 데에 크게 무리가 없을 것이라 생각한다.


Tip!

 Request가 지속적으로 일어 날 때마다 서버에서는 유효한 사용자의 요청인지를 알아내기 위해 내부적으로 HTTP Handshanking이 지속적으로 일어나기 때문에 오버헤드가 발생한다. 그래서 예측할 수 없는 웹 서비스를 제공하는 서버에서는 성능에 대한 고려를 충분히 해야 한다.


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

ing™       



Real time Apps #1
Polling, LongPolling, Server Sent Events, Websocket, SignalR


참조 URL
  1. http://en.wikipedia.org/wiki/Comet_(programming) - 위키피디아

 실시간 인터랙티브 웹 페이지(또는 앱, 이하 웹 페이지로 통일)에 대해서 익히 들어 봤을 것이다. 사용자의 행동(클릭, 스크롤,...)으로 웹 브라우저에서 서버의 변경된 데이터를 사용자에게 보여 줄 수 있는 웹 페이지 말이다. 사용자의 발전되는 경험으로 인해 오래전부터 인터랙티브형 웹 페이지에 대해 요구되고 있었으며, Ajax 방법론이 대두 되면서 사용자가 만족할 만한 인터랙티브 웹 페이지를 만들 수 있었다. 그렇지만 앞으로의 개발 방향이 기존의 레거시 시스템을 웹을 지원하는 방향으로 흘러가고 있으며 Active-X, Flash, Silverlight를 제외 하고 표준 HTML 과 CSS, Javascript 만으로 C/S 프로그램이 하던 일을 대체해야 한다는 시대적 흐름이 형성 되었다. 그리고 시대적으로 새로운 디바이스(이기종 스마트폰, 이기종 타블렛, Other...)가 출시되었으며 새로운 규격의 브라우저에서도 호환되게 개발해야 한다. 개발자는 이러한 여러 가지 제약 사항을 이겨내고 만족 스러운 인터랙티브형 웹페이지를 구현하기란 쉽지 않다. 

 그렇지만 개발자들은 고객의 요구 사항을 최대한 만족 시키기 위해 부단히 노력하여 여러 가지 시도로 인터랙티브형 웹 페이지가 되도록 노력하였다. 그래서 Polling, LongPolling등과 같은 방법으로 인터랙티브형 웹 페이지가 되도록 개발하게 되었다. 하지만 처음 설계될 당시의 HTTP 프로토콜과 HTML의 한계성을 가지고 있기 때문에 개발자는 뒤에서 컨트롤하고 사용자에게는 감추는 역할을 더 많이 신경 쓰게 된 것이다. 세계의 여러 개발자들이 이런 문제점을 개선하고자 HTML5와 ECMAScript 6(ECMAScript는 표준화된 스크립트 프로그래밍 언어를 말한다. - 위키피디아 click) 표준이 정립이 되고 있으며 Windows, Linux 같은 또 다른 하나의 개발 플랫폼으로 HTML이 진화를 하고 있다.


 HTML5에 대해서 좀더 이야기를 하고 싶으나 주제에 벗어나므로 간단히 정리하고자 한다. HTML5를 짧게 수식 같이 정리하면 아래 '표1'과 같을 것이다.


HTML5 = Javascript + CSS + Templates 

[표1] HTML5 정의 표

 (이것은 개인적인 정의일 뿐 W3C 공식적인 정리는 아님을 알려 드린다.)


 Javascript와 CSS에 대해서는 대부분 알 고 있을 것이라 생각하고 Template에 대해서만 더 설명을 더 하겠다.


 Javascript 

 사용자의 행위에 대해서 처리

 CSS 

 HTML의 레이아웃 및 디자인

 Template

 HTML Tag의 유동적인 데이터를 템플릿 엔진을 통해 DOM(Document  Object Model) 관리가 되도록 한다. Template engine은 JsRender(구 jquery-template), HandleBars와 같이 여러 라이브러리를 통해 얻을 수 있으며 HTML의 대부분 작업을 담당한다.


 간단하게나마 HTML5에 대해서 마무리를 하고 이제 본론으로 돌아와 인터랙티브 웹 페이지를 가능 토록하는 방법 중에 서버와의 통신으로 데이터를 가져오는 기법이란 주제로 좁혀 다루고자 한다. 아래 목차대로 하나씩 짚어 나가면서 진행해 보도록 하겠다.


목차
  1. Polling - 있어? : [ASP.NET MVC] - Real time Apps - Polling
  2. LongPolling - 있으면 보내줘! : [ASP.NET MVC] - Real time Apps - LongPolling
  3. SSE ( Server Sent 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



 본격적으로 살펴보기에 앞서서 아래 '표1'과 같이 데이터 통신에 관한 기술 정리를 간단하게나마 해 보았으니 참조만 하자.


기술 규격

기술 명칭

COMET

 Polling

 LongPolling

 Hidden iframe

 XMLHttpRequest

 XMLHttpRequest long polling

 Script tag long polling

HTML 5

 SSE(Server Sent Events - Streamming) - Not Socket

 WebSocket - HTML5

 SignalR - hybrid

 Socket.IO - hybrid

※ hybrid : 하위 브라우저 호환을 지원하는 기능으로 WebSocket를 지원하지 않는 브라우저에서는 다른 하위 통신 방식으로 데이터 요청을 지원해준다.

[표2] 통신 방식


+ Recent posts