Silverlight

Silverlight를 사용한 기간 업무(LOB) 엔터프라이즈 응용 프로그램 구축, 1부

Hanu Kommalapati

이 기사에서는 다음 내용에 대해 설명합니다.

  • Silverlight 런타임 환경
  • Silverlight 비동기 프로그래밍
  • 도메인 간 정책
  • 샘플 엔터프라이즈 응용 프로그램
이 기사에서 사용하는 기술:
Silverlight 2

코드는 MSDN 코드 갤러리에서 다운로드할 수 있습니다.
온라인으로 코드 찾아보기

목차

Silverlight의 기본: CoreCLR
Silverlight 런타임
응용 프로그램 시나리오
소켓 서버를 사용한 푸시 알림
Asynchronous I/O Loops
Silverlight의 모달 대화 상자
푸시 알림 구현
TCP 서비스의 도메인 간 액세스
TCP 서비스를 사용한 도메인 간 정책
요약

필자는 최근 휴스톤에서 엔터프라이즈에 대한 Silverlight의 비즈니스 영향을 주제로 경영진 브리핑을 했었지만 반응은 미온적이었습니다. DeepZoom, Picture-In-Picture 및 HD 품질 동영상, 그리고 고품질 애니메이션에 대한 멋진 데모를 선보이면 뜨거운 반응을 얻을 것이라고 예상했었기 때문에 다소 의외였습니다. 그래서 참여자들에게 관심이 적었던 이유를 묻는 설문을 진행하여 알아낸 것은 현란한 그래픽 효과는 좋았지만 Silverlight를 사용하여 엔터프라이즈급, 데이터 중심의 기간 업무(LOB) 응용 프로그램을 작성하는 데 대한 현실적인 안내가 부족했다는 것을 알게 되었습니다.

현재 엔터프라이즈급 응용 프로그램은 종종 인터넷을 통해 네트워크 경계를 통과하여 LOB 정보를 안전하게 전달할 수 있어야 하며 역할 기반 UI를 제공해야 하고 비즈니스 컨텍스트에 따라 데이터 잘라내는 기능을 요구합니다. 클라이언트에서 Silverlight를 실행하고 서버에서 Microsoft .NET Framework 3.5를 실행하면 이렇게 확장성이 우수하며 안전한 LOB 응용 프로그램을 구축하기 위한 훌륭한 기능이 제공됩니다. 샌드박스 내에서 실행되는 간단한 Silverlight 런타임은 백 오피스 데이터 서비스와의 통합을 위한 프레임워크 라이브러리를 제공합니다. 설계자와 개발자가 Silverlight를 사용하여 강력한 응용 프로그램을 작성하기 위해서는 Silverlight 프로그래밍 모델과 현실적인 응용 프로그램의 컨텍스트와 관련된 프레임워크의 기능에 대해 이해해야 합니다.

이 기사의 주 목표는 LOB 시나리오를 제시하고 처음부터 응용 프로그램을 작성하면서 그 과정에 Silverlight 개발의 다양한 측면을 설명하는 것입니다. 여기에서 설명할 솔루션은 콜센터 응용 프로그램이며 그림 1에 논리 아키텍처가 나와 있습니다. 이번 칼럼에서는 화면 팝업 알림, 비동기 프로그래밍 모델, Silverlight 대화 상자, 그리고 도메인 간 TCP 정책 서버 구현을 집중적으로 살펴볼 것입니다. 그리고 2부에서는 응용 프로그램 보안, 웹 서비스 통합, 응용 프로그램 분할, 그리고 응용 프로그램의 몇 가지 다른 측면을 살펴볼 것입니다.

그림 1 Silverlight 콜센터 논리 아키텍처

Silverlight의 기본: CoreCLR

시작하기에 앞서 Silverlight의 기본을 다시 확인해 보겠습니다. 먼저 Silverlight를 사용하여 어떠한 일이 가능한지 이해할 수 있도록 Silverlight 런타임의 내부를 살펴보겠습니다. CoreCLR은 Silverlight에서 사용되는 가상 컴퓨터입니다. .NET Framework 2.0 이상을 구동하는 CLR과는 비슷하다고 할 수 있으며 비슷한 형식 로딩 및 GC(가비지 수집) 시스템을 포함합니다.

Silverlight는 응용 프로그램 수준에서만 보안 정책을 적용하면 되므로 CoreCLR은 데스크톱 CLR에 비해 단순한 CAS(코드 액세스 보안) 모델을 가지고 있습니다. 플랫폼 독립적 웹 클라이언트이므로 특정 엔터프라이즈 또는 시스템 정책을 사용할 수 없고 사용자가 기존 정책을 변경하는 것을 허용하지 않아야 하기 때문입니다. Silverlight가 사용자의 명시적인 동의를 얻어 샌드박스 기본 규칙 집합을 위반하는 OpenFileDialog 및 IsolatedStorage(스토리지 할당량 변경)와 같은 몇 가지 예외도 있습니다. OpenFileDialog는 파일 시스템에 액세스하는 데 사용되며 IsolatedStorage는 이름이 의미하는 것처럼 격리된 저장소에 액세스하고 저장소 할당량을 늘리는 데 사용됩니다.

데스크톱 응용 프로그램의 경우 각 실행 파일은 CLR의 복사본을 하나만 로드하며 OS 프로세스는 응용 프로그램을 하나만 포함합니다. 각 응용 프로그램에는 시스템 도메인, 공유 도메인, 기본 도메인, 그리고 명시적으로 생성된 여러 AppDomains("JIT and Run: CLR이 런타임 개체를 생성하는 방법을 알아보기 위해 .NET Framework 내부 살펴보기" 참조)가 포함됩니다. CoreCLR에도 비슷한 도메인 모델이 있습니다. Silverlight의 경우 도메인이 다를 수 있는 여러 응용 프로그램이 동일한 OS 프로세스에서 실행됩니다.

Internet Explorer 8.0에서 각 탭은 해당하는 격리 프로세스에서 실행되므로 동일한 탭에 호스트되는 모든 Silverlight 응용 프로그램은 그림 2에 나와 있는 것처럼 동일한 CoreCLR 인스턴스의 컨텍스트에서 실행됩니다. 각 응용 프로그램은 근본이 다른 도메인에 속할 수 있으므로 보안을 위해 각 응용 프로그램은 자체 AppDomain에 로드됩니다. 즉, 현재 Silverlight 응용 프로그램을 호스팅하는 탭의 수만큼 CoreCLR의 인스턴스가 생성됩니다.

