login register Sysop! about ME  
qrcode
    최초 작성일 :    2008년 12월 01일
  최종 수정일 :    2008년 12월 01일
  작성자 :    Loner
  편집자 :    Loner (유경상)
  읽음수 :    30,014

강좌 목록으로 돌아가기

필자의 잡담~

지난 강좌에 이어서 제작된 서비스에 대한 클라이언트의 구현내용을 담고 있습니다. 정독~~~

Hello World 서비스 클라이언트 구현

서비스를 구현하고 간단하게나마 테스트를 했으면 클라이언트를 구현할 차례가 왔다. WCF는 XML 웹 서비스 표준을 구현하기 때문에 클라이언트를 작성하기 위해 반드시 WCF를 사용해야만 하는 것은 아니다. WCF를 전혀 사용하지 않고 닷넷 프레임워크 2.0 만을 이용하여 클라이언트를 제작할 수도 있고, C/C++이나 Visual Basic 6.0을 사용하여 제작할 수도 있다. 하지만 이 책이 과거 기술을 다루는 책이 아니므로 WCF를 사용한 클라이언트 구현 방법을 설명하도록 하겠다.

클라이언트 콘솔 어플리케이션 구현

클라이언트 프로젝트를 위해서 이미 만들어 놓은 HelloWorldSample 솔루션에 콘솔 어플리케이션 프로젝트를 추가하도록 한다([그림 11] 참조). 클라이언트 프로그램을 위한 콘솔 어플리케이션 프로젝트를 추가 했으면 마찬가지로 WCF 런타임을 위한 System.ServiceModel 어셈블리를 참조(add reference) 해야만 한다.

그림 11. 클라이언트 프로젝트 추가

System.ServiceModel 어셈블리 외에 추가적으로 참조해야 할 것은 Hello World 서비스를 구현해 놓은 HelloWorldService 프로젝트이다. WCF 클라이언트가 왜 서비스를 구현해 놓은 라이브러리를 참조해야만 할까? 서비스는 철저하게 인터페이스의 약속인 계약(contract)을 기반으로 XML 메시지를 주고 받아야 하므로 클라이언트는 Hello World 서비스의 계약인 IHelloWorld 인터페이스에 대한 정보가 필요하기 때문이다.

그렇다면 WCF는 항상 인터페이스 계약을 포함하는 닷넷 어셈블리를 참조해야만 하는 것일까? 물론 그렇지 않다. ASP.NET 웹 서비스의 클라이언트를 개발할 때, 웹 참조(Web Reference)를 추가하는 것과 동일하게 WCF도 서비스 참조를 추가할 수 있다(Add Service Reference). 이렇게 Visual Studio의 도움을 받아 클라이언트 코드의 생성을 쉽게 할 수 있지만 너무 이러한 기능에 의존하다 보면 수백 수천 개의 서비스를 호출해야 하는 대형 프로젝트에서 개발 및 관리상의 어려움을 겪을 수도 있다. 따라서 이러한 개발 도구의 도움 없이도 클라이언트 코드를 작성하는 방법을 알아 두는 것은 '대안'을 제시해 줄 수 있기 때문에 항상 도움이 된다. 이 때문에 일부러 Hello World 서비스를 별도의 라이브러리로 구현했고 이를 클라이언트에서 직접 참조하는 방식을 설명하고 있는 것이다. WCF 계약 인터페이스 타입을 직접 참조하는 것과 Visual Studio가 생성해 주는 코드를 사용하는 것은 각기 장단점이 있으며 이에 대해서는 6장에서 좀 더 상세하게 설명하기로 하겠다.

어찌 되었건 클라이언트 프로젝트는 서비스의 IHelloWorld 인터페이스를 참조 해야만 하기 때문에 HelloWorldService 라이브러리 프로젝트를 참조해야 한다. 결과적으로 [그림 12]와 같이 어셈블리 및 프로젝트 참조 구조를 갖게 된다. 2장에서 HelloWorldService 라이브러리 프로젝트 참조를 사용하지 않고 클라이언트를 작성하는 몇 가지 방법을 보여줄 것이므로 지금 마음에 안 들더라도 조금만 더 참아주기 바란다.

그림 12. 클라이언트 프로젝트의 어셈블리/프로젝트 참조

