Hello World 서비스 구현
지금까지 슈퍼 울트라 슬림하게 WCF 서비스의 개념에 대해 살펴보았다. 말로만 해서는 감이 잘 안 잡히는 법. 우리 같은 기술쟁이 개발자는 코드를 보면 훨씬 더 빠르게 감을 잡곤 하지 않았는가? 그래서 곧바로 WCF 기반의 Hello World 서비스를 구현해 보도록 하자.
WCF 개발 환경을 준비하는 방법은 별도로 언급하지 않는다. VS 2008을 가지고 있다면 이미 개발환경을 가지고 있는 것이니 말이다(태오 코멘트 : 이 부분은 원래의 원고와는 다르게 제가 편집을 하였습니다. 원서에서는 개발환경 구성은 부록 A를 참고하라고 나와있습니다). 이 책에서는 가급적 WCF를 위한 프로젝트 템플릿을 사용하지 않을 것이다. 이유는 간단하다. WCF 프로젝트 템플릿은 처음 WCF를 접하는 사람에게는 조금이나마 도움이 되겠지만, 반복적으로 잦은 WCF 서비스/클라이언트를 구현하는데 불필요한 클래스나 기타 선언들이 많기 때문이다. 또한 필자의 경험상 템플릿에 너무 의존하게 되면 그것에 익숙해져서 하부 구조나 세부 사항들을 간과하게 되는 경우가 많아서 이다. WCF의 개념을 파악할 때는 가열차게 맨땅에 헤딩을 해서 확실한 이해를 얻은 후에 템플릿의 도움을 받아도 늦지 않다.
이번 장과 2장에서는 독자들이 WCF에 대해 전혀 경험이 없다는 가정하에 WCF 서비스를 만드는 기본적인 절차를 상세히 설명하도록 하겠다. 하지만 3 장부터는 Visual Studio에서 어떤 작업을 해야 하는지 매번 구구절절 하게 상세히 설명하지 않겠다. 다만, 필요할 때만 간단한 화면 캡처를 제공하거나 간단한 메뉴 작동 방식을 설명하겠지만 Step-by-Step 형식의 따라 하기는 이번이 마지막이 될 것이다.
솔루션 및 WCF 서비스 프로젝트 만들기
WCF 서비스와 클라이언트를 작성하기 위해서는 적어도 2개의 Visual Studio 프로젝트를 작성해야 한다 . 이들을 하나의 솔루션으로 묶으면 작업하기 편리할 것이란 것은 초짜 개발자가 아닌 다음엔 누구나 알 수 있을 것이다. 따라서 [그림 4]와 같이 먼저 Visual Studio에서 HelloWorldSample 이란 이름을 갖는 '빈 솔루션'을 만들자.
이 강좌에서는 Visual Studio 2008을 사용한다. WCF가 처음 등장했을 때는 Visual Studio 2005와 추가로 설치되는 애드인을 통해 WCF 개발이 가능했지만 Visual Studio 2008이 등장한 이상 Visual Studio 2005는 WCF 개발을 지원하지 않는다. Visual Studio 2005와 Windows SDK를 사용하여 개발이 가능하긴 하지만 매우 불편한 개발 환경을 감수해야만 한다. 이 책에서는 Visual Studio 2005 환경에 대한 내용은 다루지 않는다.
그림 4. HelloWorldSample 솔루션 작성
Hello World 서비스는 매우 간단한 예제로 작성할 것이지만, 이 서비스를 호스트 하는 프로그램과 클라이언트가 모두 이 서비스의 계약, 즉 서비스의 인터페이스를 참조 해야 한다. 따라서 서비스는 클라이언트와 서비스 호스트가 모두 참조할 수 있는 라이브러리 프로젝트로서 작성할 것이다. 이를 위해 [그림 5]와 같이 HelloWorldService 란 이름을 가진 클래스 라이브러리를 추가하도록 하자. 굳이 서비스를 라이브러리로 작성하는 또 하나의 이유는 2장에서 이 서비스를 IIS에서 호스팅 하는 것 역시 예제로서 보여줄 때 재사용 하기 위해서 이기도 하다.
Visual Studio 2008에서 WCF 관련 프로젝트를 추가할 때 항상 주의할 부분은 프로젝트가 사용할 닷넷 프레임워크 버전이다. [그림 5]에서 우측 상단의 닷넷 프레임워크 버전을 확인하여 3.0 버전 이상을 사용하도록 하자. 이 책에서는 모든 예제가 닷넷 프레임워크 3.0을 기준으로 작성되었지만 3.5 버전을 사용하여도 상관은 없다.
영문판 개발도구를 쓰는 필자의 변
필자는 영문판 개발도구를 선호하는 편이다. 이유는 이렇다. 첫째로 새로운 기술을 접하는 가장 빠른 방법 중 하나는 MSDN 온라인 사이트(http://msdn.microsoft.com)를 자주 들락거려야 하는데, 여기에 기술된 자료들의 용어를 쉽게 이해할 수 있기 때문이다. 예를 들어, 어떤 White paper에서 Visual Studio의 특정 기능과 메뉴를 참고할 때는 당연히 영문 명칭을 사용하게 된다. 하지만 한글판 Visual Studio에서 해당 기능 혹은 메뉴를 직관적으로 찾기 어려운 경우가 종종 있기 때문이다. 그래서인지 영문판 Visual Studio가 편하게 느껴지는 것이다.
두 번째 이유는 각종 베타 버전의 SDK나 애드인(add-in) 등이 한글판이 나오는 경우가 드물기 때문이다. 간혹 한글판이 등장하는 경우도 있지만 영문판 등장 이후 상당한 시간을 기다려야 하기 때문에 남들 보다 앞서 나가야 한다는 압박에 시달리는 필자는 설치에 문제가 발생할 수 있는 영문판 베타/애드인을 선뜻 한글판 Visual Studio 에 설치하기가 꺼려지는 것이 당연하다고 하면 좀 억지스러운가?
몇 가지 이유가 더 있지만 생략하기로 하고, MSDN 라이브러리 마저 영문판을 사용하는 이유도 바로 여기에 있다. 영문판은 꾸준히 업데이트 될 뿐만 아니라 다른 기술 자료를 볼 때도 동일한 '용어'가 사용되기 때문에 일관성을 가지고 기술을 접할 수 있어 좋다. 결론은 필자가 영어를 잘해서가 아니라 필자가 한글 다음으로 잘하는 언어가 독일어나 일본어가 아닌 영어이기 때문에, 그리고 꾸준히 업데이트되며 일관성 있는 용어를 이해하기 위해 영문판 Visual Studio를 사용하는 것이라는 것이다.
이 책에서 등장하는 Visual Studio의 화면들은 모두 영문판 Visual Studio의 화면이다. 한글판을 사용하는 독자들에게는 상당히 미안하고 죄송하지만 영문판 화면이 나오더라도 이해해 주기 바라는 마음이다. 본문에서 Visual Studio의 기능이나 메뉴를 언급할 때는 한글 표현과 영문 표현을 모두 사용했으므로 책을 읽고 예제를 작성해 나가는 데는 큰 지장이 없으리라고 본다.
HelloWorldService 프로젝트는 WCF 서비스를 구현할 것이므로 WCF의 핵심 어셈블리인 System.ServiceModel 어셈블리를 참조해야만 한다. 이 어셈블리는 닷넷 프레임워크 3.0을 설치하거나 Windows Vista의 경우 기본으로 제공되는 어셈블리이므로 참조 추가(Add Reference)를 수행하여 프로젝트의 참조 목록에 포함시키면 된다. [그림 6]은 HelloWorldSample 솔루션에 추가된 HelloWorldService 라이브러리 프로젝트와 이 프로젝트가 참조한 System.ServiceModel 어셈블리를 보여주고 있다. 일단 이 정도면 WCF 서비스를 구현하는데 필요한 Visual Studio 솔루션과 프로젝트 구성은 끝난 것이 되겠다.
그림 5. 솔루션에 클래스 라이브러리 프로젝트 추가
그림 6. Console 어플리케이션 추가와 System.ServiceModel 참조 추가
Hello World 서비스 구현
WCF 서비스의 구현은 서비스의 계약을 선언하고 이 계약을 구현(implement)하는 서비스 타입을 작성하는 것으로 요약해 볼 수 있다. 따라서 가장 먼저 선행되어야 할 것은 서비스 계약을 선언하는 것이다. 우리가 구현할 Hello World 서비스의 계약은 다음과 같이 선언할 수 있다.
리스트 1. WCF 서비스 Contract 선언
using System.ServiceModel;
namespace HelloWorldService
{
// 서비스 Contract 선언
[ServiceContract]
public interface IHelloWorld
{
[OperationContract]
string SayHello();
}
}
[리스트 1]은 서비스가 외부 세계에게 노출할 서비스의 계약인 IHelloWorld 인터페이스를 정의하고 있다. WCF가 지향하고 있는 서비스의 세계에서는 클라이언트와 서비스가 서로 통신하는데 사용하는 인터페이스의 약속이 바로 계약이며, 이 계약을 닷넷 프레임워크에서는 닷넷 인터페이스 타입으로써 구체화 하는 것이다.
WCF에서 서비스의 계약을 위한 인터페이스 선언은 반드시 System.ServiceModel 네임스페이스의 ServiceContract 특성(attribute)을 인터페이스에 명시해야만 한다. 비슷하게 인터페이스 내에 서비스에서 사용될 메쏘드 역시 명시적으로 OperationContract 특성을 추가해 주어야 한다. 만약 인터페이스에 OperationContract 특성이 명시되지 않은 메쏘드가 존재하는 경우, WCF는 이 메쏘드를 계약의 일부로 간주하지 않는다. WCF가 계약 인터페이스의 메쏘드에 OperationContract 특성이 명시적으로 표시되어 있는 경우에만 계약의 일부로 간주하는 이유는 설계자와 개발자의 오류와 실수를 미연에 방지하고 불필요한 메쏘드들이 의도 되지 않은 채로 서비스의 계약 인터페이스에 포함되지 않게 하기 위함이다. 이렇게 명시적인 방법으로 어떤 기능의 사용 여부를 밝히거나 인터페이스를 명시하는 정책을 Opt-In 정책이라 하며 암시적으로 기능이 사용되거나 인터페이스가 정의되어 버리는 정책을 Opt-Out 정책이라 한다. WCF는 서비스 계약뿐만 아니라 다양한 상황에서 대부분 Opt-In 정책을 더 선호한다.
용어 : 특성(attribute)과 속성(property)
닷넷에는 타입이나 메쏘드 등에 대한 메타데이터로써 특성(attribute)을 제공한다. 그리고 클래스나 구조체는 속성(property)을 통해 데이터 구조의 데이터 필드를 추상화 한다. Attribute 라는 용어는 속성 혹은 특성이라고 번역될 수 있지만 아주 오래 전부터 속성이란 용어는 Property를 지칭하고 있으므로 Attribute에 대해서는 특성이라고 표기하는 것이 독자의 혼동을 줄일 수 있다. 그런데 XML에도 attribute 라는 것이 존재한다. 즉, XML 요소(element)에 attribute를 추가할 수 있는데, XML의 attribute를 특성이라고 표기하는 것 역시 혼동을 일으킬 수 있다. 따라서 이 책에서는 닷넷 프레임워크의 Attribute에만 특성이란 용어를 사용하고 클래스나 구조체의 Property에는 속성이라는 용어를, 그리고 XML attribute에도 속성이란 용어를 사용할 것이다. 다만 XML attribute를 속성이라 지칭할 때 혼동의 가능성이 있을 때는 괄호 내에 attribute라고 표시를 할 것이다.
닷넷 Attribute에 대해 추가적으로 언급할 내용은, 특성 클래스에 대한 내용이다. 일반적으로 특성으로 사용되는 클래스는 클래스의 이름에 Attribute라는 접미사를 사용한다. 예를 들어, ServiceContractAttribute 나 OperationContractAttribute 와 같은 것인데, 이 책에서는 특성을 지칭할 때는 Attribute 라는 접미사를 생략하고 ServiceContract 특성, OperationContract 특성으로 표기하도록 하겠다. 실제로 코드 상에 등장하는 특성의 이름이 Attribute 접미사를 뺀 부분만이 사용되기 때문이다. 다만, 특성 클래스 자체를 언급할 때는 전체 클래스 이름으로써 Attribute 접미사를 포함한 클래스 이름을 언급할 것이다.
IHelloWorld 인터페이스는 매우 간단하다. 달랑 SayHello 메쏘드 하나만을 가지고 있을 뿐이며, 이 메쏘드의 구현(implementation)은 이제 IHelloWorld 인터페이스를 구현하는 서비스 타입에 의해 결정 될 것이다. WCF에서 계약 인터페이스 내에 존재하는 메쏘드는 operation 이란 용어를 사용한다. Operation 이란 용어를 번역하여 쓸 마땅한 단어가 떠오르지 않아서 MSDN을 살펴보았더니 "작업" 이란 용어가 등장했다. 작업이란 용어가 그다지 맘에 들지 않을 뿐만 아니라 직관적이지 않아서 독자들의 이해를 방해할 수 있기 때문에 가급적 "작업"이란 용어는 피할 것이다. 대신 "서비스 메쏘드" 혹은 "메쏘드"라는 용어를 능동적으로 사용할 것이다. 그렇지만 MSDN이나 다른 문서에서 "작업"이란 용어가 지속적으로 등장할 것이므로 독자들은 혼동이 없기를 바란다.
[리스트 2]는 IHelloWorld 서비스 인터페이스를 구현하는 HelloWorldWCFService 클래스를 보여주고 있다. HelloWorldWCFService 클래스는 단순하게 IHelloWorld 인터페이스를 구현하는 클래스로써 특정 클래스의 파생 클래스이어야 한다거나, 특정 코드를 사용해야 한다는 등의 특별한 코드 구현을 요구하지 않는다.
리스트 2. IHelloWorld를 구현하는 서비스 타입 구현
using System.ServiceModel;
namespace HelloWorldService
{
// 서비스 Contract 선언
[ServiceContract]
public interface IHelloWorld
{
[OperationContract]
string SayHello();
}
// 서비스 타입 구현
public class HelloWorldWCFService : IHelloWorld
{
public string SayHello()
{
return "Hello WCF World !";
}
}
}
HelloWorldWCFService 클래스와 같이 WCF의 계약 인터페이스를 구현하는 클래스를 서비스 타입(service type) 이라 부르며 서비스 타입은 계약 인터페이스를 구현하기만 하면 된다. 서비스 타입은 계약 인터페이스와 달리 ServiceContract 와 같은 특성을 요구하지 않는다. 비록 서비스 타입의 작동 방식을 제어하기 위해 ServiceBehavior 와 같은 특성을 명시할 수 있지만, 반드시 이러한 특성을 명시해야 하는 것은 아니다.
좀 허무한 느낌이 들지만, 이로써 WCF 서비스의 구현이 끝났다. 비록 간단하지만 [리스트 2]는 WCF 서비스가 가져야 할 핵심적인 두 가지 요소를 적나라하게 보여준다. 즉, 서비스의 계약 인터페이스와 이 인터페이스를 구현하는 서비스 타입이 그것이다. 서비스 계약 인터페이스는 반드시 ServiceContract 특성과 OperationContract 특성이 명시되어야 하며, 서비스 타입은 계약 인터페이스를 구현하는 클래스이어야만 한다.
WCF 서비스 호스트 구현
WCF 서비스만을 구현해서는 아무것도 할 수 없다. 구현된 서비스를 호스팅 하는 서비스 호스트를 작성하고 구동해야만 실제로 클라이언트 호출을 처리하는 온전한 WCF 서비스가 될 수 있는 것이다. [리스트 2]에서 보았겠지만 서비스의 계약 인터페이스를 선언하고 이것을 구현하는 서비스 타입을 작성하는 것만으로는 클라이언트 호출 수신하고 처리할 수 없으며 [그림 3]과 같이 서비스 호스트(service host)에 의해 호스팅 될 때만 클라이언트의 요청을 받아들일 수 있는 것이다.
그렇다. 다음으로 해야 할 일은 서비스 호스트를 구현하는 것이다. 서비스 호스트는 ServiceHost 클래스를 통해 구현할 수 있다. 서비스 호스트가 어떤 서비스를 호스팅 하기 위해서는 이 서비스가 어떤 주소(address)를 갖는지 그리고 어떤 바인딩에 의해 클라이언트와 통신할 것인지를 알아야 한다. 따라서 서비스가 호스팅 되기 위해서는 서비스의 계약, 주소, 바인딩을 나타내는 서비스의 종점(endpoint)을 정의하고 이 서비스 종점을 ServiceHost에 추가하는 과정을 거치게 된다. 구체적인 코드를 차근차근 살펴보도록 하자.
서비스 호스트는 수행되는 어플리케이션이므로 앞서 작성해 놓은 라이브러리 프로젝트(DLL)로는 답이 안 나온다. 따라서 '수행' 가능한 어플리케이션 프로젝트를 만들어야 할 것이다. WinForm 어플리케이션 혹은 ASP.NET 어플리케이션 등 다양한 선택이 있을 수 있지만, 일단은 가장 간단한 Console 어플리케이션 프로젝트를 솔루션에 추가하도록 하자. '간단한' Hello World 예제 아닌가?
[그림 7]은 HelloWorldSample 솔루션에 서비스 호스트를 위해 Console 어플리케이션 프로젝트를 추가하는 화면 스냅샷이다. 꼭 이런 스냅샷을 보여줄 필요가 있는지도 의문시 되는 스냅샷이지만 그래도 Step-by-Step 형식을 약속했으니 그대로 따라 해 보도록 하자.
그림 7. 서비스 호스트를 위한 Console 어플리케이션 프로젝트 추가
호스트 어플리케이션 역시 WCF 런타임을 사용해야 하므로 WCF의 핵심 어셈블리인 System.ServiceModel 어셈블리를 참조하자. 이 어셈블리뿐만 아니라 Hello World 서비스를 호스팅 하기 위해서는 서비스의 계약 인터페이스인 IHelloWorld 인터페이스와 서비스 타입인 HelloWorldWCFService 클래스도 사용해야 하므로 작성해 놓은 HelloWorldService 프로젝트에 대한 참조 역시 추가 되어야 할 것이다. [그림 8]은 System.ServiceModel 어셈블리와 HelloWorldSerivce 프로젝트에 대한 참조가 추가된 HelloWorldHost 프로젝트의 참조 구성을 보여주고 있다.
그림 8. HelloWorldHost 프로젝트의 참조 구성
프로젝트 구성을 마쳤다면 이젠 서비스 호스트에 대한 코드를 작성하는 일만 남았다. 서비스 호스트는 System.ServiceModel 네임스페이스의 ServiceHost 클래스를 직접 사용하거나 이 클래스에서 파생된 클래스를 사용하면 된다.
ServiceHost 클래스는 여러 개의 생성자를 가지고 있지만, 일단 여기서는 서비스 타입을 매개변수로 취하는 생성자 하나 만을 살펴보겠다. 서비스 호스트는 자신이 호스트 할 WCF 서비스의 서비스 타입을 첫 번째 매개변수로 취하며, 두 번째 매개변수는 서비스가 사용할 주소의 베이스 주소(base address)이다. 잠시 후에 설명할 서비스 종점(endpoint)이 상대 경로를 사용하면 이 상대 경로는 베이스 주소로부터 계산되게 된다. 예를 들어, 베이스 주소가 http://localhost/chapter1 일 때, 서비스 종점에 test.svc 란 주소를 사용하면 실제 서비스의 주소는 http://localhost/chapter1/test.svc 가 된다는 얘기가 되겠다. 물론 베이스 주소를 사용하지 않고 서비스 종점에 절대 주소를 사용해도 상관은 없다. 서비스의 주소에 대해서는 나중에 조금 더 이야기 하도록 하겠다.
일단 ServiceHost 클래스의 인스턴스를 생성하고 나면 서비스의 종점을 서비스 호스트에 추가해야만 한다. WCF 서비스는 서비스의 주소, 바인딩, 계약으로 구성된 서비스 종점을 1개 이상 노출하며 이 서비스 종점을 통해 클라이언트와 통신하도록 되어 있으므로 서비스 호스트가 서비스를 호스팅 함에 있어서 1개 이상의 서비스 종점을 노출해야 함은 매우 당연하다고 할 수 있다. [그림 2]에서 서비스의 주소, 바인딩, 계약과 서비스 종점 사이의 관계를 간단하게나마 이미 설명했으므로 곧바로 코드를 살펴보자.
리스트 3. 서비스 호스트 코드 구현
using System.ServiceModel;
using HelloWorldService;
namespace HelloWorldHost
{
class HostApp
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(HelloWorldWCFService),
new Uri("http://localhost/wcf/example/helloworldservice"));
host.AddServiceEndpoint(
typeof(IHelloWorld), // service contract
new BasicHttpBinding(), // service binding
"");
host.Open();
Console.WriteLine("Press Any key to stop the service");
Console.ReadKey(true);
host.Close();
}
}
}
[리스트 3]은 매우 간단한 WCF 서비스 호스트 구현인 HostApp 클래스의 구현을 보여주고 있다. 먼저 ServiceHost 클래스의 인스턴스를 생성하고 있는데, 호스팅 하고자 하는 서비스 타입(HelloWorldWCFService 클래스)과 서비스의 베이스 주소를 생성자의 매개변수로 전달하고 있음을 눈 여겨 보자. 여기에서 서비스의 주소가 왜 http 프로토콜을 쓰는지, 그리고 베이스 주소와 실제 서비스의 주소가 어떤 관계를 가지는지는 잠시 후에 설명할 것이므로 그냥 그러려니 하고 넘어가도록 하자.
일단 서비스 호스트의 인스턴스가 생성되고 나면 서비스의 종점을 서비스 호스트에 추가해야 한다. 앞서 설명한 대로 서비스의 종점은 주소, 바인딩, 계약으로 구성되므로 ServiceHost 클래스의 AddServiceEndpoint 메쏘드를 호출할 때, Hello World 서비스의 계약인 IHelloWorld 인터페이스를 매개변수로 주었으며, 이 서비스가 사용할 바인딩으로써 BasicHttpBinding 클래스의 인스턴스를, 그리고 서비스의 주소는 공백(blank) 문자열을 사용하여 서비스 호스트에 명시된 서비스의 베이스 주소를 그대로 사용하도록 작성하였다.
BasicHttpBinding은 HTTP 프로토콜을 사용하는 바인딩으로써 기존 ASP.NET 웹 서비스와 호환성을 위해 WCF가 기본적으로 제공하는 바인딩이다. 이 바인딩은 기본적으로 웹 서비스 보안을 사용하지 않을 뿐 더러, 트랜잭션이나 콜백(callback)과 같은 WCF에서 제공하는 고급 기능을 사용할 수 없는 매우 간단한 바인딩이다. BasicHttpBinding 외에도 WCF는 HTTP 프로토콜을 사용하면서도 트랜잭션 등 다양한 기능을 가지고 있는 WSHttpBinding, WSDualHttpBinding 등을 제공하며, TCP 혹은 명명된 파이프 프로토콜을 기반으로 하여 고성능을 자랑하는 NetTcpBinding, NetNamedPipeBinding 과 비동기 통신이 가능한 NetMsmqBinding, P2P(Peer-to-Peer) 통신이 가능한 NetPeerTcpBinding 등을 제공하고 있다. 이들 바인딩들에 대해서는 잠시 후에 다시 한번 언급될 것이다.
서비스 종점이 어떤 바인딩을 사용하는가에 따라서 서비스의 주소는 달라지게 된다. HTTP 기반의 BasicHttpBinding, WSHttpBinding, WSDualHttpBinding 과 같은 바인딩이 사용되면 서비스의 주소는 http로 시작하는 URI가 서비스의 주소로 사용되어야 하며, NetTcpBinding이 사용되면 net.tcp 로 시작하는 URI가 서비스의 주소가 사용되어야 한다. 바인딩과 서비스의 주소와의 관계는 추후에 다시 한번 상세히 언급하도록 하겠다.
서비스의 종점이 추가되었으면 이제 서비스는 실제로 클라이언트의 호출을 받을 준비가 끝났다. ServiceHost 클래스의 Open 메쏘드를 호출함으로써 서비스 호스트는 리스너(listener)를 구동하고 클라이언트 호출을 수신하기 시작한다. Open 메쏘드는 WCF 런타임이 서비스를 위해 리스너와 디스패처(dispatcher) 등 다양한 준비를 하도록 구성한 후에 곧바로 제어를 반환한다. 즉, Open 메쏘드를 호출한 호출자(caller)는 블로킹되지 않으며 Open 메쏘드가 반환되면 구성된 WCF 서비스는 서비스를 시작했다는 말이 된다. 클라이언트 호출을 실제로 처리하는 것은 디스패처에 의해 선택된 작업 쓰레드(worker thread)가 될 것이므로 서비스 호스트를 설정하고 구동한 쓰레드는 다른 작업을 수행할 수 있다([그림 3] 참조). 이 예제의 경우에는 단순히 키보드에서 아무 키나 눌려지기를 기다리도록 구성했다.
구동된 서비스는 ServiceHost 클래스의 Close 메쏘드 호출에 의해 종료된다. Close 메쏘드가 호출되면 WCF 런타임은 더 이상의 클라이언트 호출을 수신하지 않게 되며, 이미 수신된 클라이언트 호출의 처리가 끝나기를 기다린다. 클라이언트의 모든 호출이 처리되면 구성한 리스너와 디스패처를 제거하고 할당한 시스템 자원과 닷넷 런타임의 자원을 모두 해제한 후 Close 메쏘드는 제어를 반환하게 된다.
WCF Service 테스트
작성된 서비스 호스트는 이제 구동이 가능한 WCF 서비스 어플리케이션이 되었다. 실제로 서비스를 수행하여 제대로 작동하는가를 테스트 해 보자. HelloWorldHost 어플리케이션을 컴파일 하고 어플리케이션을 수행시켜보면 [그림 9]와 같이 매우 썰렁한 메시지만을 나타낼 것이다.
그림 9. 수행중인 HelloWorldHost 어플리케이션
WCF 관련 방화벽 팁
네트워크 통신에 관련된 프로그래밍을 공부하다 보면 항상 귀찮게 따라다니는 것이 바로 Windows의 방화벽이다. 기본적으로 WCF 서비스를 일반적인 EXE 어플리케이션으로 작성하여 HTTP 포트나 TCP의 일정 포트를 리스닝을 하면 떡하고 방화벽 경고가 나타나기 마련이다. 방화벽 경고가 나타나면 필요에 따라서 방화벽을 열고 닫기를 수행하면 별 문제가 없다.
하지만 이 책에서처럼 다수의 예제를 작성해 볼 것이라면 문제가 좀 커진다. 기본적으로 Windows 방화벽은 특정 포트가 아닌 프로그램 단위로 방화벽을 열어주거나 닫기 때문에 예제 프로그램을 100개 작성해 보았다면 방화벽을 제어하는 예외 엔트리 역시 100개가 생기게 된다. 방화벽 엔트리가 많으면 많을수록 방화벽이 검사해야 할 예외상황이 많다는 얘기이므로 네트워크 성능을 저해할 뿐만 아니라 다수의 레지스트리 설정항목으로 시스템 전체의 성능 저하도 유발할 수 있다.
필자가 사용하는 방법은 HTTP 포트인 80 포트와 WCF가 TCP를 사용할 때 기본적으로 사용하는 포트인 808 포트에 대한 방화벽 예외 설정을 만들어 두는 것이다. 굳이 이들 포트를 열어둘 필요는 없다. 예외 엔트리만 만들어 두면 된다. 이렇게 하면 달당 두 개의 방화벽 예외 엔트리로 다수의 WCF 서비스 예제 코드를 커버할 수 있게 된다. 따라서 매번 예제 프로그램을 작성할 때마다 귀찮게 등장하는 방화벽 대화상자를 피할 수 있다.
아직 실망하긴 이르다. HelloWorldHost 어플리케이션은 클라이언트의 호출을 기다리는 중이므로 클라이언트를 작성하고 실제 서비스 호출을 수행해야만 반응을 시작할 것이다. 그렇다면 클라이언트를 작성하기 전에 서비스를 테스트할 방법은 없을까? 물론 있다. ASP.NET을 통해 웹 서비스를 작성할 때도 ASMX 에 대한 URL을 브라우저를 통해 브라우징 하면 웹 서비스에 대한 간략한 설명과 테스트를 수행할 수 있었다. 마찬가지로 WCF 역시 서비스의 URL을 브라우저를 통해 브라우징 하면 서비스의 수행 상태를 간단하게나마 테스트 할 수 있다.
우리가 작성한 Hello World 서비스의 주소는 http://localhost/wcf/example/helloworldservice 가 된다. 서비스 호스트를 구성할 때 서비스의 베이스 주소로 http://localhost/wcf/example/helloworldservice 가 사용되었으며 서비스의 종점을 추가할 때 상대 경로로서 빈 문자열을 사용하였으므로 서비스의 베이스 주소가 그대로 서비스 종점의 주소가 되었기 때문이다. 이 주소를 브라우저의 주소 입력 창에 Copy & Paste 신공을 사용하여 입력하면 [그림 10]과 같은 결과를 얻을 수 있다. 아무런 오류 없이 [그림 10]과 같은 결과를 얻으면 일단 서비스가 정상적으로 구동되어 클라이언트 호출을 수신할 수 있다는 얘기가 되겠다. 물론 서비스가 제공하는 각 메쏘드가 정상적으로 작동한다고는 얘기할 수는 없지만 적어도 서비스 호스트가 서비스를 호스팅 하여 구동 중이라는 것은 확인할 수 있는 것이다. 만약 이러한 결과가 나오지 않고 오류가 발생한다면 [리스트 2]와 [리스트 3]을 다시 한번 유심히 살펴보고 잘못 베낀 것이 없나 확인해 보아야 한다.
WCF & Windows Vista TIP
Windows Vista는 보안이 크게 강화된 운영체제이다. 특히 Windows Vista의 UAC(User Account Control)는 사용자 계정에서 관리자 권한을 뺏어버리는 무서운 기능으로써 코드를 작성할 때 자주 필요한 관리자 권한이 없어서 다양한 오류를 유발하곤 한다. WCF도 예외는 아니어서 HTTP 프로토콜을 통해 HTTP 메시지를 리스닝 하기 위해서는 관리자 권한을 요구한다. 왜냐하면 이 방법인 HTTP.SYS 커널 드라이버를 액세스하기 때문이다.
가장 쉬운 방법은 UAC를 꺼버리는 것이지만 결코 권장할 만한 방법이 아니다. UAC 때문에 발생할 수 있는 문제를 개발자가 미리 깨닫지 못하기 때문에 UAC에 대한 대처를 전혀 하지 않기 때문이다. 개발자라면 반드시 UAC를 켜둔 채로 개발을 하여 UAC로 인해 발생할 수 있는 다양한 문제를 파악하고 대처하는 것이 좋다. 또 한가지는 Visual Studio를 관리자 권한으로 구동하는 것이다. Visual Studio를 통해 개발할 때 프로그램을 구동하면 항상 Visual Studio에 의해 프로그램이 구동되므로 그 프로그램 역시 관리자 권한을 갖기 때문이다. 하지만 Visual Studio를 사용하지 않고 직접 프로그램을 구동시키면 역시 권한 오류가 발생하게 될 것이다.
필자가 즐겨 사용하는 방법은 HTTP 커널 드라이버에게 특정 URI 에 대해 사용권한을 필자의 계정에게 허용하도록 설정한다. 예를 들어 http://localhost/wcf/example 로 시작하는 모든 URL에 대해 관리자가 아니더라도 특정 사용자 계정에게 리스닝을 허용하도록 하는 것이다. 그리고 예제 코드를 작성할 때 HTTP를 사용하면 항상 /wcf/example 로 시작하는 URI를 사용하는 것이다.
다음은 필자가 Windows Vista에서 ksyu33 이란 계정에게 80 포트로 수신되고 URI가 /wcf/example 로 시작하는 모든 HTTP 수신을 허용하도록 하는 netsh.exe 커맨드 이다. Windows Server 2003은 HttpCfg.exe 란 유틸리티를 사용하지만 UAC 기능이 없기 때문에 관리자 계정이라면 굳이 이러한 설정을 해주지 않아도 된다.
netsh http add urlacl url=http://+:80/wcf/example user=ksyu33
위와 같은 명령을 수행하면 관리자 권한으로 작동되지 않는 프로그램일지라도 ksyu33 이란 계정이 수행하는 프로그램은 http://localhost/wcf/example/service.svc, http://svrname/wcf/example/subdir/some 등의 URL을 리스닝 하는 WCF 서비스 프로그램을 작성할 수 있다. 이 책에서 등장하는 예제 코드들 중에서 HTTP를 사용하는 대부분의 예제는 서비스의 주소로써 http://localhost/wcf/example 로 시작하는 주소를 사용한다.
Windows Vista에서는 이런 저런 귀찮은 설정이 필요하곤 하다. 하지만 멀지 않아 Windows Vista가 대세가 될 날이 있을 것이다. 비록 그때가 먼 훗날이 될지라도 낮은 보안 권한만으로 프로그램을 작성하는데 익숙해 진다면 좋은 습관이 될 것이다. 속는 셈치고 필자를 한번 믿어보기 바란다.
그림 10. WCF 서비스 작동에 대한 테스트