데스크톱 CLR과 비슷하게 각 AppDomain은 자체 정적 변수 풀을 가집니다. 각 도메인별 풀은 AppDomain 부트 스트래핑 프로세스 중에 초기화됩니다.

그림 2 각 Silverlight 응용 프로그램은 자체 AppDomain 내에서 실행됨

Silverlight 응용 프로그램은 자체 사용자 지정 응용 프로그램 도메인을 만들 수 없으며 이 기능은 내부 사용을 위해 예약되어 있습니다. CoreCLR에 대한 보다 자세한 내용은 CLR 팀에서 작성한 CLR Inside Out 칼럼인 "CoreCLR을 사용한 Silverlight 프로그래밍" 및 "Silverlight 2의 보안"을 참조하십시오.

Silverlight 런타임

Silverlight는 다양한 수준의 프레임워크와 라이브러리 지원이 필요한 광범위한 응용 프로그램을 위해 디자인되었습니다. 간단한 응용 프로그램의 예로는 사전 웹 사이트에서 작은 오디오 파일을 재생하여 단어 발음을 들려 주거나 배너 광고를 재생하는 응용 프로그램이 있습니다. 반면에 엔터프라이즈급 기간 업무(LOB) 응용 프로그램에는 보안, 데이터 기밀, 상태 관리, 다른 응용 프로그램 및 서비스와의 통합, 도구 지원 등과 같은 다양한 기능이 필요합니다. 동시에 Silverlight는 인터넷에서 연결 속도가 느리더라도 배포에 문제가 되지 않도록 런타임이 작아야 합니다.

이러한 요구 사항에는 상충되는 부분이 있지만 Silverlight 팀은 그림 2에 나와 있는 것처럼 프레임워크를 계층 보기로 분할하여 이 문제를 해결했습니다. CoreCLR + Silverlight 런타임을 "플러그 인"이라고 하며 사용자가 이를 설치해야 응용 프로그램을 실행할 수 있습니다. 이 플러그 인은 대부분의 사용자 중심 응용 프로그램에 충분합니다. 응용 프로그램에 SDK 라이브러리(WCF 통합 또는 Iron Ruby와 같은 DLR 런타임)나 사용자 지정 라이브러리가 필요한 경우 Silverlight가 런타임에 필요한 형식을 확인하는 방법을 알 수 있도록 응용 프로그램이 이러한 구성 요소를 XAP 패키지로 패키징해야 합니다. XAP에 대한 자세한 내용은 이번 호의 Cutting Edge 칼럼을 확인하십시오.

Silverlight 런타임의 크기는 약 4MB이며 agcore.dll 및 coreclr.dll과 같은 CoreCLR 라이브러리와 함께 응용 프로그램 개발자에게 필요한 필수 라이브러리가 포함되어 있습니다. 이러한 라이브러리에는 mscorlib.dll, System.dll, System.Net.dll, System.Xml.dll 및 System.Runtime.Serialization.dll과 같은 기본 라이브러리가 포함됩니다. 브라우저 플러그 인을 지원하는 런타임은 일반적으로 C:\Program Files\Microsoft Silverlight\2.0.30930.0\ 디렉터리에 설치됩니다. 이 디렉터리는 컴퓨터에서 웹 브라우징 세션의 일부로 Silverlight를 다운로드 및 설치할 때 생성됩니다.

동일한 컴퓨터에서 응용 프로그램을 작성하고 테스트하는 경우 플러그 인을 통해 설치된 런타임과 SDK 설치 중 함께 설치된 런타임이 있으며 후자는 C:\Program Files\Microsoft SDKs\Silverlight\v2.0\Reference Assemblies 디렉터리에 있습니다. 이 런타임은 컴파일 타임 참조 목록의 일부로 Visual Studio 템플릿에 의해 사용됩니다.

샌드박스는 Silverlight 응용 프로그램이 대부분의 로컬 리소스와 상호 작용하는 것을 차단하며 이것은 대부분의 일반적인 웹 응용 프로그램에도 해당됩니다. 기본적으로 Silverlight 응용 프로그램은 파일 시스템 액세스(격리 저장소 제외), 소켓 연결, 컴퓨터에 연결된 장치와 상호 작용 또는 소프트웨어 설치와 같은 작업을 할 수 없습니다. 이러한 한계는 Silverlight 플랫폼에서 작성할 수 있는 응용 프로그램의 종류에 분명한 제한으로 작용합니다. 그러나 Silverlight는 백 엔드 비즈니스 프로세스 및 서비스와 상호 작용해야 하는 엔터프라이즈급, 데이터 기반 LOB 응용 프로그램을 개발하는 데 필수적인 모든 구성 요소를 가지고 있습니다.

응용 프로그램 시나리오

여기에서 작성할 LOB 응용 프로그램에서는 중앙 집중식 서버가 PBX(Private Branch eXchange) 인프라를 활용하여 중앙 집중식으로 전화를 제어하는 타사 통화 제어 아키텍처를 보여 줍니다. 여기에서 필자의 목표는 Silverlight를 UI 인터페이스로 사용하는 것을 살펴보는 것이므로 전화 통신 통합에 대해서는 깊게 다루지 않고 간단한 통화 시뮬레이터를 사용하여 들어오는 통화 이벤트를 생성할 것입니다. 시뮬레이터는 호출 관리자의 대기 큐에 통화를 나타내는 데이터 패킷을 전달하며 호출 관리자는 이 프로젝트의 핵심인 프로세스를 트리거합니다.

이 기상의 시나리오에서 콜센터 응용 프로그램은 플랫폼에 독립적인 방식으로 웹 브라우저 내에서 실행되어야 하며 동시에 데스크톱 응용 프로그램과 같은 풍부한 사용자 상호 작용을 제공해야 합니다. ActiveX는 비-Windows 클라이언트 환경에서는 그리 많이 사용되지 않기 때문에 Silverlight는 자연스러운 선택입니다.

응용 프로그램의 아키텍처 측면을 살펴보겠습니다. 여기에서는 푸시 알림, 이벤트 통합, 비즈니스 서비스 통합, 캐시 보안, 그리고 클라우드 서비스와의 통합을 구현하게 됩니다.

푸시 알림 푸시 알림이 요구되는 이유는 시스템이 들어오는 통화 이벤트를 캡처하고 호출자가 입력한 IVR(대화형 음성 응답) 데이터를 전달하여 "스크린 팝"을 수행하거나 들어오는 통화 정보를 UI 화면에 표시해야 하기 때문입니다. 또한 사용자에게 통화를 수락하거나 거부할 기회를 제공해야 합니다.