프로젝트 설정이 완료 되었으면 이제 코드를 작성할 차례이다. WCF 클라이언트는 서비스와 통신하기 위해 유일하고 반드시 알아야 할 것이 바로 서비스의 종점(endpoint)이다. [그림 2]에서도 나타냈듯이 WCF 서비스의 클라이언트는 서비스의 종점을 통해서 서비스를 호출하고 그 결과를 받는다. 서비스 종점은 서비스의 인터페이스를 기술하는 계약(contract), 프로토콜이나 보안 등의 다양한 네트워킹 및 메시징을 기술하는 바인딩(binding), 그리고 서비스의 인터넷 상의 위치를 기술하는 주소(address)를 포함하고 있으므로 클라이언트는 호출하고자 하는 서비스의 종점이 무엇인가 WCF 런타임에게 알려주어야만 하는 것이다.

System.ServiceModel.Description 네임스페이스의 ServiceEndpoint 클래스는 바로 서비스의 종점을 기술하는데 사용되는 클래스이다. ServiceEndpoint 클래스는 서비스의 종점을 기술하는 계약, 바인딩, 그리고 주소를 모두 포함하는 정보를 모두 가지고 있다. 다행스럽게 이 클래스는 생성자가 두 개 밖에 안되므로 인스턴스를 생성하는 것이 어렵지 않다.

public class ServiceEndpoint
{
    public ServiceEndpoint(ContractDescription contract);
    public ServiceEndpoint(ContractDescription contract, Binding binding,
                     EndpointAddress address);
    public ContractDescription Contract { get; }
    public Binding Binding { get; set; }
    public EndpointAddress Address { get; set; }
    ......
}

생성자의 필수 매개변수는 서비스의 계약 정보를 나타내는 ContractDescription 클래스 객체이다. ContractDescription 클래스는 서비스 계약이 어떤 닷넷 인터페이스 타입인가에 대한 정보와 서비스 계약에 포함될 수 있는 다양한 속성들(네임스페이스, 세션 사용 여부, 암호화 여부 등)에 대한 정보를 포함한다.

public class ContractDescription
{
    public string Namespace { get; set; }
    public static ContractDescription GetContract(Type contractType);
    ......
}

예를 들어, [리스트 1]의 IHelloWorld 인터페이스에 네임스페이스 를 다음과 같이 설정했다면, ContractDescription 객체의 Namespace 속성은 인터페이스에 설정된 네임스페이스인 http://www.simpleisbest.net/wcf/helloworld 를 반환한다.

// 서비스 Contract 선언
[ServiceContract(Namespace="http://www.simpleisbest.net/wcf/helloworld")]
public interface IHelloWorld
{
    [OperationContract]
    string SayHello();
}

ContractDescription desc =
                 ContractDescription.GetContract(typeof(IHelloWorld));
Console.WriteLine(desc.Namespace);

// 출력 결과
http://www.simpleisbest.net/wcf/helloworld

ContractDescription 객체 외에 ServiceEndpoint 객체를 위한 바인딩과 서비스 주소에 대한 정보는 생성자를 통해서, 혹은 속성을 통해서 설정이 가능하다. 바인딩은 Binding 클래스에서 파생된 클래스의 인스턴스를 사용해야 하며, BasicHttpBinding, WSHttpBinding, NetTcpBinding 등이 모두 Binding 클래스에서 파생된 클래스들이다. 서비스의 주소는 EndpointAddress 클래스를 통해 표시되며 주로 서비스의 URI 정보를 포함하는 용도로 사용되지만 이외의 추가적인 정보를 포함할 수 있다. '간단한' Hello World 예제임을 다시 한번 상기하면서 EndpointAddress 클래스에는 서비스 주소에 대한 URI를 설정한다는 정도만 알아두고 넘어 가도록 하자.

ServiceEndpoint 클래스를 이용하여 구체적으로 서비스의 종점 정보를 나타내는 코드 조각은 다음과 같다.

Uri uri = new Uri("http://localhost/wcf/example/helloworldservice");
ServiceEndpoint ep = new ServiceEndpoint(
    ContractDescription.GetContract(typeof(IHelloWorld)),
    new BasicHttpBinding(),
    new EndpointAddress(uri));

먼저 ContractDescription 클래스의 정적 메쏘드인 GetContract 메쏘드 호출을 통해 IHelloWorld 인터페이스에서 서비스 계약에 대한 속성 정보를 포함하는 ContractDescription 객체를 얻어낸다. 그리고 바인딩에 사용된 객체는 BasicHttpBinding 이다. Hello World 서비스가 사용한 바인딩이 BasicHttpBinding 이므로 클라이언트 역시 동일한 바인딩을 사용해야만 한다. 이것 역시 추가 설명이 불필요할 정도로 당연한 것인데, 클라이언트와 서비스가 네트워킹이나 메시징에서 서로 다른 속성을 사용해서는 정상적인 통신이 불가능할 것이기 때문이다. 예를 들어 서비스는 클라이언트가 텍스트 기반의 XML 메시지 기반을 보낼 것으로 기대하는데 클라이언트는 엉뚱하게 MIME 기반의 바이너리 데이터를 보낸다면 당연히 오류가 발생할 것 이다. 따라서 클라이언트와 서비스는 정확하게 동일한 바인딩을 사용하거나 호환되는 바인딩을 사용해야만 하는 것이다. ServiceEndpoint 객체를 생성하는데 사용한 마지막 매개변수는 서비스의 주소를 나타내는 EndpointAddress 객체로서 Uri 클래스를 사용하여 가장 단순한 서비스 종점 주소를 나타내고 있다.

서비스에 대한 종점을 나타내는 ServiceEndpoint 객체를 구성했으면 다음으로 할 일을 WCF의 채널 팩토리(channel factory)를 생성하는 작업이다. WCF의 내부 구조는 다양한 XML 웹 서비스 통신에 사용할 수 있는 다양한 채널(channel)들이 메시지를 순차적으로 처리해 나가도록 구성되어 있다. 즉, WCF를 통해 클라이언트가 서비스를 호출하면, 닷넷의 메쏘드 호출이 XML 메시지로 변환하는 작업을 수행하는 채널을 거치고 필요하다면 메시지에 추가적인 보안 설정을 수행하는 채널도 통과하며 최종적으로 메시지를 트랜스포트(transport) 프로토콜을 사용하여 네트워크로 흘려 보내는 트랜스포트 채널을 통과하게 된다. 이러한 채널들은 서비스의 계약에 주어진 보안, 트랜잭션, 세션 등의 속성과 바인딩에 설정된 메시지 인코딩, 트랜잭션 처리 등등에 의해 채널의 파이프 라인 형태로 구성 되어 진다. 채널에 대한 상세한 내용은 5장과 13장에서 상세하게 다시 설명하도록 하겠다.

이렇게 여러 채널들 중 필요한 채널들의 파이프 라인을 구성해 주는 역할을 하는 것이 채널 팩토리 객체이며 ChannelFactory<T> 클래스에 의해 구현되어 있다 . 제너릭 타입인 이 클래스는 서비스의 계약 인터페이스를 타입 매개변수로 취하여 WCF의 내부 채널들을 구성하고 이 채널들을 대표하는 프록시 객체를 생성하여 반환해 주는 역할을 수행한다.

public abstract class ChannelFactory :
        CommunicationObject, IChannelFactory, ICommunicationObject, IDisposable
{
    public ClientCredentials Credentials { get; }
    public ServiceEndpoint Endpoint { get; }
......
}

public class ChannelFactory<TChannel> :
            ChannelFactory, IChannelFactory,
            IChannelFactory, ICommunicationObject
{
    public ChannelFactory(ServiceEndpoint endpoint);
    public TChannel CreateChannel();
    ......
}

ChannelFactory<T> 클래스를 사용하여 구체적으로 서비스에 대한 프록시 객체를 얻는 코드는 다음과 같다.

ServiceEndpoint ep = new ServiceEndpoint(......);
ChannelFactory<IHelloWorld> factory = new ChannelFactory(ep);
IHelloWorld proxy = factory.CreateChannel();

// 서비스 계약 인터페이스(IHelloWorld)를 이용하여 서비스 호출

(proxy as IDisposable).Dispose();