이벤트 스트리밍 일반적인 웹 응용 프로그램에서 웹 서버가 비즈니스 프로세스의 많은 부분을 처리하므로 비즈니스 이벤트에 대한 모든 정보를 가집니다. 그러나 RIA(다기능 인터넷 응용 프로그램)의 경우 비즈니스 프로세스 구현이 웹 브라우저 내에서 실행되는 응용 프로그램과 비즈니스 웹 서비스를 구현하는 서버에서 공유됩니다. 이것은 Silverlight 응용 프로그램 내에서 생성되는 기술 이벤트는 물론 비즈니스 이벤트도 특수한 웹 서비스를 통해 서버로 전달해야 한다는 것을 의미합니다.

이 솔루션에서 비즈니스 이벤트의 예에는 사용자(rep)가 통화를 거부("rep rejected the call")하거나 수락("rep accepted the call")한 경우가 있습니다. 일반적인 기술 이벤트에는 "호출 관리자 TCP 서버로의 연결 실패" 및 "웹 서비스 예외"가 있습니다.

비즈니스 서비스 통합 콜센터 솔루션도 다른 LOB 응용 프로그램과 마찬가지로 관계형 데이터베이스에 저장된 데이터와 통합이 필요합니다. 여기에서는 이러한 통합을 위한 매체로 웹 서비스를 사용할 것입니다.

캐싱 사용자 환경을 개선하기 위해 정보를 디스크에는 물론 로컬에 메모리로 캐시할 것입니다. 캐시되는 정보에는 담당자의 프롬프터 스크립트를 나타내는 XML 파일과 자주 변경되지 않는 다른 참조 데이터가 포함될 수 있습니다.

보안 응용 프로그램 보안은 이러한 유형의 응용 프로그램에서 기본적인 요구 사항입니다. 보안에는 인증, 권한 부여, 전송 중인 데이터의 기밀 유지, 그리고 사용자 프로필 기반 데이터 잘라내기가 포함됩니다.

클라우드 서비스와의 통합 저장소 서비스와 같은 클라우드 기반 파운데이션 서비스와의 통합에는 특수한 서버 쪽 인프라가 필요합니다. 이것은 통제력과 서비스 수준을 위해 클라우드 서비스 사용을 밀접하게 모니터링 및 조절하기 위해서입니다.

이 기사의 2부에서는 비즈니스 서비스와의 통합, 응용 프로그램 보안, 웹 서비스를 위한 도메인 간 정책, 응용 프로그램 분할에 대해 다루겠습니다.

소켓 서버를 사용한 푸시 알림

화면 팝은 콜센터 응용 프로그램이 전화 통신 인프라에서 에이전트의 화면으로 통화 컨텍스트를 전송하기 위한 기본적인 요구 사항입니다. 전송되는 통화 컨텍스트에는 음성 정보(IVR 시스템용)나 전화 참가 소비자가 입력한 정보가 포함될 수 있습니다.

알림은 브라우저 내에서 클라이언트 폴링이나 서버 푸시의 두 가지 방법을 통해 Silverlight 응용 프로그램으로 전달할 수 있습니다. 폴링은 구현하기가 비교적 쉽지만 전화 통신 이벤트와 클라이언트 간의 상태 동기화가 정확해야 하는 콜센터 시나리오에는 최적의 선택이 아닐 수 있습니다. Silverlight 소켓을 통한 푸시 알림을 사용하는 것은 바로 이 때문입니다.

TCP 소켓을 통한 통신은 Silverlight의 중요한 기능 중 하나입니다. 보안상의 이유 때문에 Silverlight에서는 4502에서 4532 범위에 서버 포트에서만 연결을 허용합니다. 이것은 샌드박스에 구현된 여러 보안 정책 중 하나입니다. 다른 중요한 샌드박스 정책으로 Silverlight가 수신기가 될 수 없다는 것이 있습니다. 따라서 들어오는 소켓 연결을 수락할 수 없습니다. 이러한 이유 때문에 여기에서는 포트 4530을 수신하는 소켓 서버를 만들고 각 연결이 활성화된 콜센터 담당자를 나타내는 연결의 풀을 유지할 것입니다.

또한 Silverlight 소켓 런타임은 서버에서 모든 소켓 연결에 도메인 간 옵트인 정책을 적용합니다. Silverlight 응용 프로그램 코드가 허용되는 포트 번호에 IP 끝점에 대한 연결을 열려고 시도하면 내부적으로 런타임은 동일한 IP 주소의 포트 번호 943에 IP 끝점에 대한 연결을 시도합니다. 이 포트 번호는 Silverlight 구현과 직접 연결되며 응용 프로그램에서 구성하거나 응용 프로그램 개발자가 변경할 수 없습니다.

그림 1에는 아키텍처에서 정책 서버의 위치가 나와 있습니다. Socket.ConnectAsync가 호출될 때의 메시지 흐름 시퀀스는 그림 3에 나와 있습니다. 기본적으로 사용자 코드에서는 메시지 2, 3 및 4를 볼 수 없습니다.

그림 3 Silverlight 런타임은 소켓 연결을 위해 자동으로 도메인 간 정책을 요청합니다.

호출 관리자 서버와 같은 IP 주소에 정책 서버를 구현해야 합니다. 두 서버를 단일 OS 프로세스에 구현할 수 있지만 단순함을 위해 별도의 두 콘솔 프로그램으로 구현할 것입니다. 이러한 콘솔 프로그램은 손쉽게 Windows 서비스로 전환할 수 있으며 장애 조치(Failover)를 통해 안정성과 가용성을 제공하도록 클러스터를 인식하도록 수정할 수 있습니다.

비동기 I/O 루프

.NET Framework 3.5에는 소켓에 대한 새로운 비동기 프로그래밍 API가 추가되었으며 Async()로 끝나는 메서드들이 이러한 추가된 항목입니다. 서버에서 사용할 메서드는 Socket.AcceptAsync, Socket.SendAsync 및 Socket.ReceiveAsync입니다. 비동기 메서드는 I/O 완료 포트, 그리고 재사용 가능한 SocketAsyncEventArgs 클래스를 통한 효과적인 수신 및 전송 버퍼 관리를 갖춤으로써 처리량이 높은 서버 응용 프로그램에 맞게 최적화되었습니다.

Silverlight는 TCP 수신기를 만드는 것을 허용하지 않으므로 해당 Socket 클래스는 ConnectAsync, SendAsync 및 ReceiveAsync만 지원합니다. Silverlight는 비동기 프로그래밍 모델만 지원하며 이러한 특성은 소켓 API 뿐만 아니라 모든 네트워크 상호 작용에 적용됩니다.