먼저 ChannelFactory<T> 인스턴스 생성을 위해 미리 구성해 놓은 ServiceEndpoint 객체를 이용하고, 채널 팩토리의 CreateChannel 메쏘드를 호출하면 WCF 서비스에 대한 프록시 객체가 생성된다. 이 프록시 객체는 서비스의 계약 인터페이스 타입을 갖게 되며, 프록시 객체는 인터페이스 메쏘드 호출을 XML 메시지로 변환하고 다양한 채널들을 통과하면서 이 XML 메지지를 처리하고 최종적으로 이 XML 메시지를 서비스에 전달하게 될 것이다.

프록시 객체는 반드시 Dispose 메쏘드를 호출하여 통신에 할당된 시스템 자원을 해제해 주어야 한다. 나중에 8장에서 세션을 다루게 될 때도 설명을 하겠지만 프록시 객체는 WCF 서비스 세션과도 연관이 깊으므로 프록시 객체를 닫거나(close) 해제하는(dispose) 작업은 매우 중요하다. 다시 한번 강조하는 바이다. 프록시 객체는 적절한 시점(이 경우 사용이 완료된 시점)에서 반드시 해제를 해주어야만 한다.

[리스트 4]는 Hello World 서비스에 대한 WCF 클라이언트 전체 코드를 보여주고 있다. 서비스에 대한 종점을 ServiceEndpoint 객체를 통해 구성하고, 이를 바탕으로 ChannelFactory<T> 객체를 통해 채널 파이프 라인을 구성한 후, CreateChannel 메쏘드를 호출함으로써 서비스에 대한 프록시 객체를 구해 사용하고 있다. 설명은 길었지만 코드는 상당히 간결한 편이다. 지금 당장 [리스트 4]가 완전히 이해되지 않는다고 해서 조급해 할 필요는 없다. 이 책을 읽어 가면서 점차로 모든 것이 이해될 것이기 때문이다.

리스트 4. Hello World 서비스 WCF 클라이언트 코드

using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using HelloWorldService;

namespace HelloWorldClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri uri =
                    new Uri("http://localhost/wcf/example/helloworldservice");
            ServiceEndpoint ep = new ServiceEndpoint(
                    ContractDescription.GetContract(typeof(IHelloWorld)),
                    new BasicHttpBinding(),
                    new EndpointAddress(uri));

            ChannelFactory<IHelloWorld> factory =
                    new ChannelFactory(ep);
            IHelloWorld proxy = factory.CreateChannel();
            string result = proxy.SayHello();
            (proxy as IDisposable).Dispose();

            Console.WriteLine(result);
        }
    }
}

완성된 Hello WCF World 예제

HelloWorldHost 프로젝트를 빌드하고 수행시킨 후, HelloWorldClient 프로젝트를 빌드하고 수행 시키자. 그리고 나서 클라이언트가 [그림 13]과 같은 결과를 출력하는지 확인하도록 하자.

그림 13. HelloWorldClient 프로그램 수행 결과

이렇게 WCF를 사용하여 Hello World 서비스와 그 클라이언트를 작성해 보았다. Kernighan과 Ritchie의 Hello World 프로그램보다 훨씬 더 복잡하고 많은 코드를 소요할 뿐만 아니라 새로운 개념이 많이 등장했지만, Hello World 서비스가 WCF를 이해하는 가장 기초적인 요소들을 소개하는 데에는 더 없이 좋은 예제임에는 분명하다.

수행 결과는 더 없이 허탈하게 달랑 "Hello WCF World !" 문자열이 출력되는 것이 전부지만 Hello World 서비스와 클라이언트는 WCF 런타임과 많은 작업을 수행하고 있다. [리스트 4]에서 IHelloWorld 인터페이스의 SayHello 메쏘드를 호출하는 것은 개발자에게 단순히 닷넷 인터페이스의 메쏘드를 호출하는 것으로 보이지만, 서비스의 계약 인터페이스에 대한 메쏘드 호출은 다음과 같은 XML 메시지로 변환된다.

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
    <SayHello xmlns="http://www.simpleisbest.net/wcf/helloworld"/>
    </s:Body>
</s:Envelope>