여기에서는 클라이언트에는 물론 서버에도 비동기 프로그래밍 모델을 사용할 것이므로 이 디자인 패턴에 익숙해지는 것이 필요합니다. 자주 사용되는 디자인 패턴 중 하나인 I/O 루프는 모든 비동기 작업에 적용이 가능합니다. 먼저 다음과 같은 소켓 수락 루프의 일반적인 동기 실행을 확인해 보겠습니다.

_listener.Bind(localEndPoint);
 _listener.Listen(50);
 while (true)
 {
    Socket acceptedSocket = _listener.Accept();
    RepConnection repCon = new 
      RepConnection(acceptedSocket);
    Thread receiveThread = new Thread(ReceiveLoop);
    receiveThread.Start(repCon);
 }

동기 수락은 직관적이며 프로그래밍과 관리가 쉽지만 각 클라이언트 연결에 전용 스레드가 사용되므로 서버에서 확장성은 그리 좋지 않습니다. 이 구현은 연결이 잦은 환경에서 금방 문제를 일으킬 수 있습니다.

Silverlight가 브라우저 런타임 환경에서 잘 작동하려면 리소스를 가능한 한 적게 사용해야 합니다. 앞서 살펴본 "소켓 수락" 의사 코드에 있는 모든 호출은 자신이 실행되고 있는 스레드를 차단하므로 확장성에는 부정적인 영향을 줍니다. 이 때문에 Silverlight는 호출을 차단하는 데 있어 매우 제한적이며 실제로 네트워크 리소스와의 비동기 상호 작용만 허용합니다. 비동기 루프는 루프가 작동하기 위해 항상 내부에 최소한 한 개의 요청을 포함해야 하는 보이지 않는 메시지 상자를 상상해야 하므로 생각의 틀을 수정해야 합니다.

그림 4에는 수신 루프가 나와 있습니다. 전체 구현은 코드 다운로드에 있습니다. 여기에는 앞서 동기 소켓 수락 의사 코드에서 보았던 while (true) 루프와 같은 무한 루프 프로그래밍 구문이 없는 것을 알 수 있습니다. Silverlight 개발자에게는 이러한 유형의 프로그래밍에 익숙해지는 것이 매우 중요합니다. 메시지를 수신하고 처리한 다음 수신 루프가 계속 데이터를 수신하려면 연결된 소켓과 연관된 I/O 완료 포트에 대한 큐에 요청이 하나 이상 있어야 합니다. 그림 5에는 일반적인 비동기 루프가 나와 있으며 ConnectAsync, ReceiveAsync 및 SendAsync에 적용이 가능합니다. .NET Framework 3.5를 사용할 서버에서는 이 목록에 AcceptAsync를 추가할 수 있습니다.

그림 4 Silverlight 소켓을 사용한 비동기 전송/수신 루프

public class CallNetworkClient
{
   private Socket _socket;
   private ReceiveBuffer _receiveBuffer;

   public event EventHandler<EventArgs> OnConnectError;
   public event EventHandler<ReceiveArgs> OnReceive;
   public SocketAsyncEventArgs _receiveArgs;
   public SocketAsyncEventArgs _sendArgs;
//removed for space
    public void ReceiveAsync()
    {
       ReceiveAsync(_receiveArgs);
    }

    private void ReceiveAsync(SocketAsyncEventArgs recvArgs)
    {
       if (!_socket.ReceiveAsync(recvArgs))
       {
          ReceiveCallback(_socket, recvArgs);
       }
    }

    void ReceiveCallback(object sender, SocketAsyncEventArgs e)
    {
      if (e.SocketError != SocketError.Success)
      {
        return;
      }
      _receiveBuffer.Offset += e.BytesTransferred;
      if (_receiveBuffer.IsMessagePresent())
      {
        if (OnReceive != null)
        {
           NetworkMessage msg = 
                       NetworkMessage.Deserialize(_receiveBuffer.Buffer);
           _receiveBuffer.AdjustBuffer();
           OnReceive(this, new ReceiveArgs(msg));
        }
      }
      else
      {
        //adjust the buffer pointer
        e.SetBuffer(_receiveBuffer.Offset, _receiveBuffer.Remaining);
      }
      //queue an async read request
      ReceiveAsync(_receiveSocketArgs);
    }
    public void SendAsync(NetworkMessage msg) { ... }

    private void SendAsync(SocketAsyncEventArgs sendSocketArgs)  
    { 
    ... 
    }

     void SendCallback(object sender, SocketAsyncEventArgs e)  
    { 
    ... 
    }
   }

fig05.gif

그림 5 비동기 소켓 루프 패턴

그림 4에 나와 있는 수신 루프 구현에서 ReceiveAsync는 소켓의 I/O 완료 포트에 대한 요청을 큐에 저장하는 재진입 메서드 ReceiveAsync(SocketAsyncEventArgs recvArgs)에 대한 래퍼입니다. .NET Framework 3.5에 도입된 SocketAsyncEventArgs는 Silverlight 소켓 구현과 비슷한 역할을 가지고 있으며 여러 요청에 재사용하여 과도한 가비지 수집 작업을 줄일 수 있습니다. 메시지를 추출하고, 메시지 처리 이벤트를 트리거하며, 루프 실행을 계속하기 위해 다음 수신 항목을 큐에 저장하는 작업은 콜백 루틴의 역할입니다.

메시지가 부분적으로 수신되는 경우를 처리하기 위해 ReceiveCallback은 다른 요청을 큐에 저장하기 전에 버퍼를 조정합니다. NetworkMessage는 ReceiveArgs의 인스턴스에 래핑되며 수신된 메시지 처리를 위해 외부 이벤트 처리기로 전달됩니다.

버퍼는 부분 메시지(있는 경우)를 복사한 후 NetworkMessage 수신을 완료할 때마다 버퍼 시작으로 재설정됩니다. 서버에도 비슷한 디자인이 사용되지만 실제 구현에서는 순환 버퍼를 사용하여 혜택을 볼 수 있습니다.

"통화 수락" 시나리오를 구현하려면 모든 새 메시지마다 직렬화 논리를 다시 작성할 필요 없이 임의의 콘텐츠가 포함된 메시지를 직렬화 및 역직렬화할 수 있도록 확장 가능한 메시지 아키텍처를 구축해야 합니다.

fig06.gif

그림 6 직렬화된 NetworkMessage 형식의 레이아웃

메시지 아키텍처는 상당히 단순합니다. NetworkMessage의 각 자식 개체는 인스턴스화 시에 해당하는 MessageAction을 사용하여 자체 용법을 선언합니다. NetworkMessage.Serialize 및 Deserialize구현은 소스 코드 수준 호환성을 위해 Silverlight 및 .NET Framework 3.5(서버)에서 작동합니다. 직렬화된 메시지의 레이아웃은 그림 6에 나와 있습니다.

메시지의 시작 부분에 길이를 삽입하는 방법 대신 적절한 이스케이프 시퀀스로 "시작" 및 "끝" 표시를 사용할 수 있습니다. 메시지에 길이를 인코드하면 버퍼 처리가 한결 수월해집니다.

모든 직렬화된 메시지의 처음 4바이트에는 직렬화된 개체에서 4바이트를 제외한 바이트 수를 포함합니다. Silverlight는 Silverlight SDK의 일부인 System.Xml.dll에 있는 XmlSerializer를 지원합니다. 직렬화 코드는 코드 다운로드에 포함되어 있습니다. 이 코드를 보면 RegisterMessage와 같은 자식 클래스나 UnregisterMessage 및 AcceptMessage를 포함한 다른 메시지에 대한 직접적인 종속성이 없다는 것을 알 수 있습니다. Xml­Include 주석은 직렬 변환기가 자식 클래스를 직렬화하는 동안 .NET 형식을 올바르게 확인하도록 지원합니다.

NetworkMessage.Serlialize 및 Deserialize 사용은 그림 4에 나와 있는 ReceiveCallback 및 SendAsync에서 볼 수 있습니다. 수신 루프에서 실제 메시지 처리는 NetworkClient.OnReceive 이벤트에 연결된 이벤트 처리기에 위해 처리됩니다. CallNetworkConnection 내에서 메시지를 처리할 수도 있지만 수신 처리기를 연결하여 메시지를 처리하면 디자인 타임에 CallNetworkConnection으로부터 처리기를 분리하여 확장성을 개선하는 데 도움이 됩니다.

그림 7에는 CallNetworkClient(그림 4 참조)를 시작하는 Silverlight 응용 프로그램 RootVisual이 나와 있습니다. 모든 Silverlight 컨트롤은 단일 UI 스레드에 연결되며 해당 UI 스레드의 컨텍스트에서 코드가 실행될 때만 UI 업데이트가 수행될 수 있습니다. Silverlight의 비동기 프로그래밍 모델은 네트워크 코드를 실행하고 스레드 풀의 작업자 스레드에 있는 처리기를 실행합니다. Control, Border, Panel 및 대부분의 UI 요소와 같이 FrameworkElement에서 파생된 모든 클래스는 UI 스레드의 코드를 실행할 수 있는 Dispatcher 속성(DispatcherObject에서)을 상속합니다.

그림 7에서 MessageAction.RegisterResponse는 익명 대리자를 통해 에이전트의 콜센터 교대 세부 사항으로 UI를 업데이트합니다. 그림 8에는 대리자 실행을 통해 업데이트된 UI 결과가 나와 있습니다.

그림 7 들어오는 메시지를 처리하는 Silverlight UserControl

public partial class Page : UserControl
{
  public Page()
  {
    InitializeComponent();
    ClientGlobals.socketClient = new CallNetworkClient();
    ClientGlobals.socketClient.OnReceive += new 
                         EventHandler<ReceiveArgs>(ReceiveCallback);
    ClientGlobals.socketClient.Connect(4530);
    //code omitted for brevity
  }
  void ReceiveCallback(object sender, ReceiveArgs e)
  {
    NetworkMessage msg = e.Result;
    ProcessMessage(msg);
  }
  void ProcessMessage(NetworkMessage msg)
  {
    switch(msg.GetMessageType())
    {
      case MessageAction.RegisterResponse:
           RegisterResponse respMsg = msg as RegisterResponse;
           //the if is unncessary as the code always executes in the 
           //background thread
           this.Dispatcher.BeginInvoke(
              delegate()
              {
                 ClientGlobals.networkPopup.CloseDialog();
                 this.registrationView.Visibility = Visibility.Collapsed;
                 this.callView.Visibility = Visibility.Visible;
                 this.borderWaitView.Visibility = Visibility.Visible;
                 this.tbRepDisplayName.Text = this.txRepName.Text;
                 this.tbRepDisplayNumber.Text = respMsg.RepNumber;
                 this.tbCallServerName.Text = 
                                      respMsg.CallManagerServerName;
                 this.tbCallStartTime.Text = 
                                respMsg.RegistrationTimestamp.ToString(); 
              });
            break;
      case MessageAction.Call:
           CallMessage callMsg = msg as CallMessage;
       //Code omitted for brevity
           if (!this.Dispatcher.CheckAccess())
           {
              this.Dispatcher.BeginInvoke(
                 delegate()
                 { 
                    ClientGlobals.notifyCallPopup.ShowDialog(true); 
                 });
           }
           break;
           //
           //Code omitted for brevity  
           //
      default:
             break;
    }
  }
}

fig08.gif

그림 9 담당자의 초기 등록 화면

fig08.gif

그림 8 등록을 처리하고 있는 콜센터 서버

Silverlight의 모달 대화 상자

콜센터 담당자가 로그인하면 콜센터 서버에 등록하여 교대를 시작하라는 메시지가 표시됩니다. 서버의 등록 프로세스에서는 담당자 번호로 색인을 지정하고 세션을 저장합니다. 이 세션은 후속 스크린 팝 및 다른 알림에 사용됩니다. 콜센터 응용 프로그램에서 등록 프로세스를 위한 화면 전환은 그림 89에 나와 있습니다. 네트워크 전송의 진행 상태를 보여 주는 데는 모달 대화 상자를 사용할 것입니다. 일반적인 엔터프라이즈 LOB 응용 프로그램에서는 팝업 대화 상자를 사용하며 모달 및 비-모달 선택은 상당히 자유롭습니다. Silverlight SDK에는 기본 제공 DialogBox가 없기 때문에 이 응용 프로그램에서 사용할 수 있도록 Silverlight로 이를 개발하는 방법을 살펴보겠습니다.

키보드 이벤트가 UI로 전달되는 것을 방지할 쉬운 방법이 없기 때문에 Silverlight 이전에는 모달 대화 상자를 만드는 간단한 방법이 없었습니다. 마우스 상호 작용은 UserControl.IsTestVisible = false 설정을 통해 간접적으로 해제할 수 있습니다. RC0부터는 Control.IsEnabled = false 설정을 사용하면 UI 컨트롤이 키보드나 마우스 이벤트를 수신하지 않습니다. 여기에서는 기존 컨트롤 위에 대화 상자 UI를 표시하기 위해 System.Windows.Controls.Primitives.Popup을 사용할 것입니다.