지금까지 단순히 XML 메시지이란 말을 썼었지만, 정확히 말하자면 XML 기반의 SOAP(Simple Object Access Protocol) 메시지로서 W3C의 표준으로 제정된 웹 서비스의 메시지 표준이 되겠다. SOAP에 대해서는 3장과 4장에서 상세히 다시 설명하기로 하고, 여기서는 WCF가 닷넷 메쏘드 호출을 이와 같이 웹 서비스 표준인 XML SOAP 메시지로 변환하여 전송해 주는 것으로만 알아 두자.

다음은 Hello World 서비스가 반환한 결과 SOAP 메시지를 보여주고 있다. [그림 13]과 같은 수행 결과가 나오도록 SOAP 메시지 내에 "Hello WCF World !" 라는 문자열이 보이고 있다.

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <SayHelloResponse xmlns="http://www.simpleisbest.net/wcf/helloworld">
            <SayHelloResult>Hello WCF World !</SayHelloResult>
        </SayHelloResponse>
    </s:Body>
</s:Envelope>

이제 좀 정리를 해보자. 지금까지 WCF의 간략한 개념과 아주 간단한 Hello World 서비스를 구현해 보았다. WCF는 SOAP 메시지 기반의 웹 서비스를 구현하기 쉽게 해주는 프레임워크로서 닷넷 프로그래밍 적인 요소를 XML 웹 서비스 요소로 바꾸어 줄 뿐만 아니라 보다 간편한 프로그래밍 모델을 제시해 준다는 것을 알 수 있을 것이다.

이러한 개념을 보여주는 것이 [그림 14]이다. WCF 서비스와 WCF 클라이언트는 WCF 런타임과 클래스 라이브러리를 이용하여 서비스와 클라이언트 프록시를 구성하고 클라이언트가 프록시를 통해 서비스를 호출하면 서비스 호출은 여러 채널을 통과함에 따라 XML 기반의 SOAP 메시지로 변환되고 이 메시지가 서비스에 도착하게 된다. 서비스 측의 채널들은 수신된 SOAP 메시지를 처리하여 다시 서비스 메쏘드 호출로 변환해 주며 서비스 메쏘드 수행 후 서비스의 수행 결과는 다시 채널을 통과하면서 XML 기반의 SOAP 결과 메시지로 변환된다. 클라이언트 측의 채널들은 결과 XML을 처리하여 프록시의 리턴 값(return value)으로 변환하여 최종적으로 클라이언트에게 전달될 것이다.

그림 14. WCF의 역할

[리스트 2]와 [리스트 3]의 서비스와 [리스트 4]의 클라이언트 구현 코드는 상당히 간단해 보이지만 그 하부에 자리 잡은 WCF 프레임워크는 아주 많은 일을 하고 있으며 이러한 많은 작업 덕택에 간단한 닷넷 메쏘드 호출이 XML 웹 서비스 호출로 변환되는 것이다.


authored by

  heykiss
  2008-12-01(15:55)
캐릭 이미지
잘보고 있습니다..(^^)(--)(__)(^^)
  BeginerASP
  2008-12-01(16:21)
캐릭 이미지
잘 보고 있는데요 닷넷 리모팅 이랑 소스랑 개념이 비스무리 한것같네요

책 나오면 바로 구매 하겠습니다 ㅎㅎ

  raintype
  2008-12-23(14:01)
캐릭 이미지
리스트 4. 소스에서 ChannelFactory를 통하여 객체 생성하는 부분에서
오류가 발생하네요
Validation Exception 발생으로 교정된 소스는 못 올리겠네요 ^^
생성시 사용한 제너릭 타입과 동일하게 new로 생성하면
정상적으로 동작하네요

  senosora
  2009-02-24(10:58)
ChannelFactory < IHelloWorld > factory = new ChannelFactory < IHelloWorld
> ( ep );

  senosora
  2009-02-24(10:59)
이렇게 올리면 되지 않을까용 <, >, ( , )사이에 한칸 씩 사이띄기는 없애주세요
  saja0405
  2010-01-06(03:16)
감사히 보고 갑니다.
proxy와 channel 간의 관계를 명확히 이해하지 못했으나,
남은 아티클에 부연설명이 나오지 않을까 기대해 봄니다.


 
 
.NET과 Java 동영상 기반의 교육사이트

로딩 중입니다...

서버 프레임워크 지원 : NeoDEEX
based on ASP.NET 3.5
Creative Commons License
{5}
{2} 읽음   :{3} ({4})