그림 10에는 추상 메서드 GetControlTree, WireHandlers 및 WireUI를 포함하는 기본 SLDialogBox 컨트롤이 나와 있습니다. 이러한 메서드는 그림 11에 나오는 것처럼 자식 클래스에 의해 다시 정의됩니다. Primitives.Popup을 사용하려면 Popup이 연결될 컨트롤 트리의 일부가 아닌 컨트롤 인스턴스가 필요합니다. 그림 10의 코드에서 ShowDialog(true) 메서드는 포함된 컨트롤이 마우스나 키보드 이벤트를 수신하지 않도록 전체 컨트롤 트리를 재귀적으로 해제합니다. 여기에서 사용할 팝업 대화 상자는 대화형이어야 하므로 새 컨트롤 인스턴스에서 Popup.Child를 설정해야 합니다. 자식 클래스의 GetControlTree 구현은 컨트롤 팩터리 역할을 하며 대화 상자의 UI 요구 사항에 맞는 적절한 사용자 컨트롤의 새 인스턴스를 제공합니다.

그림 10 Silverlight의 팝업 DialogBox

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace SilverlightPopups
{
    public abstract class SLDialogBox
    {
        protected Popup _popup = new Popup();
        Control _parent = null;
        protected string _ caption = string.Empty;
        public abstract UIElement GetControlTree();
        public abstract void WireHandlers();
        public abstract void WireUI();

        public SLDialogBox(Control parent, string caption)
        {
            _parent = parent;
            _ caption = caption;
            _popup.Child = GetControlTree();
            WireUI();
            WireHandlers();
            AdjustPostion();

        }
        public void ShowDialog(bool isModal)
        {
            if (_popup.IsOpen)
                return; 
            _popup.IsOpen = true;
            ((UserControl)_parent).IsEnabled = false;
        }
        public void CloseDialog()
        {
            if (!_popup.IsOpen)
                return; 
            _popup.IsOpen = false;
            ((UserControl)_parent).IsEnabled = true;
        }
        private void AdjustPostion()
        {
            UserControl parentUC = _parent as UserControl;
            if (parentUC == null) return; 

            FrameworkElement popupElement = _popup.Child as FrameworkElement;
            if (popupElement == null) return;

            Double left = (parentUC.Width - popupElement.Width) / 2;
            Double top = (parentUC.Height - popupElement.Height) / 2;
            _popup.Margin = new Thickness(left, top, left, top);
        }
    }
}

그림 11 NotifyCallPopup.xaml 스킨

//XAML Skin for the pop up
<UserControl 
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
  Width="200" Height="95">
   <Grid x:Name="gridNetworkProgress" Background="White">
     <Border BorderThickness="5" BorderBrush="Black">
       <StackPanel Background="LightGray">
          <StackPanel>
             <TextBlock x:Name="tbCaption" HorizontalAlignment="Center" 
                        Margin="5" Text="&lt;Empty Message&gt;" />
             <ProgressBar x:Name="progNetwork" Margin="5" Height="15" 
                        IsIndeterminate="True"/>
          </StackPanel>
          <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
             <Button x:Name="btAccept"  Margin="10,10,10,10"  
                      Content="Accept" HorizontalAlignment="Center"/>
             <Button x:Name="btReject"  Margin="10,10,10,10"  
                     Content="Reject" HorizontalAlignment="Center"/>
          </StackPanel>
       </StackPanel>
      </Border>
    </Grid>
</UserControl>

응용 프로그램 패키지로 컴파일되는 Silverlight UserControl을 인스턴스화하도록 GetControlTree를 구현하거나 XamlReader.LoadControl을 사용하여 XAML(eXtensible Application Markup Language) 파일을 통해 컨트롤을 생성할 수 있습니다. 일반적으로 대화 상자는 런타임에 컴파일된 처리기가 연결될 수 있는 스킨에서 손쉽게 구현할 수 있습니다. 그림 11에는 btAccept 및 btReject 단추가 있는 XAML 스킨이 나와 있습니다. Microsoft Expression Studio 또는 Visual Studio 디자인 작업을 수행한 후에 XAML의 클래스 특성(<userControl class="AdvCallCenter.NotifyCallPopup"…>…</UserControl>)을 그대로 두면 LoadControl 메서드에서 예외가 발생합니다. UI 이벤트 처리기 특성을 모두 제거해야 LoadControl을 사용한 구문 분석을 올바르게 수행할 수 있습니다.

스킨을 만들려면 Silverlight UserControl을 프로젝트에 추가하고, Expression에서 이를 디자인한 다음, 컨트롤에 추가된 "class" 특성과 이벤트 처리기 이름(있는 경우)을 XAML 파일에서 제거하면 됩니다. 클릭 처리기는 그림 12에 나와 있는 것처럼 자식 팝업 클래스의 자식이거나 리플렉션을 사용하여 컨트롤에 연결할 수 있는 별도의 처리기 라이브러리로 만들 수 있습니다.

그림 12 NotifyCallPopup 구현

public class NotifyCallPopup : SLDialogBox
{
   public event EventHandler<EventArgs> OnAccept;
   public event EventHandler<EventArgs> OnReject;
   public NotifyCallPopup(Control parent, string msg)
        : base(parent, msg)
   {
   }
   public override UIElement GetControlTree()
   {
      Return SLPackageUtility.GetUIElementFromXaml("NotifyCallPopup.txt");
   }
   public override void WireUI()
   {
      FrameworkElement fe = (FrameworkElement)_popup.Child;
      TextBlock btCaption = fe.FindName("tbCaption") as TextBlock;
      if (btCaption != null)
          btCaption.Text = _caption;
      }
   public override void WireHandlers()
   {
      FrameworkElement fe = (FrameworkElement)_popup.Child;
      Button btAccept = (Button)fe.FindName("btAccept");
      btAccept.Click += new RoutedEventHandler(btAccept_Click);

      Button btReject = (Button)fe.FindName("btReject");
      btReject.Click += new RoutedEventHandler(btReject_Click);
   }

   void btAccept_Click(object sender, RoutedEventArgs e)
   {
      CloseDialog();
      if (OnAccept != null)
          OnAccept(this, null);
   }
   void btReject_Click(object sender, RoutedEventArgs e)
   {
      CloseDialog();
      if (OnReject != null)
         OnReject(this, null);
   }
}

처리기는 프로젝트 종속성에 의해 자동으로 XAP 패키지로 컴파일되므로 어떤 Silverlight 라이브러리 프로젝트에도 포함될 수 있습니다. 스킨 파일이 XAP 패키지에 포함되도록 하려면 이를 Silverlight 프로젝트에 XML 파일로 추가하고 확장자를 XAML로 변경하십시오. 확장자가 XAML인 파일의 기본 빌드 동작은 응용 프로그램 DLL로 컴파일하는 것입니다. 여기에서는 이러한 파일을 텍스트 파일로 패키징하기를 원하므로 속성 창에서 다음과 같이 특성을 설정해야 합니다.

  • BuildAction = "Content"
  • 출력 디렉터리로 복사 = "복사 안 함"
  • 사용자 지정 도구 = <기존 값 삭제>

XAML 파서(XamlReader.Load)를 사용하는 데는 확장자가 관계없지만 .xaml 확장자를 사용하면 알아보기 쉽고 내용을 잘 나타냅니다. SLDialogBox는 대화 상자를 표시하고 닫는 역할만 수행하며 응용 프로그램의 필요에 맞게 자식 구현을 사용자 지정해야 합니다.

푸시 알림 구현

콜센터 응용 프로그램은 호출자 정보로 스크린 팝을 수행할 수 있어야 합니다. 콜센터의 업무는 담당자가 콜센터 서버에 등록하는 것으로 시작됩니다. 푸시 알림은 연결 지향 소켓을 사용하여 구현됩니다. 기사에는 전체 호출 관리자 서버 구현이 나와 있지 않으며 전체를 보려면 코드 다운로드를 참조하십시오. Silverlight 클라이언트가 서버에 소켓 연결을 만들면 RepList에 새 RepConnection 개체가 추가됩니다. RepList는 고유 담당자 번호로 인덱싱되는 제네릭 목록입니다. 호출이 들어오면 이 목록에서 연결 가능한 담당자를 찾고 RepConnection과 연관된 소켓 연결을 사용하여 에이전트에 호출 정보를 알립니다. RepConnection은 그림 13에 나와 있는 것처럼 ReceiveBuffer를 사용합니다.

그림 13 ReceiveBuffer를 사용하는 RepConnection

class SocketBuffer
{
  public const int BUFFERSIZE = 5120;
  protected byte[] _buffer = new byte[BUFFERSIZE]
  protected int _offset = 0;
  public byte[] Buffer
  {
    get { return _buffer; }
    set { _buffer = value; }
  }

 //offset will always indicate the length of the buffer that is filled
  public int Offset
  {
    get {return _offset ;}
    set { _offset = value; }
  }

  public int Remaining
  {
    get { return _buffer.Length - _offset; }
  }
}
class ReceiveBuffer : SocketBuffer
{
  //removes a serialized message from the buffer, copies the partial message
  //to the beginning and adjusts the offset
  public void AdjustBuffer()
  {
    int messageSize = BitConverter.ToInt32(_buffer, 0);
    int lengthToCopy = _offset - NetworkMessage.LENGTH_BYTES - messageSize;
    Array.Copy(_buffer, _offset, _buffer, 0, lengthToCopy);
    offset = lengthToCopy;
  }
  //this method checks if a complete message is received
  public bool IsMessageReceived()
  {
    if (_offset < 4)
       return false;
    int sizeToRecieve = BitConverter.ToInt32(_buffer, 0);
    //check if we have a complete NetworkMessage
    if((_offset - 4) < sizeToRecieve)
      return false; //we have not received the complete message yet
    //we received the complete message and may be more
      return true;
   }
 }

Silverlight 호출 시뮬레이터를 사용하여 CallDispatcher._callQueue를 호출하면 스크린 팝 프로세스를 트리거할 수 있습니다. CallDispatcher는 기사에는 나와 있지 않으므로 이를 보려면 코드 다운로드를 참조하십시오. CallDispatcher는 _callQueue.OnCallReceived에 처리기를 연결하며 시뮬레이터가 ProcessMessage 구현 내에서 _callQueue에 메시지를 큐에 저장하면 알림을 받습니다. 클라이언트는 이전에 설명한 팝업 대화 상자를 활용하여 그림 14와 같은 수락/거부 알림을 표시합니다. 다음은 그림 8에 나오는 실제 알림 대화 상자를 표시하는 코드 줄입니다.

ClientGlobals.notifyCallPopup.ShowDialog(true);  

그림 14 들어오는 호출 알림

TCP 서비스의 도메인 간 액세스

미디어 및 광고 표시 응용 프로그램과는 달리 실제 엔터프라이즈급 LOB 응용 프로그램에는 다양한 서비스 호스팅 환경과의 통합이 필요합니다. 예를 들어 콜센터 응용 프로그램을 웹 사이트에 호스트(localhost:1041에서 호스트되는 advcallclientweb)하고, 스크린 팝에는 다른 도메인(localhost:4230)에 있는 상태 저장 소켓 서버를 사용하며, 다른 도메인(localhost:1043)에서 호스트되는 서비스를 통해 LOB 데이터에 액세스할 수 있습니다. 또한 계측 데이터를 전송하는 데도 다른 도메인을 사용합니다.

Silverlight 샌드박스는 기본적으로 원래 도메인인 advcallclientweb(localhost:1041)을 제외하고는 다른 도메인으로의 네트워크 액세스를 허용하지 않습니다. 이러한 네트워크 액세스가 감지되면 Silverlight 런타임은 대상 도메인에 설정된 옵트인 정책을 확인합니다. 다음은 클라이언트에서 요청하는 도메인 간 정책을 지원할 필요가 있는 일반적인 서비스-호스팅 시나리오의 목록입니다.

  • 클라우드에서 호스트되는 서비스
  • 서비스 프로세스에서 호스트되는 웹 서비스
  • IIS나 다른 웹 서버에서 호스트되는 웹 서비스
  • XAML 태그 및 XAP 패키지와 같은 HTTP 리소스
  • 서비스 프로세스에서 호스트되는 TCP 서비스

IIS에서 호스트되는 HTTP 리소스와 웹 서비스 끝점을 위한 도메인 간 정책을 구현하기는 간단하지만 다른 경우에는 정책 요청/응답 의미 체계에 대한 지식이 필요합니다. 이 섹션에서는 그림 1에서 호출 관리자라고 소개된 TCP 화면 팝 서버에 필요한 정책 인프라를 구현하는 방법을 간단하게 설명하겠습니다. 다른 도메인 간 시나리오는 이 기사의 2부에서 설명하겠습니다.

TCP 서비스를 사용한 도메인 간 정책

Silverlight에서 모든 TCP 서비스 액세스는 도메인 간 요청으로 취급되며 서버는 포트 943에 바인딩된 동일한 IP 주소에 TCP 수신기를 구현해야 합니다. 그림 3에 나와 있는 정책 서버는 이러한 용도를 위해 구현된 수신기입니다. 이 서버는 Silverlight 런타임이 클라이언트의 네트워크 스택이 화면 팝 서버(그림 3의 호출 관리자)에 연결하기 전에 필요한 선언적 정책을 스트리밍하기 위한 요청/응답 프로세스를 구현합니다.

간단하게 설명하기 위해 여기에서는 콘솔 응용 프로그램에서 호출 관리자 서버를 호스트할 것입니다. 실제 구현에서는 이 콘솔 응용 프로그램을 손쉽게 Windows 서비스로 변환할 수 있습니다. 그림 3에는 정책 서버와의 일반적인 상호 작용이 나와 있습니다. Silverlight 런타임은 서버에 포트 943으로 연결하고 "<policy-file-request/>"라는 텍스트 한 줄이 포함된 정책 요청을 전송합니다.

XML 기반 정책을 사용하면 그림 3에 나와 있는 시나리오가 가능합니다. 소켓 리소스 섹션에서는 4502 - 4534 범위에서 허용되는 포트 그룹을 지정할 수 있습니다. 범위를 축소하는 이유는 공격 범위를 줄임으로서 방화벽 구성에서 우발적인 취약성 위험을 완화하기 위한 것입니다. 콜센터 서버(그림 1의 호출 관리자)는 포트 번호 4530에서 수신하므로 소켓 리소스는 다음과 같이 구성됩니다.

<access-policy>
   <policy>
     <allow-from> list of URIs</allow-from>
     <grant-to> <socket-resource port="4530" protocol="tcp"/></grant-to>
  </policy>     
</access-policy>

port="4502–4534"를 지정하면 허용되는 모든 포트 번호를 사용하도록 <socket-resource>를 구성할 수도 있습니다.

시간을 절약하기 위해 정책 서버를 구현하는 호출 관리자 서버의 코드를 재활용할 것입니다. Silverlight 클라이언트는 정책 서버로 연결하고 요청을 제출하며 응답을 읽습니다. 정책 서버는 정책 응답이 성공적으로 전송되면 연결을 답습니다. 정책 서버는 로컬 파일인 clientaccesspolicy.xml에서 정책 내용을 읽습니다. 이 파일은 다운로드 파일에 포함되어 있습니다.

그림 15에는 정책 서버의 TCP 수신기 구현이 있습니다. 여기에는 이전에 TCP 수락에서 설명한 동일한 비동기 루프 패턴이 사용됩니다. Clientaccesspolicy.xml은 버퍼로 로드되며 모든 Silverlight 클라이언트로 전송하는 데 재사용됩니다. ClientConnection은 SocketAsyncEventArgs와 연결될 수락된 소켓과 수신 버퍼를 캡슐화합니다.

그림 15 TCP 정책 서버 구현

class TcpPolicyServer
{
  private Socket _listener;
  private byte[] _policyBuffer;
  public static readonly string PolicyFileName = "clientaccesspolicy.xml";
  SocketAsyncEventArgs _socketAcceptArgs = new SocketAsyncEventArgs();
  public TcpPolicyServer()
  {
    //read the policy file into the buffer
    FileStream fs = new FileStream(PolicyServer.PolicyFileName, 
                        FileMode.Open);
    _policyBuffer = new byte[fs.Length];
    fs.Read(_policyBuffer, 0, _policyBuffer.Length);
    _socketAcceptArgs.Completed += new 
                 EventHandler<SocketAsyncEventArgs>(AcceptAsyncCallback);

  }
  public void Start(int port)
  {

    IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
    //Should be within the port range of 4502-4532
    IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, port);

    _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, 
                                                       ProtocolType.Tcp);

    // Bind the socket to the local endpoint and listen for incoming connections 
    try
    {
      _listener.Bind(ipEndPoint);
      _listener.Listen(50);
      AcceptAsync();
    }
    //code omitted for brevity

   }
   void AcceptAsync()
   {
      AcceptAsync(socketAcceptArgs);
    }

    void AcceptAsync(SocketAsyncEventArgs socketAcceptArgs)
    {
      if (!_listener.AcceptAsync(socketAcceptArgs))
      {
         AcceptAsyncCallback(socketAcceptArgs.AcceptSocket, 
                                             socketAcceptArgs);
      }
    }

    void AcceptAsyncCallback(object sender, SocketAsyncEventArgs e)
    {
      if (e.SocketError == SocketError.Success)
      {
        ClientConnection con = new ClientConnection(e.AcceptSocket, 
                                               this._policyBuffer);
        con.ReceiveAsync();
      }
      //the following is necessary for the reuse of _socketAccpetArgs
      e.AcceptSocket = null;
      //schedule a new accept request
      AcceptAsync();
     }
   }

그림 15에 나와 있는 코드 샘플에서는 여러 TCP 수락 간에 SocketAsyncEventArgs를 재사용합니다. 이를 위해서는 AcceptAsyncCallback에서 e.AcceptSocket을 null로 설정해야 합니다. 이러한 방식을 사용하면 확장성 요구 사항이 높은 서버에서 GC가 과도하게 수행되는 것을 방지할 수 있습니다.

요약

푸시 알림을 구현하는 것은 콜센터 응용 프로그램의 중요한 측면이며 스크린 팝 프로세스를 가능하게 하는 핵심입니다. Silverlight에서는 AJAX나 비슷한 프레임워크에 비해 스크린 팝을 구현하기가 훨씬 수월합니다. 서버 프로그래밍과 클라이언트 프로그래밍 모델이 비슷하므로 소스 코드 수준에서 어느 정도 재사용이 가능합니다. 콜센터의 경우에는 서버와 클라이언트 쪽 구현 모두에 메시지 정의와 수신 버퍼 추상화를 사용할 수 있습니다.

이 시리즈 2부에서는 앞서 약속한 대로 웹 서비스 통합, 보안, 클라우드 서비스 통합, 그리고 응용 프로그램 분할을 구현하겠습니다. 이러한 과정이 여러분이 구현할 LOB 시나리오에서 활용될 수 있기를 기대하며 이에 대한 의견이 있으면 보내 주십시오.

Silverlight 소켓 구현의 내부와 관련하여 도움을 주신 Microsoft의 Dave Murray와 Shane DeSeranno, 그리고 스크린 팝에 대한 설명에 도움을 주신 콜센터 도메인 전문가 Robert Brooks에게 감사 인사를 전합니다.

Hanu Kommalapati는 Microsoft 플랫폼 전략 관리자이며 현재는 엔터프라이즈 고객에게 Silverlight와 Azure Services 플랫폼 기반의 기간 업무(LOB) 응용 프로그램을 개발하도록 조언하는 업무를 맡고 있습니다.