'.net'에 해당되는 글 6건

  1. 2008.09.05 .NET Enterprise Services 성능
  2. 2008.09.05 .NET의 Enterprise Service(COM+) 이해
  3. 2008.08.27 웹서비스 .net compact framework 2,0 사용시 웹참조 동적으로 하기
  4. 2008.08.20 닷넷 가비지 컬렉터의 동작원리
  5. 2008.04.22 ActiveX 없이 간단한 AES 암호화 시스템 적용하기
  6. 2008.02.27 입문자에게... "프로그램 공부 어떻게 해야 하나요?"
2008.09.05 11:04

.NET Enterprise Services 성능

원문 : http://www.microsoft.com/korea/msdn/library/ko-kr/dev/dotnet/entsvcperf.aspx

.NET Enterprise Services 성능

Richard Turner, 프로그램 관리자, XML Enterprise Services
Larry Buerk, 프로그램 관리자, XML Enterprise Services
Dave Driver, 소프트웨어 디자인 엔지니어, XML Enterprise Services

Microsoft Corporation

2004년 3월

적용 대상:
   COM+ 구성 요소
   Microsoft .NET Enterprise Services

요약: 다른 활성화 및 호출 패턴에 적용될 때의 원시 COM+ 및 .NET Enterprise Services 구성 요소 성능을 확인합니다. .NET Enterprise Services 구성 요소를 C++의 COM+ 구성 요소처럼 빨리 실행하기 위한 지침과 함께 고성능 .NET Enterprise Service 구성 요소를 만드는 데 도움이 되는 주요 권장 사항을 살펴봅니다(45페이지/인쇄 페이지 기준).

관련 EnterpriseServicesPerf.exe 코드 샘플을 다운로드하십시오.

목차

소개
관리되는 코드로 마이그레이션해야 하는 이유
코드 변경 정도 지정
.NET Enterprise Services에 COM+ 연결
.NET Enterprise Services와 COM+ 성능 비교
테스트 결과 및 분석
결론
부록 1: 성능 권장 사항
부록 2: "Indigo" 및 .NET의 동향
부록 3: 분산 트랜잭션이 성능에 미치는 영향
부록 4: 참고 자료
부록 5: 성능 테스트 소스 코드
부록 6: 테스트 결과

소개

COM+ 코드를 "원시" Visual C++ 또는 Visual Basic 6에서 관리되는 .NET Enterprise Services 구성 요소로 이동할 것을 고려 중인 개발자는 다음과 같은 고민 사항이 있을 수 있습니다.

  • 왜 관리되는 코드로 전환해야 하는가?
  • 코드를 얼마나 변경해야 하는가?
  • Enterprise Services 구성 요소는 어떻게 수행되는가?
  • COM+ 및 .NET Enterprise Services가 제시하는 미래는 어떠한가?

이 문서에서는 특히 성능에 대한 질문을 위주로 위 주제를 다룰 것입니다. 이러한 주제에 대한 자세한 내용은 부록 4: 참고 자료에 있는 리소스를 참조하십시오.

이 문서는 COM+ 구성 요소를 개발하고 코드를 .NET Enterprise Services로 마이그레이션할 것을 고려 중인 개발자와 설계자를 대상으로 합니다.

관리되는 코드로 마이그레이션해야 하는 이유

개발자가 .NET에서 코드를 개발해야 하는 이유는 여러 가지입니다. 다음은 코드 개발의 몇 가지 이점입니다.

  • 향상된 개발자 생산성: 개발자들은 종종 .NET을 사용하여 개발할 때 작성해야 하는 "통로 코드"가 훨씬 적기 때문에 응용 프로그램 논리를 작성하는 데 더 많은 신경을 쓸 수 있습니다. 또한 대부분의 개발자는 .NET에서 제공하는 명확하고 일관되게 구성된 풍부한 리소스 라이브러리 덕분에 다른 기술과 비교하여 훨씬 빨리 배울 수 있습니다.
  • 향상된 코드 안정성 및 보안: 개발자는 원시 코드보다는 .NET을 사용할 때 안정적이고 보안된 코드를 더욱 쉽게 작성할 수 있습니다. 그 이유는 코드 액세스 보안 및 CLR(공용 언어 런타임) 같은 기능 때문입니다. 이러한 기능은 NET 코드가 다른 실행 중인 코드에 의도하지 않은 영향을 미치는 것을 방지하고 해커가 .NET 코드를 사용하여 환경을 방해하거나 제어할 수 있는 기회를 줄이는 데 도움이 됩니다.
  • 향상된 성능 및 확장성: 개발자는 .NET으로 마이그레이션할 때 코드의 성능과 확장성이 향상되는 것을 알 수 있습니다. 모든 .NET 언어가 다중 스레딩 같은 기능을 지원하고 활용하기 때문입니다.
  • XCOPY 배포: 대부분의 .NET 응용 프로그램에서는 필요한 파일을 하드 드라이브의 폴더로 복사하고 공유 구성 요소를 운영 체제에 선택적으로 등록하기만 하면 배포가 이루어집니다. 이는 다른 응용 프로그램에서 일반적으로 사용하는 것보다 훨씬 명쾌한 배포 전략입니다.

개발자가 .NET으로 마이그레이션하기를 원하는 10가지 이유에 대해서는 Top 10 Reasons for Developers to Use the .NET Framework 1.1 을 참조하십시오.

.NET은 시스템 관리자에게도 이점을 제공합니다. 관리자가 .NET으로 이동하기를 원하는 10가지 이유가 기록된 목록을 보려면 Top 10 Reasons for Systems Administrators to Use the .NET Framework 1.1 을 참조하십시오.

일반 C++ 코드를 관리되는 C++로 마이그레이션하는 방법에 대한 소개는 Managed Extensions for C++ Migration Guide의 Introduction to Wrapping C++ Classes 를 참조하십시오.

코드 변경 정도 지정

대부분의 경우 COM+ 코드를 .NET Enterprise Services에 연결하려면 COM+ 구성 요소가 어떠한 언어로 개발되었는지를 비롯한 다수의 요인에 따라 몇 가지 수동 작업이 필요합니다. 예를 들어, Visual Basic 6 개발자는 앞으로 나올 Visual Studio 2005의 도구에서 서브루틴 및 함수의 본문을 수정하지 않고도 클래스 정의 및 메서드 서명을 변환할 수 있는 몇 가지 지원을 받게 됩니다(다른 변환된 메서드에 대한 호출은 제외).

C++ 개발자는 COM+ 코드에서 .NET 코드로 변환하는 작업을 대부분 수동으로 수행해야 하지만, 대부분의 통로가 ATL(Active Template Library) 같은 클래스 라이브러리 대신 CLR에 구현되기 때문에 변환된 코드가 좀 더 간결해지는 것을 확인할 수 있습니다. C++ 개발자는 응용 프로그램을 연결할 언어를 선택할 수도 있습니다. 예를 들어, 기존 코드 기반을 최대한 활용하기 위해 관리되는 C++를 선택하거나, 더욱 간결한 코드를 위해 C#을 선택할 수 있습니다.

.NET Enterprise Services에 COM+ 구성 요소 연결

기존 COM+ 코드를 Visual Basic 6 및 Visual C++ 같은 원시 프로그래밍 언어와 도구로부터 마이그레이션하는 개발자는 .NET 코드로 완벽하게 변환하기 위해 몇 가지 기존 코드를 수정해야 합니다. 여기에 동반되는 작업 양은 사용할 수 있는 기존 코드 기반 및 도구에 따라 다릅니다. 다음 표에는 코드를 .NET으로 마이그레이션할 때 고려할 옵션이 요약되어 있습니다.

변환하기 전의 언어 변환한 후의 언어 코드 변환 도구 사용 가능 여부 필요한 코드 변환 작업
Visual Basic 6 Visual Basic .NET 예(Visual Studio 2005) 대부분의 Visual Basic 6 코드는 Visual Basic .NET에 직접 연결됩니다. Visual Studio 2005의 Visual Basic 6 코드 마이그레이션 도구는 대부분의 클래스와 메서드 선언 및 형식을 Visual Basic .NET 구문으로 변환합니다.
Visual C++ Visual C++ .NET 아니요 C++ .NET은 특히 원시 코드와 .NET 사이에서 상호 작동하는 코드를 작성하는 데 유용합니다.
Visual C++ Visual C# 아니요 C# 구문은 많은 면에서 C++와 비슷합니다. 변환을 수행하려면 개발자의 특정 작업이 필요합니다.

.NET 특성

COM+ 구성 요소를 설치한 후에는 구성 요소 서비스 스냅인 도구를 사용하여 수동으로 구성하거나 스크립트 또는 코드를 통해 구성해야 합니다.

예를 들어, 구성 요소에 트랜잭션 지원이 필요하다고 표시하고 각 구성 요소의 AddSale() 메서드가 오류 없이 완료될 경우 트랜잭션을 자동으로 커밋하도록 지정하는 상황을 가정합니다. 그러기 위해서는 구성 요소 및 필요한 메서드의 속성을 COM+ 구성 요소 서비스 관리 콘솔에서 수동으로 구성해야 합니다.

  1. 구성 요소 서비스 관리 콘솔을 열고 올바른 응용 프로그램 및 구성 요소를 탐색합니다.

  2. 이 구성 요소의 속성을 열고 트랜잭션 탭을 클릭한 다음 필수를 클릭합니다.

  3. 확인을 클릭하고 구성 요소 탐색기를 통해 ATLPerfTests 개체의 AddSale() 메서드를 찾습니다.

  4. AddSale() 메서드를 마우스 오른쪽 단추로 클릭하고 메서드의 속성을 연 다음 이 메서드가 반환하면 자동으로 이 개체 비활성화 확인란을 선택합니다.

관리자는 일반적으로 보안 및 ID 설정 같은 배포 지향 설정이나 역할 구성원 및 재활용 같은 런타임 지향 설정을 구성합니다. 개발자는 트랜잭션 지원과 같은 구성 요소의 개발 지향 기능을 구성합니다. 그러나 COM+에서는 개발자가 코드 내에서 구성 요소의 구성 방법을 지정하기가 어렵습니다. Visual Basic 개발자는 구성 요소에 필요한 트랜잭션 지원을 어느 정도 지정할 수 있지만 C++ 개발자는 그렇지 못합니다. COM+ 구성 요소를 안정적이면서 반복적으로 설치하기 위해서는 스크립트, 설치 관리자 응용 프로그램 또는 설치 지침을 작성해야 합니다.

.NET은 개발자가 구성 요소의 코드 내에서 구성 요소에 필요한 서비스와 해당 서비스의 구성 방법을 지정할 수 있도록 함으로써 구성 요소 구성을 단순화합니다. 구성 요소가 설치되면 플랫폼에서 구성 설정을 자동으로 구성하지만, 이러한 구성 설정은 설치 후에 변경할 수 있습니다.

COM+에서는 설치 후에 구성 설정을 변경할 수 있기 때문에 이러한 속성을 다수 변경할 때는 매우 주의를 기울여야 합니다. 예를 들어, 보안 설정을 변경하면 개체를 인스턴스화하고 개체의 메서드를 호출할 수 있는지의 권한 지정에 영향을 미칠 수 있습니다. 반면에 트랜잭션 지원을 제거하면 구성 요소가 안정적이지 못하고 예측 불가능해질 수 있으며 구성 요소의 데이터가 손실되거나 손상될 수도 있습니다.

개발자는 특성(C# 및 C++에서는 [attribute]와 같이 대괄호 안에 표시되고, Visual Basic .NET에서는 <attribute>와 같이 꺾쇠괄호 안에 표시됨)을 사용하여 어셈블리, 응용 프로그램, 구성 요소 또는 메서드의 요소를 구성합니다. 예를 들어, 아래 코드에서는 SimpleTest 구성 요소에 트랜잭션이 필요하며, AddSale() 메서드가 오류 없이 완료될 경우 트랜잭션이 자동으로 완료된다는 것을 보여줍니다.

C#

   [Transaction(TransactionOption.RequiresNew)]
public class SimpleTest: ServicedComponent
{
...
[AutoComplete]
public void AddSale(int orderNumber, int storeID, int
titleID, int qty)
{
...
}
...
}

Visual Basic .NET

<Transaction(TransactionOption.RequiresNew)> _
Public Class VBTestObject : Inherits ServicedComponent
...
<AutoComplete> _
Public Function Sum(ByVal number1 As Integer, ByVal number2
As Integer) As Integer
...
End Function
...
End Class

다음 표에는 Enterprise Services 구성 요소에 적용할 수 있는 주로 사용되는 특성이 들어 있고, 설치 후에 특성을 안전하게 변경할 수 있는지 여부가 표시되어 있습니다.

특성 범위 기본 소유자 실행 중에 안전하게 수정 가능한지의 여부
ApplicationAccessControl  어셈블리 개발자 아니요.
개발자가 응용 프로그램의 보안 방법에 대한 하드 종속성 또는 묵시적 종속성을 사용하여 코드를 작성했을 수 있습니다.

액세스 제어를 낮추면 시스템 보안이 손상될 수 있습니다.

ApplicationActivation  어셈블리 관리자 예. 그러나 주의해야 합니다.
라이브러리 또는 서비스로 변경하거나 이로부터 변경하면 성능에 영향을 미칠 수 있으며 대기 중인 구성 요소가 손상될 수 있습니다.
ApplicationID  어셈블리 개발자 예. 그러나 주의해야 합니다.
이 특성을 변경하면 하드 코딩된 구성 요소 등록 도구에 영향을 미칠 수 있습니다.
ApplicationName  어셈블리 개발자 예. 그러나 주의해야 합니다.
이 특성을 변경하면 하드 코딩된 구성 요소 등록 도구에 영향을 미칠 수 있습니다.
ApplicationQueuing  어셈블리 개발자 아니요.
AutoComplete  메서드 개발자 아니요.
아래의 "Transaction"을 참조하십시오.
ComponentAccessControl  클래스 개발자 아니요.
개발자는 구성 요소의 보안 방법에 대한 하드 종속성 또는 묵시적 종속성을 사용하여 코드를 작성했을 수 있습니다.

액세스 제어를 낮추면 시스템 보안이 손상될 수 있습니다.

ConstructionEnabled  클래스 관리자 예.
생성자 문자열을 변경할 수는 있지만, 설정/해제 상태를 전환하지는 마십시오.
Description  어셈블리
클래스
메서드
인터페이스
관리자 예.
EventTrackingEnabled  클래스 관리자 예.
InterfaceQueuing  클래스
인터페이스
개발자 아니요.
JustInTimeActivation  클래스 개발자 아니요.
MustRunInClientContext  클래스 개발자 아니요.
구성 요소가 클라이언트의 컨텍스트와 호환되지 않을 수도 있습니다.
ObjectPooling  클래스 개발자 예. 풀 설정은 변경할 수 있습니다.
구성 요소가 손상될 수 있기 때문에 개체 풀링 설정/해제 상태를 전환하지는 마십시오.
PrivateComponent  클래스 개발자 아니요.
이 구성 요소를 공개적으로 호출할 수 없도록 개발자가 특별히 지정한 것입니다. 구성 요소가 알 수 없는 사용 패턴을 지원하는지에 대한 테스트를 받지 않았을 수도 있습니다. 공개되는 경우 심각한 보안 위협을 초래할 수 있습니다.
SecurityRole - 역할 이름  어셈블리
클래스
인터페이스
개발자 아니요. 역할을 제거하지 마십시오.
개발자가 지정된 역할에 대한 명시적 종속성 또는 역할 존재에 대한 묵시적 예외를 사용하여 코드를 작성했을 수 있습니다.
SecurityRole - 역할 구성원  어셈블리
클래스
인터페이스
관리자 예. 그러나 주의해야 합니다.
액세스 제어를 너무 광범위하게 공개하면 시스템의 보안이 손상되고, 너무 제한되게 공개하면 액세스가 과도하게 제한될 수 있습니다.
Synchronization  클래스 개발자 아니요.
Transaction  클래스 개발자 아니요.
구성 요소의 트랜잭션 지원을 수정하면 시스템의 안정성과 무결성이 손상될 수 있습니다.

위 표는 구성 요소 개발자가 기본적으로 소유하는 구성 요소와 관리자가 기본적으로 소유하는 구성 요소도 보여 줍니다. 코드에 미치는 영향을 자세히 알고 있는 경우가 아니라면 개발자의 설정을 변경하지 않는 것이 좋습니다. 개발자가 지정한 보안 관련 특성을 관리자가 변경할 수는 있지만, 이 경우에는 구성 요소 또는 응용 프로그램에 대한 액세스를 충분히 제한하되 너무 과도하게 제한하지는 않도록 주의를 기울여 보안을 구성해야 합니다.

특성을 통해 개발자는 자신의 구성 요소에 대한 구성 요구 사항을 간단하고 효율적으로 지정하면서 설치 후에 관리자가 구성 설정을 변경할 수 있도록 합니다. Enterprise Services가 제공하는 특성에 대한 자세한 내용은 .NET Framework Class Library 를 참조하십시오.

.NET Enterprise Services와 COM+ 성능 비교

Enterprise Services의 성능을 COM+와 비교하여 측정하기 위해 다음 언어로 구성 요소를 만들었습니다.

  • Visual C++ .NET 및 ATL COM+
  • Visual Basic 6 COM+
  • C# 및 .NET Framework 1.1 Enterprise Services
  • Visual Basic .NET 및 .NET Framework 1.1 Enterprise Services

각 구성 요소에는 두 개의 공용 메서드가 포함되어 있습니다.

  • Sum(): 이 간단한 메서드는 두 숫자를 합해 디스크 또는 데이터베이스 액세스 작업을 수행하지 않는 간단한 작업을 시뮬레이트합니다.
  • AddSale(): 이 일반적인 메서드는 트랜잭션되며, 테이블에 레코드를 삽입하고 반환 전에 트랜잭션을 완료하는 개인 메서드인 InsertSale()을 호출합니다. 이 메서드는 일반적인 비즈니스 응용 프로그램 작업을 수행하는 "실제" 메서드의 성능 특징을 보여 줍니다.

그 다음에는 각 구성 요소에 대해 다음 테스트를 수행한 테스트 프로그램을 만들었습니다.

  • 반복적인 만들기/호출/릴리스: 이 테스트는 개체를 만들고 호출하고 릴리스하는 과정을 반복적으로 수행합니다.
  • 만들기/반복 호출/릴리스: 이 테스트는 개체를 인스턴스화하여 수천 번 호출한 다음 마지막에 개체를 릴리스합니다.

테스트 프로그램에서는 각 구성 요소에 대해 두 가지 테스트를 모두 실행했으며 결과를 쉼표로 분리된 파일에 작성하는 고해상도 타이머를 사용하여 각 테스트를 수행하는 데 걸린 시간을 측정했습니다. 이 파일의 결과를 Microsoft Excel로 가져와서 분석했습니다. 각 구성 요소에 대한 코드 목록은 부록 5: 성능 테스트 소스 코드를 참조하십시오.

테스트는 다음 표에 표시된 구성으로 설정된 컴퓨터에서 실행되었습니다.

  컴퓨터 1: 서버 컴퓨터 컴퓨터 2: 클라이언트 컴퓨터/단일 컴퓨터
CPU Dual Pentium 4 Xeon 3.06GHz Dual Pentium 4 Xeon 2.8GHz
RAM 1GB 1GB
디스크 로컬 SCSI 로컬 SCSI
네트워크 기가비트 이더넷 기가비트 이더넷
OS 및 .NET Windows Server™ 2003
.NET Framework 1.1
Windows Server 2003
.NET Framework 1.1

다른 하드웨어에서 테스트 응용 프로그램을 실행할 때 나타나는 특정 결과는 아래에 보고된 결과는 물론, 서로 간에도 다를 수 있습니다. 그러나 각 결과는 여기에 소개된 결과와 비례해야 합니다.

테스트 결과 및 분석

다음 절에서는 이전에 논의한 코드에 대해 실행한 성능 테스트의 결과를 분석합니다. 이 결과는 위에 나열된 하드웨어와 소프트웨어에서 테스트를 실행하여 얻은 것입니다. 모든 결과의 목록은 부록 6: 테스트 결과에 포함되어 있습니다.

결과를 보여 주는 다음 차트에서 차트 막대 길이가 길거나 숫자가 클수록 성능이 뛰어난 것입니다.

개체 활성화 및 삭제 성능

먼저 C++ 및 Visual Basic 6을 사용하여 개발한 원시 COM+ 구성 요소의 성능을 통해 COM+ 인프라가 어떻게 수행되는지를 살펴보겠습니다. 아래 차트는 개체 만들기, 간단한 메서드 호출 및 개체 릴리스를 반복적으로 수행함으로써 얻은 초당 호출 수를 보여 줍니다.

MTS(Microsoft Transaction Server) 1.0의 원래 디자이너는 이와 비슷한 데이터를 보고서 호출을 배달하는 데 필요한 인프라, 즉 프록시, DCOM(또는 프로세스간) 채널, 스텁 및 컨텍스트를 설정함으로써 프로세스 간 및 컴퓨터 간 활성화 시간에 큰 영향을 미칠 수 있다는 것을 깨달았습니다. 이 점이 다음을 수행하는 JIT(Just In Time) 활성화를 디자인하게 된 기본적인 동기였습니다.

  • 서버 구성 요소가 반환 이전에 SetComplete() 또는 SetAbort()를 호출하여 자체 수명 주기를 제어할 수 있도록 합니다.
  • 여러 메서드 호출을 사용하여 DCOM 통로를 설정하는 부담을 줄입니다.

다음 차트는 JIT 활성화를 활용하는 수정된 테스트를 단일 개체를 만들고 두 숫자를 합하는 간단한 메서드를 반복적으로 호출한 다음 마지막에 개체를 릴리스하는 방법으로 실행할 경우 발생하는 상황을 보여 줍니다.

이 결과는 JIT 활성화를 사용할 때 초당 호출 수 면에서 성능이 크게 향상되는 것을 보여 줍니다. JIT 활성화 및 Visual Basic 6을 사용하면 JIT 활성화를 사용하지 않는 C++보다 거의 33배 빠른 결과를 얻을 수 있습니다. JIT가 활성화된 Visual Basic 6에서는 초당 호출 수가 약 8600인 반면 JIT가 활성화되지 않은 Visual C++에서는 초당 호출 수가 약 261입니다.

작업을 수행하는 데 필요한 통로를 설정한 이후 컴퓨터 간 호출을 수행하면 네트워크가 성능에 큰 영향을 미치게 됩니다. 이 경우 Visual Basic 6 및 C++는 성능이 거의 비슷하지만, 그래도 Visual Basic 6가 C++보다 88% 빨리 수행됩니다.

Enterprise Services에서는 필요한 통로를 설정하는 추가 작업이 수행됩니다. 특히 개체를 생성하고 릴리스하기 위한 추가 호출이 필요합니다. 따라서 개체에서 아무런 작업을 수행하지 않고 단순히 개체를 만들었다 제거하는 경우를 비교할 때 이러한 추가 왕복 부담이 성능 비교에 큰 영향을 미치게 됩니다. Visual Studio 2005에서는 Enterprise Services가 향상되어 이러한 활성화 왕복 중 하나가 제거되었기 때문에 "활성화/단일 호출/릴리스" 패턴을 사용할 때 .NET Framework 1.1과 비교하여 성능이 20-30% 향상됩니다. 그러나 가능하면 이러한 패턴은 사용하지 않는 것이 좋습니다.

JIT 활성화를 사용할 때 C++ 및 Visual Basic 6의 성능이 비슷하다는 것을 고려하면 C# 및 Visual Basic .NET을 사용하는 Enterprise Services도 대략 같은 성능을 보일 것으로 예상할 수 있습니다. 아래 그림은 간단한 메서드를 호출하는 위 테스트를 실행한 결과를 보여 줍니다.

이 데이터는 단순히 두 정수를 합하고, 개체의 컨텍스트에 SetComplete()를 호출하고, 결과를 반환하는 간단한 메서드에 대해 수행한 초당 호출 수를 보여 줍니다. 개체를 활성화하고 릴리스하는 부담은 거의 사라졌지만 버퍼를 마샬링하고 호출 스택으로 변환하는 등의 작업으로 인해 호출을 수행하는 부담은 아직 남아 있습니다.

이처럼 매우 간단한 메서드에서도 프로세스 간 호출의 경우 Enterprise Services는 Visual Basic 6과 성능이 거의 비슷합니다. 컴퓨터간에 호출할 경우에는 모든 언어의 성능이 거의 비슷합니다.

그러나 일반적인 비즈니스 응용 프로그램은 메서드에서 이보다 더 복잡한 작업을 수행합니다. 다음 차트는 일반적인 메서드를 호출하여 분산 트랜잭션 내에서 데이터베이스 연결을 열고 간단한 SQL 문을 실행하는 동일한 응용 프로그램을 네 가지 언어로 작성했을 때 각각의 상대적인 성능을 보여 줍니다.

앞의 결과는 메서드 내에서 많은 작업을 수행할 경우 모든 언어의 결과가 실험 오차 범위 내에서 동등하다는 것을 보여 줍니다. ADO를 사용하는 C++ 및 Visual Basic 6을 통해 작성한 COM+ 원시 응용 프로그램은 Enterprise Services를 사용하는 C# 또는 Visual Basic .NET 응용 프로그램과 동일한 속도로 수행됩니다. 프로세스 간 작업을 실행하거나 컴퓨터 간 작업을 실행하는 경우 성능 면에서는 거의 차이가 없습니다.

결과 요약

위 결과는 구성 요소를 최대한 효율적으로 수행하는 데 있어서 JIT 활성화 및 "만들기/반복 호출/릴리스" 패턴이 매우 중요하다는 것을 보여 줍니다.

결론

코드를 .NET으로 마이그레이션하는 것이 유리한 몇 가지 주요 이유를 설명했습니다. 다른 활성화 및 호출 패턴에 적용될 때의 원시 COM+ 및 .NET Enterprise Services 구성 요소 성능에 대해서도 논의했습니다. 또한 지침을 따라가면서 .NET Enterprise Services 구성 요소가 C++ COM+ 구성 요소만큼 빨리 실행된다는 것을 보여 주었습니다. 부록 1: 성능 권장 사항에는 고성능 .NET Enterprise Service 구성 요소를 만드는 데 도움이 되는 주요 권장 사항이 나와 있습니다.

여기에 설명된 기술을 일관되게 적용하면 기존 COM+ 코드를 .NET Enterprise Service 구성 요소로 바로 변환하고 아무런 성능 저하 없이 .NET Framework의 사용성, 보안 및 개발자 생산성 등의 이점을 누릴 수 있습니다.

지금 COM+ 구성 요소를 Enterprise Services 구성 요소로 변환해 놓으면 나중에 코드를 "Indigo"로 더욱 쉽게 마이그레이션할 수 있다는 점도 중요합니다. 부록 2: "Indigo" 및 .NET의 동향에서는 이 주제에 대해 간략히 논의합니다.

부록 1: 성능 권장 사항

다음 절에서는 높은 수준의 성능을 제공하는 빠른 COM+ 및 Enterprise Services 구성 요소를 만드는 방법에 대한 팁과 안내를 제공합니다. 대부분의 제안은 .NET Enterprise Services 구성 요소와 원시 COM+ 구성 요소에 동일하게 적용됩니다.

해당하는 경우 개체 풀링 및 JIT 활성화 사용

위의 테스트 결과가 보여주듯이 메서드 호출이 구성 요소 활성화보다 빠르며 관리되지 않는 구성 요소의 활성화가 Enterprise Services 구성 요소의 활성화보다 빠릅니다. 따라서 구성 요소 기반 응용 프로그램의 속도를 최대한 높이려면 코드에서 구성 요소 활성화 및 삭제의 수를 최소화하는 것이 중요합니다.

COM+에서는 개체 활성화를 최소화할 수 있는 두 가지 서비스를 제공합니다.

  1. 첫 번째는 앞에서 설명했듯이 호출자가 개체에 대한 활성 참조를 보관하고 있는 동안 해당 개체를 원활하게 비활성화할 수 있는 COM+ 서비스인 JIT(Just-in-time) 활성화입니다. 클라이언트가 개체에 메서드를 호출하기만 하면 COM+가 개체의 할당을 동적으로 관리하여 요청을 처리합니다.
  2. 두 번째는 해당 형식의 구성 요소 인스턴스를 요청하는 클라이언트가 바로 사용할 수 있도록 개체를 풀에서 활성 상태로 유지하는 개체 풀링입니다. COM+는 풀을 자동으로 관리하며, 개체 활성화 정보를 처리하고 사용자가 지정한 기준(예: 풀 크기)에 따라 다시 사용합니다.

풀링된 구성 요소 및 JIT 활성화된 구성 요소에 대한 참조를 보관하고 다시 사용함으로써 구성 요소 활성화 및 삭제를 최소화하고 높은 수준의 성능을 얻을 수 있습니다.

COM+ JIT 활성화 및 개체 풀링에 대한 자세한 내용은 Platform SDK: COM+ (Component Services) 설명서 를 참조하십시오.

왕복 회피

COM+ 구성 요소의 성능을 최적화하려면 호출자와 구성 요소 사이에 수행되는 프로세스 간 또는 컴퓨터 간 호출의 수를 최소화하는 것이 중요합니다. COM+ 구성 요소에서 만들어진 모든 메서드는 프로세스 간 호출 전환은 물론 컴퓨터 간 호출 전환도 초래하며, 매번 전환할 때마다 시간이 걸립니다. 따라서 COM+ 개체에서 만들어지는 메서드 호출을 최소한으로 유지하는 것이 필수적입니다. 그러기 위해 단일 호출에서 최대한 많은 작업을 수행하는 메서드를 사용하여 COM+ 구성 요소를 디자인하는 것도 좋은 방법입니다. 단, 이 경우에는 순수 아키텍처에서 변형된 구성 요소도 디자인해야 합니다.

COM+ 서비스 사용 최적화

COM+가 중요한 서비스를 다수 제공하기는 하지만 이러한 서비스를 현명하게 사용하는 것이 중요합니다. COM+가 구성 요소에 필요한 서비스를 제공하는 경우에는 대개 이 서비스가 가장 높은 성능을 제공하므로 반드시 사용하도록 하십시오. 그러나 서비스가 필요하지 않은 경우에는 구성 요소가 불필요한 작업을 수행하여 실행 속도도 느려질 수 있으므로 해당 서비스를 사용할 필요가 없습니다.

COM 마샬링 가능 매개 변수 사용

호출자가 데이터를 전달할 때 사용하는 매개 변수를 Enterprise Services 구성 요소의 메서드가 받아들이는 경우 다음과 같이 COM과 .NET 사이에 쉽게 마샬링할 수 있는 형식을 사용할 것을 매우 강력히 제안합니다.

  • Boolean
  • Byte, SByte
  • Char
  • DateTime
  • Decimal
  • Single, Double
  • Guid
  • Int16, UInt16, Int32, UInt32, Int64, UInt64
  • IntPtr, UIntPtr
  • String

이러한 형식만 사용하고 다른 복잡한 형식(예: 구조 또는 배열)은 전달하지 않으면 .NET serializer가 호출 처리 스택을 최적화하고 호출을 유선(RPC의 경우) 또는 가상 유선(LRPC의 경우)에 일렬로 직렬화할 수 있습니다. 그러면 호출이 더욱 빨리 실행됩니다. 그러나 메서드에 복잡한 형식이 필요한 경우에는 코드가 일반 DCOM 호출 스택을 통해 호출되므로 추가 처리 과정이 발생합니다.

파이널라이저 사용하지 않기

Enterprise Services 구성 요소에서 파이널라이저(C# 및 C++의 ~Classname() 소멸자)를 구현하지 마십시오. 파이널라이제이션은 가비지 수집기의 단일 스레드 작업입니다. Enterprise 구성 요소를 파이널라이제이션할 경우 완료되는 데 상당한 시간이 걸리므로 가비지 수집기 성능이 저하됩니다. 가비지 수집기 엔진이 실행 중인 동안에는 응용 프로그램에서 다른 작업을 수행할 수 없으므로 가비지 수집기가 완료되는 데 오랜 시간이 걸리면 전체 응용 프로그램의 성능이 저하됩니다.

대신 개체에서 Dispose(bool)을 재정의하고 Dispose(true)가 호출될 때 파이널라이제이션 형식 작업을 수행하는 것을 고려해 보십시오. 또한 이러한 종료 코드를 최대한 정돈되고, 안전하고, 간단하게 유지하도록 하십시오.

단일 스레드 COM+ 구성 요소 만들지 않기

여러 스레드의 동시 액세스를 지원하지 않는 개체는 STA(단일 스레드 아파트) 기능을 지원하는 것으로 표시되어 있습니다. 여러 스레드가 같은 인스턴스에 동시 액세스할 수 있도록 지원하는 구성 요소는 MTA(다중 스레드 아파트) 인식으로 표시되어 있습니다.

.NET Enterprise Services 구성 요소는 항상 STA 및 MTA를 모두 지원하는 것으로 표시되어 있기 때문에 더 이상 이 논의에 포함시키지 않겠습니다.

모든 Visual Basic 6 COM+ 구성 요소는 STA입니다. C++ COM+ 개발자는 구성 요소를 STA, MTA 또는 둘 다로 표시할 수 있습니다.

STA COM+ 구성 요소의 잠재적인 문제는 개체가 단일 스레드에서만 실행될 수 있으므로 해당 스레드가 개체의 메서드를 실행하는 유일한 스레드라는 것입니다. 이 직렬화를 통해 개발자는 STA 구성 요소를 더욱 쉽게 작성할 수 있지만, 도메인 간 마샬링이 종종 필요해서 성능이 저하되고 STA에서 한 스레드만 계속 실행됨으로 인해 확장성이 떨어지는 것은 피할 수 없습니다.

가능하면 STA 구성 요소를 만들거나 사용하지 않을 것을 제안합니다. 특히 확장성이 중요한 곳에서는 더욱 그렇습니다. 구성 요소가 다른 COM+ 구성 요소를 호출하는 경우 특히 STA 스레딩을 피해야 합니다. 이러한 호출에는 종종 스레드 전환이 필요한데, 스레드 전환은 해당 아파트에 있는 다른 모든 COM+ 구성 요소를 차단합니다.

설상가상으로 가비지 수집기의 파이널라이저도 개체를 소유한 STA 스레드를 호출할 때 차단됩니다. 그러면 파이널라이제이션 프로세스가 단일 스레드에 직렬화되며, 이는 이전 주제에서 설명했듯이 시스템 성능을 크게 저하시킬 수 있습니다.

부록 2: "Indigo" 및 .NET의 동향

Microsoft에서 현재 개발 중인 연결된 응용 프로그램을 위한 새로운 플랫폼, 코드 이름 "Indigo"에 대해 들어 보셨을 것입니다. 그렇다면 "Indigo"란 과연 어떤 제품일까요?

"Indigo"는 서비스 지향 연결 응용 프로그램을 위한 Microsoft의 전략적 기술 플랫폼으로서, 로스앤젤레스에서 개최된 2003 PDC(Professional Developers Conference)에서 소개되었습니다.

"Indigo"는 다음 기술의 개념, 특징 및 기능을 하나의 기술 스택으로 통합합니다.

  • COM
  • DCOM
  • COM+/Enterprise Services
  • ASMX/Web Services
  • .NET Remoting
  • 향상된 웹 서비스
  • MSMQ의 요소

"Indigo"는 서비스를 호출자에게 노출하는 데 필요한 프로토콜 및 전송으로부터 서비스의 개념을 추상화하는 다계층 플랫폼입니다. 최대한 많은 시스템과 상호 작동하기 위해 "Indigo"는 HTTP, TCP 및 IPC를 통한 고급 웹 서비스(WS-*)를 완벽하게 지원합니다. 현재 Microsoft는 코드 이름이 "Longhorn"인 Microsoft Windows의 출시 시기에 맞춰 "Indigo"를 배포할 예정입니다. Windows XP 및 Windows Server 2003에 대한 "Indigo" 지원도 함께 배포될 것입니다.

향후 "Indigo"가 발표된다고 하니 .NET Enterprise Services(또는 이 경우 ASMX 및 Remoting) 같은 기존 기술이 오늘날의 연결된 응용 프로그램을 개발하는 데 아직 유효한 것인지 걱정할 수도 있습니다. ASMX & WSE, Enterprise Services, Remoting 및 MSMQ는 오늘날의 엔터프라이즈 수준 솔루션에 가장 적합한 기술입니다. 적절하게 사용하면 "Indigo"가 릴리스되고 널리 사용될 때까지 훌륭한 응용 프로그램 개발 플랫폼을 제공해 줄 것입니다.

.NET을 사용하여 새 응용 프로그램을 작성하고 기존 응용 프로그램을 .NET으로 마이그레이션하면 향상된 보안, 안정성, 관리 및 확장성의 혜택을 누릴 수 있습니다. 또한 "Indigo"로 업그레이드하는 것이 원시 코드보다 훨씬 쉬워질 것입니다. 응용 프로그램의 "Indigo" 업그레이드를 준비하는 방법과 기존 기술을 사용하여 "Indigo"와 상호 작동하는 방법에 대한 자세한 내용은 나중에 MSDN을 참조하십시오.

"Indigo"에 대한 자세한 소개는 MSDN Magazine 기사, Code Name Indigo: A Guide to Developing and Running Connected Systems with Indigo 를 참조하십시오. 이 기사에 제공된 아키텍처 개요를 통해 이후의 Microsoft 응용 프로그램 플랫폼을 미리 살펴볼 수 있습니다.

"Indigo"에 대한 일반적인 내용은 Microsoft "Indigo" Frequent叿䉍/�᠀젇������ࠅက抅.�ⷞˀĀ＀�ÿ�Ā�䀀��Āc="/korea/msdn/images/tous.gif" border=0>를 참조하십시오.

부록 3: 분산 트랜잭션이 성능에 미치는 영향

위 테스트를 진행하면서 "COM+ 분산 트랜잭션이 이러한 구성 요소에 얼마나 많은 영향을 미칠까?"라는 질문이 생길 수도 있습니다. 이 질문의 답을 찾기 위해 각 구성 요소에 대해 COM+에서 "트랜잭션 필요" 설정을 끄고 테스트를 다시 실행해 보았습니다. 그 결과는 다음 차트에 표시되어 있습니다.

위 차트에서 볼 수 있듯이 COM+ 트랜잭션 지원이 없는 구성 요소의 성능과 트랜잭션이 켜져 있는 구성 요소의 성능은 거의 동일합니다. 이 결과는 테스트에서 COM+ 트랜잭션의 영향을 무시해도 상관없다는 것을 분명하게 보여 줍니다.

부록 4: 참고 자료

Upgrading Microsoft Visual Basic 6.0 to Microsoft Visual Basic .NET 

Visual Basic 6.0 응용 프로그램을 Visual Basic .NET으로 업그레이드하는 내용을 다루며 프로그래밍 팁, 요령 및 단계별 코드 비교 내용을 함께 제공합니다.

Programming with Managed Extensions for Microsoft Visual C++ .NET 

Visual C++ .NET 2003에 맞춰 업데이트된 이 책은 개발자를 위해 컴파일러의 새 기능과 언어에 대한 링커 확장을 심층적이고 전문적으로 다룹니다.

.NET Enterprise Services and COM+ 1.5 Architecture 

Microsoft .NET 및 Enterprise Services가 어떻게 조화를 이루는지 보여 주고 COM+/Enterprise Services 구성 요소를 빌드, 제어, 관리 및 보안하는 방법을 설명합니다.

.NET Framework Developer Center의 Performance 페이지 

고성능 코드를 작성하는 방법과 발생하는 문제를 진단하는 방법에 대해 심도있게 다루는 여러 링크와 리소스가 제공됩니다.

Performance Tips and Tricks in .NET Applications 

.NET 응용 프로그램이 제대로 수행되도록 하기 위한 팁과 힌트를 모아 놓았습니다.

Writing Faster Managed Code: Know What Things Cost 

.NET 코드에서 다양한 작업이 시스템에 어떤 부담을 주는지 자세히 분석한 내용입니다.

Garbage Collector Basics and Performance Hints 

가비지 수집기의 작동 방법, 가비지 수집기가 코드에 미치는 영향, 가비지 수집의 영향을 최소화하도록 코드를 작성하는 방법 등에 대해 설명합니다.

Performance Considerations for Run-Time Technologies in the .NET Framework 

가비지 수집 및 메모리 사용, JIT, 스레딩, .NET Remoting, 보안 등의 주제를 다룹니다.

부록 5: 성능 테스트 소스 코드

C++\ATL 구성 요소

헤더 파일

// ATLPerfTestObj.h : CATLPerfTestObj의 선언

#pragma once
#include "ATLPerfTests.h"
#include "resource.h" // 주 기호입니다.
#include <comsvcs.h>
#include <mtxattr.h>


// CATLPerfTestObj

class ATL_NO_VTABLE CATLPerfTestObj :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CATLPerfTestObj, &CLSID_ATLPerfTestObj>,
public IDispatchImpl<IPerfTestObj, &IID_IPerfTestObj, &LIBID_ATLPerfTestsLib,
/*wMajor =*/ 1, /*wMinor =*/ 0>
{
public :
CATLPerfTestObj()
{
}

DECLARE_PROTECT_FINAL_CONSTRUCT()

HRESULT FinalConstruct()
{
return S_OK;
}

void FinalRelease()
{
}

DECLARE_REGISTRY_RESOURCEID(IDR_ATLPERFTESTOBJ)

DECLARE_NOT_AGGREGATABLE(CATLPerfTestObj)

BEGIN_COM_MAP(CATLPerfTestObj)
COM_INTERFACE_ENTRY(IPerfTestObj)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()


// IPerfTestObj
public :
STDMETHOD(Sum)(LONG number1, LONG number2, LONG* result);
STDMETHOD(AddSale)(LONG orderNumber, LONG storeID, LONG titleID,
LONG qty);

private :
HRESULT InsertSaleRecord(LONG orderNumber, LONG storeID, LONG
titleID, LONG qty);

};

OBJECT_ENTRY_AUTO(__uuidof(ATLPerfTestObj), CATLPerfTestObj)(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

소스 파일

// ATLPerfTestObj.cpp : CATLPerfTestObj의 구현

#include "stdafx.h"
#include "ATLPerfTestObj.h"
#include ".\atlperftestobj.h"

#include "atlstr.h"

#import "c:\Program Files\Common Files\System\ADO\msado15.dll" rename_namespace("ADO")
rename("EOF", "EndOfFile")
using namespace ADO;

// CATLPerfTestObj

// 여기에서 간단한 작업을 수행하여 간단한 메서드를 시뮬레이트합니다.
STDMETHODIMP CATLPerfTestObj::Sum(LONG number1, LONG number2, LONG* result)
{
// 개체 컨텍스트를 가져옵니다.
IObjectContext* ctx = NULL;
HRESULT hr = CoGetObjectContext(IID_IObjectContext,
(LPVOID*)&ctx);
if(SUCCEEDED(hr))
{
// 계산을 수행합니다.
*result = number1 + number2;

// 트랜잭션을 커밋합니다.
ctx->SetComplete();
ctx->Release();
}

return hr;
}

STDMETHODIMP CATLPerfTestObj::AddSale(LONG orderNumber, LONG storeID, LONG titleID, LONG qty)
{
// COM+ 개체 컨텍스트를 가져옵니다.
IObjectContext* ctx = NULL;
HRESULT hr = CoGetObjectContext(IID_IObjectContext, (LPVOID*)&ctx);

// COM+ 컨텍스트가 있는지 확인합니다.
if(SUCCEEDED(hr))
{
// 기본 작업은 중단하는 것입니다.
ctx->SetAbort();

// 데이터베이스에 레코드를 삽입합니다.
hr = InsertSaleRecord(orderNumber, storeID, titleID, qty);

// 삽입 작업의 결과를 확인합니다.
if(SUCCEEDED(hr))
{
// 모든 것이 올바르면 트랜잭션을 완료로
// 표시합니다.
ctx->SetComplete();
}

// 컨텍스트 포인터를 정리합니다.
ctx->Release();
ctx = NULL;
}

// 전체 결과를 반환합니다. 삽입 작업이 오류 없이
// 실행되는 경우에만 결과가 S_OK입니다.
return hr;
}

// 데이터베이스에 판매 레코드를 삽입합니다.
HRESULT CATLPerfTestObj::InsertSaleRecord(LONG orderNumber, LONG storeID,
LONG titleID, LONG qty)
{
// 기본 결과는 실패를 반환하는 것입니다.
HRESULT hr = E_FAIL;

try
{
// 서버에서 실행할 SQL을 포맷합니다.
CString str;
str.Format("insert into sales (order_no, store_id, \
title_id, order_date, qty) values (%i, %i, %i, \
GetDate(), %i)",
orderNumber, storeID, titleID, qty);

// 데이터베이스에 대한 연결을 엽니다.
_ConnectionPtr cn("ADODB.Connection");
cn->Open("Provider=SQLOLEDB;SERVER=localhost;Integrated \
Security=SSPI;DATABASE=ESPERFTESTDB",
"", "", adConnectUnspecified);

// 명령을 실행합니다.
_variant_t rs;
rs = cn->Execute(_bstr_t(str), &rs, adCmdText);
hr = S_OK;

// 연결을 명시적으로 닫습니다.
cn->Close();
}
catch(_com_error e)
{
// 문제가 있으면 오류가 반환됩니다.
hr = e.Error();
}

return hr;
}

Visual Basic 6 구성 요소

Private Function IPerfTestObj_Sum(ByVal nA As Long, ByVal nB As Long) As Long
IPerfTestObj_Sum = nA + nB

GetObjectContext.SetComplete
End Function

Sub IPerfTestObj_AddSale(ByVal OrderNumber As Long, ByVal StoreID As Long,
ByVal TitleID As Long, ByVal Qty As Long)
' 기본값은 오류가 throw될 경우 중단하는 것입니다.
GetObjectContext.SetAbort

Call InsertSaleRecord(OrderNumber, StoreID, TitleID, Qty)

' 실패하지 않았기 때문에 트랜잭션을 완료할 수 있습니다.
GetObjectContext.SetComplete
End Sub

Sub InsertSaleRecord(ByVal OrderNumber As Long, ByVal StoreID As Long,
ByVal TitleID As Long, ByVal Qty As Long)
Dim command As String

connDB.Open ("Provider=SQLOLEDB;SERVER=localhost;Integrated" + _
"Security=SSPI;DATABASE=ESPERFTESTDB")

command = "insert into sales (store_id, order_no, order_date, qty, title_id)
values (" & StoreID & ", " & OrderNumber & ", GetDate(),
" & Qty & ", " & TitleID & ")"

connDB.Execute (command)
connDB.Close
End Sub

C# 구성 요소

using System;
using System.EnterpriseServices;
using System.Data.SqlClient;
using System.Reflection;
using System.Runtime.InteropServices;

using perftestsinterop;

[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyKeyFile("..\\..\\sign.key")]
[assembly: ApplicationName("PerfTest")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false)]

namespace CSPerfTests
{
[Guid("0BA5534E-8544-42e2-A909-3265105BEA09")]
[Transaction(TransactionOption.Required)]
public class CSPerfTestObj : ServicedComponent, IPerfTestObj
{
public CSPerfTestObj()
{
}

// 여기에서 간단한 작업을 수행하여 간단한 메서드를
// 시뮬레이트합니다.
public int Sum(int number1, int number2)
{
int result = 0;

// 계산을 수행합니다.
result = number1 + number2;

// 트랜잭션을 커밋합니다.
ContextUtil.SetComplete();

return result;
}

// 트랜잭션에 새 판매 항목을 추가합니다.
public void AddSale(int orderNumber, int storeID, int
titleID, int qty)
{
try
{
// 데이터베이스에 레코드를 삽입합니다.
InsertSaleRecord(orderNumber, storeID, titleID,
qty);

// 모든 것이 올바르면 트랜잭션을 완료로
// 표시합니다.
ContextUtil.SetComplete();
}
catch(Exception)
{
ContextUtil.SetAbort();
throw;
}
}

// 데이터베이스에 판매 레코드를 삽입합니다.
private void InsertSaleRecord(int orderNumber, int storeID,
int titleID, int qty)
{
// 서버에서 실행할 SQL을
// 포맷합니다.
string commandStr = string.Format("insert into sales\
(order_no, store_id, title_id, order_date, qty) \
values ({0}, {1}, {2}, GetDate(), {3})",
orderNumber, storeID, titleID, qty);

// 데이터베이스에 대한 연결을 엽니다.
SqlConnection connection = new
SqlConnection("SERVER=localhost;Integrated
Security=SSPI;DATABASE=ESPERFTESTDB");
connection.Open();

// 명령을 실행합니다.
SqlCommand cmd = new SqlCommand(commandStr,
connection);
cmd.ExecuteNonQuery();

// 연결을 명시적으로 닫습니다.
connection.Close();
}
}
}

Visual Basic .NET 구성 요소

Imports System
Imports System.EnterpriseServices
Imports System.Data.SqlClient
Imports System.Reflection
Imports System.Runtime.InteropServices

Imports perftestsinterop

<Assembly: AssemblyVersion("1.0.0")>
<Assembly: AssemblyKeyFile("..\..\sign.key")>
<Assembly: ApplicationName("PerfTest")>
<Assembly: ApplicationActivation(ActivationOption.Server)>
<Assembly: ApplicationAccessControl(False)>

<Guid("53400EEB-E8C1-4D15-81D4-AFAC896B34FA"), _
Transaction(TransactionOption.Required)> _
Public Class VBPerfTestObj
Inherits ServicedComponent
Implements IPerfTestObj

'-- 여기에서 간단한 작업을 수행하여 간단한 메서드를
'-- 시뮬레이트합니다.
Public Function Sum(ByVal number1 As Integer, ByVal number2 As
Integer) As Integer _
Implements IPerfTestObj.Sum
Dim result As Integer = 0

'-- 계산을 수행합니다.
result = number1 + number2

'-- 트랜잭션을 커밋합니다.
ContextUtil.SetComplete()

Return result
End Function

'-- 트랜잭션에 새 판매 항목을 추가합니다.
Public Sub AddSale(ByVal orderNumber As Integer, ByVal storeID As
Integer, ByVal titleID As Integer, ByVal qty As Integer) _
Implements IPerfTestObj.AddSale
Try
'-- 데이터베이스에 레코드를 삽입합니다.
InsertSaleRecord(orderNumber, storeID, titleID, qty)

'-- 모든 것이 올바르면 트랜잭션을 완료로 표시합니다.
ContextUtil.SetComplete()

Catch
ContextUtil.SetAbort()
Throw
End Try

End Sub

'-- 데이터베이스에 판매 레코드를 삽입합니다.
Private Sub InsertSaleRecord(ByVal orderNumber As Integer, ByVal
storeID As Integer, ByVal titleID As Integer, ByVal qty As
Integer)
'-- 서버에서 실행할 SQL을 포맷합니다.
Dim commandStr As String
commandStr = String.Format("insert into sales (order_no," + _
"store_id, title_id, order_date, qty) values ({0}, {1}, _
{2}, GetDate(), {3})", _
orderNumber, storeID, titleID, qty)

'-- 데이터베이스에 대한 연결을 엽니다.
Dim connection As SqlConnection = New
SqlConnection("SERVER=localhost;Integrated
Security=SSPI;DATABASE=ESPERFTESTDB")
connection.Open()

'-- 명령을 실행합니다.
Dim cmd As SqlCommand = New SqlCommand(commandStr, connection)
cmd.ExecuteNonQuery()

'-- 연결을 명시적으로 닫습니다.
connection.Close()
End Sub

End Class

테스트 응용 프로그램

using System;
using System.Reflection;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
using System.Text;
using System.IO;
using COMAdmin;

using ATLPerfTestsLib;
using CSPerfTests;
using VBPerfTests;

[assembly: AssemblyVersion("1.0.*")]

namespace TestApp
{
[Flags]
enum TestType
{
NoOp = 0x0,
LightMethod = 0x1,
HeavyMethod = 0x2,
Activations = 0x40000000,

ActivateLight = Activations | LightMethod,
ActivateHeavy = Activations | HeavyMethod,

}

public class App
{
public static HighResolutionTimer Timer;
public static int RunID = 0;
public static TextWriter outputFile;

[STAThread]
public static void Main(string[] args)
{
Timer = new HighResolutionTimer();
RunID = (int)Timer.TickCount;

Console.WriteLine("\a");

outputFile = new StreamWriter(new
FileStream("results.csv", FileMode.Create,
FileAccess.Write), Encoding.UTF8);

Type t;
long iterations = 5000;

// 빈 활성화 및 기본 삭제를 테스트합니다.
outputFile.WriteLine("Test Type, Number of
Iterations, Object Type, Elapsed Time (sec),
Operations/sec");

t = typeof(ATLPerfTestObjClass);
TestActivations(t, iterations, TestType.Activations,
ApartmentState.MTA);

t = Type.GetTypeFromProgID("VB6Perf.HPerf");
TestActivations(t, iterations, TestType.Activations,
ApartmentState.STA);

t = typeof(CSPerfTestObj);
TestActivations(t, iterations, TestType.Activations,
ApartmentState.MTA);

t = typeof(VBPerfTestObj);
TestActivations(t, iterations, TestType.Activations,
ApartmentState.MTA);


t = typeof(ATLPerfTestObjClass);
TestActivations(t, iterations,
TestType.ActivateLight, ApartmentState.MTA);

t = Type.GetTypeFromProgID("VB6Perf.HPerf");
TestActivations(t, iterations,
TestType.ActivateLight, ApartmentState.STA);

t = typeof(CSPerfTestObj);
TestActivations(t, iterations,
TestType.ActivateLight, ApartmentState.MTA);

t = typeof(VBPerfTestObj);
TestActivations(t, iterations,
TestType.ActivateLight, ApartmentState.MTA);

t = typeof(ATLPerfTestObjClass);
TestActivations(t, iterations,
TestType.ActivateHeavy, ApartmentState.MTA);

t = Type.GetTypeFromProgID("VB6Perf.HPerf");
TestActivations(t, iterations,
TestType.ActivateHeavy, ApartmentState.STA);

t = typeof(CSPerfTestObj);
TestActivations(t, iterations,
TestType.ActivateHeavy, ApartmentState.MTA);

t = typeof(VBPerfTestObj);
TestActivations(t, iterations,
TestType.ActivateHeavy, ApartmentState.MTA);

// 올바른 Trivial Method Test 숫자를
// 가져오기 위해 두 개의 확대 명령으로
// 반복 수를 늘립니다.
t = typeof(ATLPerfTestObjClass);
TestActivations(t, iterations*100,
TestType.LightMethod, ApartmentState.MTA);

t = Type.GetTypeFromProgID("VB6Perf.HPerf");
TestActivations(t, iterations*100,
TestType.LightMethod, ApartmentState.STA);

t = typeof(CSPerfTestObj);
TestActivations(t, iterations*100,
TestType.LightMethod, ApartmentState.MTA);

t = typeof(VBPerfTestObj);
TestActivations(t, iterations*100,
TestType.LightMethod, ApartmentState.MTA);

t = typeof(ATLPerfTestObjClass);
TestActivations(t, iterations, TestType.HeavyMethod,
ApartmentState.MTA);

t = Type.GetTypeFromProgID("VB6Perf.HPerf");
TestActivations(t, iterations, TestType.HeavyMethod,
ApartmentState.STA);

t = typeof(CSPerfTestObj);
TestActivations(t, iterations, TestType.HeavyMethod,
ApartmentState.MTA);

t = typeof(VBPerfTestObj);
TestActivations(t, iterations, TestType.HeavyMethod,
ApartmentState.MTA);

outputFile.Close();

Console.WriteLine("\a");
}

private static void TestActivations(Type type, long
iterations, TestType test, ApartmentState aptype)
{
// 새 스레드를 만들어 실행 중인 스레드의
// 아파트 형식을 제어합니다.
TestRunner runner = new TestRunner(type, iterations,
test);
Thread thread = new Thread(new
ThreadStart(runner.Run));
thread.ApartmentState = aptype;
thread.Start();
thread.Join();
COMAdminCatalog catalog = new COMAdminCatalogClass();
catalog.ShutdownApplication("PerfTest");
}

class TestRunner
{
Type type;
long iterations;
TestType test;

string testTypeName;
string componentTypeName;

public TestRunner(Type type, long iterations,
TestType test)
{
this.type = type;
this.iterations = iterations;
this.test = test;

// 나중에 사용할 테스트 형식 및
// 구성 요소 형식 이름 문자열을 만듭니다.
switch(test)
{
case TestType.Activations:
testTypeName = "Activations, ";
break;

case TestType.ActivateLight:
testTypeName = "Activations with \
Light Method Call, ";
break;

case TestType.ActivateHeavy:
testTypeName = "Activations with \
Heavy Method Call, ";
break;

case TestType.LightMethod:
testTypeName = "Light Method \
Calls, ";
break;

case TestType.HeavyMethod:
testTypeName = "Heavy Method \
Calls, ";
break;
};

if (type == typeof(ATLPerfTestObjClass))
componentTypeName = "C++/ATL, ";
else if(type == typeof(CSPerfTestObj))
componentTypeName = "C#, ";
else if(type == typeof(VBPerfTestObj))
componentTypeName = "VB.NET, ";
else
componentTypeName = "VB6, ";
}

void TestLoop(long iterations)
{
bool timingActivations = (this.test &
TestType.Activations) != 0;
perftestsinterop.IPerfTestObj o = null;

// 활성화에 시간을 지정하지 않은 경우
// 루프 밖에서 지정합니다.
if(!timingActivations)
{
o = (perftestsinterop.IPerfTestObj)
Activator.CreateInstance(type);
}

// 테스트를 수행합니다.
for (int i = 0; i < iterations; i++)
{
if(timingActivations)
{
o = (perftestsinterop.IPerfTestObj)
Activator.CreateInstance(type);
}

// 수행해야 하는 테스트를
// 찾아내어 수행합니다. 활성화 비트를
// 제거하여 실행하고자 하는
// 종류의 메서드만 남아 있도록
// 합니다.
switch(test & ~TestType.Activations)
{
case TestType.NoOp:
break;

case TestType.LightMethod:
o.Sum(100, 200);
break;

case TestType.HeavyMethod:
int count =
(int)Timer.TickCount;
o.AddSale(RunID, count, count
% 10000, count % 10);
break;
};

if(timingActivations)
{
// 지금 개체를 삭제합니다.
// 작업이 완료되었습니다.
if (type.IsCOMObject)
{
Marshal.ReleaseComObject(o);
}
else
{
ServicedComponent.DisposeObject((ServicedComponent)o);
}
}
}
}

public void Run()
{
float elapsedTime = 0.0f;

// 실행을 준비합니다.
TestLoop(10);

// 타이머를 중지하고 다시 설정합니다.
Timer.Stop();
Timer.Reset();
Timer.Start();

TestLoop(iterations);

// 루프가 끝나면 타이머를 중지하고
// 경과 시간을 반환합니다.
elapsedTime = Timer.ElapsedTime;
Timer.Stop();

// 결과를 출력합니다.
StringBuilder sb = new StringBuilder();
sb.Append(testTypeName);
sb.Append(iterations.ToString());
sb.Append(", ");
sb.Append(componentTypeName);
sb.Append(elapsedTime.ToString());
sb.Append(", ");
sb.Append((iterations/elapsedTime).ToString());
outputFile.WriteLine(sb.ToString());

GC.WaitForPendingFinalizers();
}
}

}
}

부록 6: 테스트 결과

TX 범위 JITA 메서드 형식 개체 형식 연산/초
Tx 프로세스 내 JITA 없음 메서드 없음 C++/ATL 2846.9
Tx 프로세스 내 JITA 없음 메서드 없음 Visual Basic 6 1186.2
Tx 프로세스 내 JITA 없음 메서드 없음 C# 1581.7
Tx 프로세스 내 JITA 없음 메서드 없음 Visual Basic .NET 1581.6
Tx 프로세스 내 JITA 없음 간단한 메서드 C++/ATL 2588.1
Tx 프로세스 내 JITA 없음 간단한 메서드 Visual Basic 6 1138.8
Tx 프로세스 내 JITA 없음 간단한 메서드 C# 1423.8
Tx 프로세스 내 JITA 없음 간단한 메서드 Visual Basic .NET 1423.5
Tx 프로세스 내 JITA 없음 일반 메서드 C++/ATL 243.3
Tx 프로세스 내 JITA 없음 일반 메서드 Visual Basic 6 243.3
Tx 프로세스 내 JITA 없음 일반 메서드 Visual Basic .NET 245.4
Tx 프로세스 내 JITA 없음 일반 메서드 C# 245.4
Tx 프로세스 내 JITA 간단한 메서드 C++/ATL 49084.6
Tx 프로세스 내 JITA 간단한 메서드 Visual Basic 6 29655.3
Tx 프로세스 내 JITA 간단한 메서드 C# 12218.5
Tx 프로세스 내 JITA 간단한 메서드 Visual Basic .NET 12166.3
Tx 프로세스 내 JITA 일반 메서드 C++/ATL 245.4
Tx 프로세스 내 JITA 일반 메서드 Visual Basic 6 247.6
Tx 프로세스 내 JITA 일반 메서드 C# 247.6
Tx 프로세스 내 JITA 일반 메서드 Visual Basic .NET 245.4
Tx 프로세스 외 JITA 없음 메서드 없음 C++/ATL 351.5
Tx 프로세스 외 JITA 없음 메서드 없음 Visual Basic 6 237.2
Tx 프로세스 외 JITA 없음 메서드 없음 C# 176.8
Tx 프로세스 외 JITA 없음 메서드 없음 Visual Basic .NET 176.8
Tx 프로세스 외 JITA 없음 간단한 메서드 C++/ATL 261.2
Tx 프로세스 외 JITA 없음 간단한 메서드 Visual Basic 6 192.4
Tx 프로세스 외 JITA 없음 간단한 메서드 C# 124.3
Tx 프로세스 외 JITA 없음 간단한 메서드 Visual Basic .NET 123.8
Tx 프로세스 외 JITA 없음 일반 메서드 C++/ATL 123.2
Tx 프로세스 외 JITA 없음 일반 메서드 Visual Basic 6 121.7
Tx 프로세스 외 JITA 없음 일반 메서드 Visual Basic .NET 80.0
Tx 프로세스 외 JITA 없음 일반 메서드 C# 79.3
Tx 프로세스 외 JITA 간단한 메서드 C++/ATL 16456.1
Tx 프로세스 외 JITA 간단한 메서드 Visual Basic 6 8600.9
Tx 프로세스 외 JITA 간단한 메서드 C# 7299.8
Tx 프로세스 외 JITA 간단한 메서드 Visual Basic .NET 7281.1
Tx 프로세스 외 JITA 일반 메서드 C++/ATL 243.3
Tx 프로세스 외 JITA 일반 메서드 Visual Basic 6 245.4
Tx 프로세스 외 JITA 일반 메서드 C# 243.3
Tx 프로세스 외 JITA 일반 메서드 Visual Basic .NET 245.4
Tx 컴퓨터간 JITA 없음 메서드 없음 C++/ATL 142.8
Tx 컴퓨터간 JITA 없음 메서드 없음 Visual Basic 6 93.5
Tx 컴퓨터간 JITA 없음 메서드 없음 C# 100.7
Tx 컴퓨터간 JITA 없음 메서드 없음 Visual Basic .NET 100.7
Tx 컴퓨터간 JITA 없음 간단한 메서드 C++/ATL 117.1
Tx 컴퓨터간 JITA 없음 간단한 메서드 Visual Basic 6 80.2
Tx 컴퓨터간 JITA 없음 간단한 메서드 C# 72.8
Tx 컴퓨터간 JITA 없음 간단한 메서드 Visual Basic .NET 73.0
Tx 컴퓨터간 JITA 없음 일반 메서드 C++/ATL 81.7
Tx 컴퓨터간 JITA 없음 일반 메서드 Visual Basic 6 61.7
Tx 컴퓨터간 JITA 없음 일반 메서드 C# 48.0
Tx 컴퓨터간 JITA 없음 일반 메서드 Visual Basic .NET 48.9
Tx 컴퓨터간 JITA 간단한 메서드 C++/ATL 1902.7
Tx 컴퓨터간 JITA 간단한 메서드 Visual Basic 6 1672.5
Tx 컴퓨터간 JITA 간단한 메서드 C# 1573.3
Tx 컴퓨터간 JITA 간단한 메서드 Visual Basic .NET 1552.6
Tx 컴퓨터간 JITA 일반 메서드 C++/ATL 242.9
Tx 컴퓨터간 JITA 일반 메서드 Visual Basic 6 245.2
Tx 컴퓨터간 JITA 일반 메서드 C# 238.5
Tx 컴퓨터간 JITA 일반 메서드 Visual Basic .NET 238.5
Tx 없음 프로세스 내 JITA 없음 메서드 없음 C++/ATL 2695.3
Tx 없음 프로세스 내 JITA 없음 메서드 없음 Visual Basic 6 1207.0
Tx 없음 프로세스 내 JITA 없음 메서드 없음 C# 1664.7
Tx 없음 프로세스 내 JITA 없음 메서드 없음 Visual Basic .NET 1654.1
Tx 없음 프로세스 내 JITA 없음 간단한 메서드 C++/ATL 2586.3
Tx 없음 프로세스 내 JITA 없음 간단한 메서드 Visual Basic 6 1154.6
Tx 없음 프로세스 내 JITA 없음 간단한 메서드 C# 1517.1
Tx 없음 프로세스 내 JITA 없음 간단한 메서드 Visual Basic .NET 1507.0
Tx 없음 프로세스 내 JITA 없음 일반 메서드 C++/ATL 245.1
Tx 없음 프로세스 내 JITA 없음 일반 메서드 Visual Basic 6 245.4
Tx 없음 프로세스 내 JITA 없음 일반 메서드 C# 247.0
Tx 없음 프로세스 내 JITA 없음 일반 메서드 Visual Basic .NET 246.2
Tx 없음 프로세스 내 JITA 간단한 메서드 C++/ATL 50660.9
Tx 없음 프로세스 내 JITA 간단한 메서드 Visual Basic 6 31121.7
Tx 없음 프로세스 내 JITA 간단한 메서드 C# 12928.1
Tx 없음 프로세스 내 JITA 간단한 메서드 Visual Basic .NET 12808.6
Tx 없음 프로세스 내 JITA 일반 메서드 C++/ATL 247.4
Tx 없음 프로세스 내 JITA 일반 메서드 Visual Basic 6 246.2
Tx 없음 프로세스 내 JITA 일반 메서드 C# 247.4
Tx 없음 프로세스 내 JITA 일반 메서드 Visual Basic .NET 247.5
Tx 없음 프로세스 외 JITA 없음 메서드 없음 C++/ATL 327.0
Tx 없음 프로세스 외 JITA 없음 메서드 없음 Visual Basic 6 239.7
Tx 없음 프로세스 외 JITA 없음 메서드 없음 C# 177.4
Tx 없음 프로세스 외 JITA 없음 메서드 없음 Visual Basic .NET 177.9
Tx 없음 프로세스 외 JITA 없음 간단한 메서드 C++/ATL 259.3
Tx 없음 프로세스 외 JITA 없음 간단한 메서드 Visual Basic 6 192.2
Tx 없음 프로세스 외 JITA 없음 간단한 메서드 C# 125.1
Tx 없음 프로세스 외 JITA 없음 간단한 메서드 Visual Basic .NET 124.7
Tx 없음 프로세스 외 JITA 없음 일반 메서드 C++/ATL 123.3
Tx 없음 프로세스 외 JITA 없음 일반 메서드 Visual Basic 6 123.6
Tx 없음 프로세스 외 JITA 없음 일반 메서드 C# 82.6
Tx 없음 프로세스 외 JITA 없음 일반 메서드 Visual Basic .NET 82.6
Tx 없음 프로세스 외 JITA 간단한 메서드 C++/ATL 16637.9
Tx 없음 프로세스 외 JITA 간단한 메서드 Visual Basic 6 8847.9
Tx 없음 프로세스 외 JITA 간단한 메서드 C# 7345.1
Tx 없음 프로세스 외 JITA 간단한 메서드 Visual Basic .NET 7392.8
Tx 없음 프로세스 외 JITA 일반 메서드 C++/ATL 244.8
Tx 없음 프로세스 외 JITA 일반 메서드 Visual Basic 6 244.9
Tx 없음 프로세스 외 JITA 일반 메서드 C# 243.9
Tx 없음 프로세스 외 JITA 일반 메서드 Visual Basic .NET 245.5
Tx 없음 프로세스 외 JITA 없음 메서드 없음 C++/ATL 147.5
Tx 없음 컴퓨터간 JITA 없음 메서드 없음 Visual Basic 6 95.1
Tx 없음 컴퓨터간 JITA 없음 메서드 없음 C# 102.3
Tx 없음 컴퓨터간 JITA 없음 메서드 없음 Visual Basic .NET 101.7
Tx 없음 컴퓨터간 JITA 없음 간단한 메서드 C++/ATL 119.6
Tx 없음 컴퓨터간 JITA 없음 간단한 메서드 Visual Basic 6 81.6
Tx 없음 컴퓨터간 JITA 없음 간단한 메서드 C# 74.1
Tx 없음 컴퓨터간 JITA 없음 간단한 메서드 Visual Basic .NET 73.5
Tx 없음 컴퓨터간 JITA 없음 일반 메서드 C++/ATL 82.2
Tx 없음 컴퓨터간 JITA 없음 일반 메서드 Visual Basic 6 61.9
Tx 없음 컴퓨터간 JITA 없음 일반 메서드 C# 60.3
Tx 없음 컴퓨터간 JITA 없음 일반 메서드 Visual Basic .NET 61.2
Tx 없음 컴퓨터간 JITA 간단한 메서드 C++/ATL 1896.7
Tx 없음 컴퓨터간 JITA 간단한 메서드 Visual Basic 6 1620.5
Tx 없음 컴퓨터간 JITA 간단한 메서드 C# 1576.8
Tx 없음 컴퓨터간 JITA 간단한 메서드 Visual Basic .NET 1591.5
Tx 없음 컴퓨터간 JITA 일반 메서드 C++/ATL 242.7
Tx 없음 컴퓨터간 JITA 일반 메서드 Visual Basic 6 243.7
Tx 없음 컴퓨터간 JITA 일반 메서드 C# 245.8
Tx 없음 컴퓨터간 JITA 일반 메서드 Visual Basic .NET 246.0

신고
Trackback 6 Comment 0
2008.09.05 10:46

.NET의 Enterprise Service(COM+) 이해


원문 : http://www.microsoft.com/korea/msdn/library/dndotnet/html/entserv.asp

.NET의 Enterprise Service(COM+) 이해


Shannon Pahl
Microsoft Corporation

요약: Microsoft .NET과 COM+ 서비스 통합의 이면에 있는 기술적 정보를 제공하고 관리되는 코드에 사용할 수 있는 서비스에 대해 설명합니다(26페이지/인쇄 페이지 기준).

목차

소개
트랜잭션
배포
서비스되는 구성 요소
개체 수명
보안
원격 구성 요소
결론

소개

이 기사에서는 Microsoft.NET Framework와 COM+ 서비스를 잘 알고 있어야 합니다. Enterprise Services를 반드시 알고 있을 필요는 없지만 알고 있으면 도움이 됩니다. 이 항목에 대한 자세한 배경은 다음을 참조하십시오.

COM은 구성 요소 기반 응용 프로그램을 작성하는 한 가지 방법을 제공합니다. COM 구성 요소를 작성하는 데 필요한 측정 작업이 중요하고 반복된다는 사실은 잘 알려져 있습니다. COM+는 COM의 새로운 버전이 아닙니다. COM+는 구성 요소에 대한 서비스 인프라를 제공합니다. 배포가 쉽고 처리량이 많은 확장 가능한 서버 응용 프로그램을 작성하려면 구성 요소를 작성한 다음 COM+ 응용 프로그램에 설치합니다. 구성 요소에서 서비스를 사용할 필요가 없을 경우 해당 구성 요소를 COM+ 응용 프로그램에 설치할 필요가 없습니다. 응용 프로그램을 처음부터 트랜잭션, 개체 풀링, 동작 구문 등을 사용하도록 디자인하면 확장성 및 처리량을 얻을 수 있습니다.

.NET Framework는 구성 요소 기반 응용 프로그램을 작성하는 다른 방법을 제공하며, 보다 나은 도구를 지원하는 COM 프로그래밍 모델, 공용 언어 런타임(CLR), 훨씬 쉬워진 코딩 구문보다 이점들을 가지고 있습니다. COM+ 서비스 인프라는 관리되는 코드와 관리되지 않는 코드 모두에서 액세스될 수 있습니다. 관리되지 않는 코드의 서비스를 COM+ 서비스라 합니다. .NET에서는 이 서비스를 Enterprise Services라 합니다. ServicedComponent에서 클래스가 파생될 경우 한 구성 요소를 위해 서비스가 필요하다는 것을 나타냅니다. 구성 요소에서 서비스를 사용할 필요가 없을 경우 ServicedComponent에서 해당 구성 요소를 파생할 필요가 없습니다. 프로그래머들이 서버 기반 응용 프로그램을 작성할 수 있도록 도구 지원을 향상시켰지만, 우수한 프로그래밍 영역에서는 여전히 확장성 및 처리량 문제가 존재합니다. 서비스의 이면에 있는 기본적인 생각은 처음부터 처리량과 확장성을 고려해서 디자인하고 Enterprise Services를 사용하여 해당 위치에서 디자인 패턴을 쉽게 구현하게 하는 것입니다.

서비스 인프라 디자인은 COM 또는 구성 요소와 거의 관련이 없다고 주장할 수도 있습니다. 그러나, COM+ services는 이제 COM 구성 요소와 .NET 구성 요소에 적용될 수 있으며, 심지어는 ASP 페이지, 임의의 코드 블록 등과 같은 구성 요소가 아닌 엔티티에도 적용될 수 있습니다(Microsoft Windows XP의 Components COM+ 기능 없는 서비스 참조).

현재 사용할 수 있는 모든 COM+ 서비스는 .NET 및 COM 개체에 사용될 수 있습니다. 이러한 서비스로는 트랜잭션, 개체 풀링 및 작업 문자열, JIT, 동기화, 역할 기반 보안, CRM, BYOT 등이 있습니다. Microsoft Windows 2000의 전체 서비스 목록은 Platform SDK의 COM+에 제공되는 서비스를 참조하십시오. Microsoft Windows XP에는 .NET 구성 요소와 함께 사용될 수 있는 추가 서비스가 있는 COM+ 1.5라는 새로운 COM+ 버전이 포함되어 있습니다.

트랜잭션

서비스를 사용하는 관리되는 응용 프로그램을 작성하려면 서비스를 필요로 하는 클래스를 ServicedComponent에서 파생하고 다양한 사용자 지정 특성을 사용하여 실제로 필요한 서비스를 지정해야 합니다. 이 절에서는 서비스의 개념과 서비스가 관리되는 코드 작성에 어떤 영향을 미치는지에 대해 설명합니다. 자세한 설명은 이후의 절들에 제공됩니다.

Account라는 클래스(실제 코드는 뒤에 나열됨)가 작성되어 BankComponent 어셈블리에 있다고 가정합니다. 이 클래스는 다음과 같이 사용될 수 있습니다.

BankComponent Client

using system;
using BankComponent;
namespace BankComponentClient
{
class Client
{
public static int Main()
{
Account act = new Account();
act.Post(5, 100);
act.Dispose();
return 0;
}
}
}

클라이언트를 구축하려면 BankComponent 네임스페이스에 참조를 추가해야 합니다. 또한, BankComponentClient 네임스페이스에 System.EnterpriseServices 어셈블리에 대한 참조를 추가해야, 클라이언트가 Dispose() 및 ServicedComponent 생성자를 호출합니다. 이 생성자는 BankComponent가 포함된 어셈블리가 아니라 System.EnterpriseServices에 정의되는 메서드입니다. 다음은 파생 클래스가 모든 기본 클래스 메서드를 재정의하지 않는 일반적인 .NET 요구 사항입니다.

BankComponent Server 코드는 .NET에서 트랜잭션을 사용하는 Account 클래스 구현을 표시합니다. Account 클래스는 System.EnterpriseServices.ServicedComponent 클래스에서 파생됩니다. Transaction 특성은 클래스에 트랜잭션이 필요하다고 표시합니다. Transaction 특성이 사용되기 때문에 Synchronization 및 JIT 서비스는 자동으로 구성됩니다. AutoComplete 특성은 메서드를 실행하는 동안 처리되지 않은 예외가 throw될 경우 런타임에 트랜잭션을 위한 SetAbort 함수가 자동으로 호출되고, 그렇지 않을 경우 SetComplete 함수가 호출되도록 지정하는 데 사용됩니다. ApplicationName 특성은 이 응용 프로그램의 서비스 구성 데이터를 저장하는 COM+ 응용 프로그램에 이 어셈블리를 연결합니다. 이 클래스에 필요한 추가 수정은 코드에서 강조 표시됩니다.

BankComponent Server

using System.EnterpriseServices;
[assembly: ApplicationName("BankComponent")]
[assembly: AssemblyKeyFileAttribute("Demos.snk")]

namespace BankComponentServer
{
[Transaction(TransactionOption.Required)]
public class Account : ServicedComponent
{
[AutoComplete]
public bool Post(int accountNum, double amount)
{
// SetComplete를 호출하지 않고 데이터베이스를 업데이트합니다.
// 예외가 throw되지 않으면 SetComplete가 자동으로 호출됩니다.
}
}
}

BankComponent Server 네임스페이스에 있는 코드는 .NET에서 COM+ 서비스를 얼마나 쉽게 사용할 수 있는지를 보여 줍니다. 아래 목록은 코딩에서 배포까지의 전체 프로세스에 대한 요약입니다.

  1. 서버 어셈블리를 작성합니다.
  2. 어셈블리 작성:
    1. 어셈블리에 서명합니다. 키 파일은 프로젝트마다 한 번만 생성될 수 있으므로 컴파일할 때마다 생성할 필요가 없습니다. 키는 Microsoft .NET 명령 프롬프트 및 sn.exe를 다음과 같이 사용하여 생성될 수 있습니다.
      sn –k Demos.snk
    2. 코드를 컴파일합니다. System.EnterpriseServices에 대한 참조가 추가되어야 합니다.
  3. 응용 프로그램을 배포합니다.

    서비스되는 구성 요소를 사용하는 어셈블리는 COM+ 카탈로그에 등록되어야 합니다. ServicedComponent 클래스와 사용자 지정 특성은 관리되는 코드에서 COM+ 서비스에 액세스하는 두 가지 핵심 개념입니다. 서비스의 구성은 COM+ 카탈로그에 저장됩니다. 개체는 CLR 내부에서 존재하고 실행됩니다. 관리되는 개체와 관련된 COM+ 컨텍스트는 그림 1에 표시되어 있으며 다음 두 절에서 자세히 설명합니다.

    그림 1. 관리되는 구성 요소와 연결된 서비스

    COM+ 구성 요소에서는 카탈로그를 수동으로 구성해야 하지만, 서비스되는 구성 요소에서는 카탈로그가 코드에 있는 특성을 기반으로 업데이트될 수 있습니다. 명령줄 도구 regsvcs.exe를 사용하거나 관리되는 API를 액세스하는 스크립트를 작성하여 어셈블리를 명시적으로 등록할 수 있습니다. 자세한 내용은 아래의 배포 정보 절을 참조하십시오. 개발하는 동안 편리한 때에 어셈블리를 응용 프로그램 디렉터리에 간단하게 복사하여 배포하는 XCopy 배포 기능이 제공됩니다. 클라이언트 응용 프로그램이 ServicedComponent에서 파생되는 클래스의 인스턴스를 만들 때마다 런타임에서 COM+ 응용 프로그램의 어셈블리가 이미 등록되어 있는지 여부를 검색합니다. 등록되어 있지 않을 경우 로컬 디렉터리에서 어셈블리를 검색하여, 어셈블리가 있으면 해당 어셈블리에 있는 모든 서비스된 구성 요소를 COM+ 응용 프로그램에 등록하여 활성화될 수 있는 상태로 만듭니다. 이 등록을 지연 등록이라 하지만, 모든 시나리오에 적용되지는 않습니다. 예를 들어, COM+ 서버 응용 프로그램으로 표시되는 어셈블리는 모두 명시적으로 등록해야 합니다(아래 참조). 지연 등록은 관리되는 서비스 구성 요소를 호출하는 관리되지 않는 클라이언트에는 적용되지 않습니다. 지연 등록은 개발 기간 동안에는 유용하지만, 그 외에는 스크립트, 코드 또는 RegSvcs를 사용하여 어셈블리를 등록합니다.

  4. 어셈블리를 GAC에 저장합니다. 자세한 내용은 배포 절을 참조하십시오.
  5. 클라이언트를 실행합니다.

배포

사용자 지정 특성은 관리되는 코드에서 COM+ 서비스를 액세스하는 두 가지 핵심 개념 중 하나입니다. 사용자 지정 특성은 이전 코드 목록의 Transaction 사용자 지정 특성과 같은 필수 서비스를 지정하는 데 사용됩니다. 이 특성은 어셈블리 메타데이터에 있는 서비스에 대한 구성 옵션을 저장합니다. 사용자 지정 특성은 일부 코드가 어셈블리를 로드하고 반사를 사용하여 특성 인스턴스를 만든 다음 해당 특성에 대한 메서드를 호출하여 특성에 저장된 서비스 구성을 추출하는 방식으로 사용됩니다. 그런 다음 COM+ 카탈로그에 정보를 기록할 수 있습니다. 모든 단계를 실행하는 코드는 EnterpriseServices.RegistrationHelper에 포함됩니다. 등록 프로세스를 보다 쉽게 만들기 위해 모든 등록 양식에서 EnterpriseServices.RegistrationHelper 구성 요소를 사용합니다. 이 구성 요소는 COM 개체 뿐만 아니라 관리되는 클래스로도 액세스될 수 있습니다.

그림 2. 서비스되는 구성 요소 등록

개념적으로 RegistrationHelper는 다음 단계를 실행합니다.

  • RegistrationServices.RegisterAssembly를 사용하여 레지스트리에 어셈블리를 등록합니다. 따라서, 클래스는 레지스트리에 관리되는 코드로 작성된 COM 구성 요소로 표시되며 mscoree.dll을 가리키는 InprocServer32 키를 가집니다. 관리되는 클래스가 인터페이스를 구현하지 않을 경우 ClassInterfaceAttribute가 사용되지 않으면 해당 클래스의 공개 메서드가 COM+ 카탈로그에 표시되지 않습니다. 이것은 메서드 수준에 연결된 서비스 구성이 카탈로그에 저장될 수 없음을 의미합니다. 그러나, 일부 COM+ 서비스는 메서드 수준에서 구성될 수 있으며 인터페이스를 COM+ 카탈로그에 표시된대로 노출하려면 구성 요소가 있어야 합니다. 예를 들어, 메서드 수준의 COM+ 역할 기반 보안에서는 서비스를 구성하기 위한 인터페이스를 구현하는 구성 요소가 필요합니다. 이 문제에 대해서는 보안 절에 자세히 설명되어 있습니다.
  • TypeLibConverter를 사용하여 어셈블리에서 COM 형식 라이브러리를 생성합니다. ConvertAssemblyToTypeLib.
  • 형식 라이브러리를 등록합니다. 지금까지는 RegAsm.exe /tlb와 거의 동일합니다.
  • COM+ 응용 프로그램을 찾거나 만듭니다. 이름은 ApplicationName 특성, 어셈블리 이름, 제공된 응용 프로그램 이름/GUID 등에서 추출됩니다.
  • 형식 라이브러리에서 COM+ 관리 API를 사용하여 COM+ 응용 프로그램을 구성합니다.
  • 사용자 지정 특성을 모두 진행한 다음 IConfigurationAttribute를 사용하여 COM+ 카탈로그에 특정 서비스에 대한 구성 데이터를 기록합니다.

RegistrationHelper는 .NET를 설치할 때 만들어지는 COM+ 응용 프로그램에 있는 RegistrationHelperTx 클래스를 사용하여 트랜잭션 내부에서 이 단계를 실행하려고 시도합니다. 따라서, 등록에 실패하면 COM+ 카탈로그와 레지스트리가 원래의 상태로 복원됩니다. 그러나, 현재 생성된 형식 라이브러리는 디스크에(어셈블리가 GAC에 있을 경우 GAC에) 그대로 유지됩니다. 등록 중인 어셈블리가 또한 COM+ 서비스를 사용하는 다른 어셈블리를 참조할 경우 종속성 그래프에 있는 모든 어셈블리는 위에 나열된 단계를 실행합니다.

RegistrationHelper는 COM+ 카탈로그를 액세스하기 때문에 시스템에 관리되지 않는 코드 사용 권한 및 관리자 권한이 있어야 합니다. 클라이언트의 RegistrationHelper(지연 등록, RegSvcs, 스크립트/코드)의 경우에도 마찬가지입니다. 이것은 인터넷에서 다운로드했거나 네트워크 공유에 저장된 코드는 등록될 수 없음을 의미합니다.

트랜잭션 필요, 동기화를 사용 안 함으로 설정과 같이 호환되지 않는 특성 조합을 코딩할 수 있습니다. 이러한 조합은 현재 컴파일 기간이 아니라 COM+ 카탈로그에 기록하는 등록 기간 동안 검색됩니다. 일부 특성은 다른 특성에 암시적으로 종속됩니다. 예를 들어, Transaction 특성만 사용해도 Transaction, JustInTimeActivation 및 Synchronization 특성을 사용하는 것과 동일합니다. 관리되는 구성 요소가 등록되면 특성을 사용하여 '구성되지 않은' 기본값을 덮어쓰지 않는 한 COM+ 카탈로그 기본값이 사용됩니다. 예를 들어, 구성 요소를 등록하고 Transaction 특성을 지정하지 않으면 카탈로그의 트랜잭션 설정에 대한 구성되지 않은 기본값이 TransactionOption.Disabled로 설정됩니다. 개발자는 이 접근법을 사용하여 구성 요소에 어느 특성이 더 이상 필요하지 않을 경우 코드에서 해당 특성을 제거할 수 있습니다. 트랜잭션의 카탈로그 항목은 어셈블리가 다시 등록될 때 적절하게 다시 설정됩니다. 구성되지 않은 기본값에 대한 자세한 내용은 온라인 설명서를 참조하십시오. 기본 구성 값은 특성 매개 변수의 기본 값입니다. 예를 들어, [Transaction] 특성을 사용하는 것은 TransactionOption.Required를 나타냅니다.

관리되는 클래스의 서비스에 대한 구성 데이터는 COM+ 카탈로그에 저장되기 때문에 어셈블리가 등록된 후 특정 카탈로그 항목이 관리상 수정될 수도 있습니다. 일부 서비스는 이런 방식으로 수정되어서는 안됩니다. 예를 들어, 카탈로그에서 트랜잭션 서비스를 비활성화하면 코드가 올바르게 작동되지 않을 수 있습니다. 개체 생성 문자열, 보안 역할 등과 같은 배포 관련 설정은 등록 후에도 조작될 수 있습니다. 사후 등록 설정을 만들 경우에는 서비스된 구성 요소가 포함된 어셈블리를 XCopy 배포만으로 완전히 배포할 수 없을 수도 있습니다. COM+ 응용 프로그램의 가져오기/내보내기 기능은 응용 프로그램을 현재 상태로 분배하도록 도와 줍니다. 가져오기 및 내보내기에 대한 자세한 내용은 원격 절에 제공됩니다.

카탈로그가 구성 데이터에서는 참조되지 않고 어셈블리 메타데이터에서만 추출되는 경우도 있습니다. AutoComplete, JIT, 개체 풀링(풀 크기는 카탈로그에서 추출됨), 보안 메서드 특성 등의 경우가 그렇습니다. 이 문제에 대한 자세한 내용은 서비스를 설명하는 해당 절을 참조하십시오.

어셈블리를 등록하는 동안 COM+에 필요한 GUID가 자동으로 생성됩니다. 어셈블리가 서명되지 않을 경우 GUID는 형식 이름과 네임스페이스 이름만을 기반으로 하여 생성됩니다. 따라서, 어셈블리가 서명되지 않으면 고유하지 않은 GUID가 생성될 수 있습니다. COM+ 서비스를 사용하지 않지만 고유한 형식 이름이 필요한 .NET 어셈블리의 경우에도 마찬가지입니다. 따라서, COM+ 서비스를 사용하는 어셈블리는 서명되어야 합니다. 어셈블리가 서명되지 않으면 등록되지 않습니다. 등록은 COM+ 서비스를 사용하는 .NET 클래스에 글로벌 구성 데이터 저장소가 하나 있다는 의미를 내포합니다. 전용 어셈블리를 여러 응용 프로그램 디렉터리에 복사할 수 있더라도 궁극적으로는 그런 응용 프로그램들도 모두 서비스되는 구성 요소에 대해 하나의 구성 데이터를 참조합니다. 따라서, COM+ 카탈로그에서 구성 데이터를 변경하면 해당 클래스를 사용하는 모든 응용 프로그램이 영향을 받습니다. 이것은 서비스되는 구성 요소를 사용하는 동일한 어셈블리 사본을 구성하는 Microsoft ASP.NET 구성에 여러 vroot가 있다는 증거입니다. 동일한 응용 프로그램이 여러 구성을 갖게 하려면 Microsoft Windows .NET에서 COM+ 파티션을 사용합니다. .NET에서 COM+ 파티션을 사용하려면 ApplicationID 특성을 사용하지 않고 동일한 구성 요소를 여러 파티션에 설치하십시오. COM+에는 고유한 응용 프로그램 ID가 필요합니다.

일반적으로 클라이언트가 클라이언트 응용 프로그램 디렉터리가 아닌 다른 디렉터리에 있는 어셈블리에 액세스해야 하거나 어셈블리가 클라이언트 디렉터리에 없는 다른 프로세스에 로드되는 경우 GAC가 사용됩니다. 개념적으로, 서비스되는 구성 요소를 사용하는 전용 어셈블리는 실제로 공유되는 구성 데이터를 사용하는 공유 어셈블리입니다. ApplicationActivationOption이 라이브러리에 설정되면 어셈블리 내의 클래스에서 트랜잭션을 사용할 수 있습니다. 모든 어셈블리가 동일한 디렉터리에서 로드될 경우 하나의 클라이언트에서 해당 어셈블리를 사용할 수 있습니다. ApplicationActivationOption을 사용하는 어셈블리가 서버에 설정되어 있으면, 해당 어셈블리는 dllhost.exe에 의해 로드되며, 대체로 클라이언트와 동일한 디렉터리에 존재하지 않습니다. COM+ 서버 응용 프로그램에서 서비스되는 구성 요소를 사용하는 어셈블리는 GAC에 배치되어야 합니다. COM+ 라이브러리 응용 프로그램에서 서비스되는 구성 요소를 사용하는 어셈블리는 서로 다른 디렉터리에 있지 않는 한 GAC에 배치되지 않아도 됩니다. ASP.NET에서 호스팅될 경우에만 예외입니다. 섀도 복사를 사용하여 올바르게 작업하려면 어셈블리를 GAC에 배치하지 않아야 합니다.

서비스되는 구성 요소를 사용하는 .NET 응용 프로그램을 제거하려면 GAC에서 해당 어셈블리를 제거(GAC에 등록되어 있을 경우)하고, regsvcs.exe를 사용하여 COM+에서 어셈블리를 다시 등록한 다음 해당 어셈블리와 연결된 형식 라이브러리를 삭제합니다.

버전 지정

GUID 특성을 사용하여 COM+에 필요한 GUID를 고정시킬 수 있습니다. 그러나, GUID를 명시적으로 사용하는 대신 버전 지정을 사용하는 것이 좋습니다. 클래스가 서로 다른 서비스 특성으로 데코레이팅되거나 새로운 메서드 서명이 만들어질 때마다 어셈블리의 주 버전 번호 또는 부 버전 번호가 증분되어야 합니다. 등록은 버전마다 한 번만 실행하면 됩니다. 새로운 어셈블리 버전을 등록하면 해당 버전에 대한 새로운 GUID가 생성되고 구성 요소가 동일한 구성 요소 이름을 사용하여 동일한 COM+ 응용 프로그램에 등록됩니다. 따라서 구성 요소는 COM+ 응용 프로그램에 여러 번 표시됩니다. 그러나, 각 구성 요소에는 GUID에 의해 지정되는 고유한 ID가 있습니다. 각 인스턴스는 구성 요소의 특정 버전을 참조합니다. Microsoft Visual Studio .NET을 사용하여 .NET 응용 프로그램을 작성할 때 통지되는 경우도 있습니다. 이 환경에서는 프로젝트에 [assembly: AssemblyVersion("1.0.*")] 특성이 추가됩니다. 프로젝트는 빌드마다 새로운 빌드 번호가 생성되기 때문에 어셈블리가 다시 등록되면 GUID가 새로 생성됩니다. 따라서, 가능하면 빌드 번호를 수동으로 증분하는 것이 좋습니다. 클라이언트는 CLR 버전 정책을 사용하여 어셈블리에 바인딩하기 때문에 COM+ 응용 프로그램에서 올바른 클래스 버전이 사용됩니다. 다음은 서비스되는 구성 요소를 사용하는 어셈블리를 작성하는 side-by-side 시나리오입니다. 아래에 사용된 일부 활성화 항목은 다음 절에 설명되어 있습니다.

  • 관리되는 클라이언트, 관리되는 서버, 고정되지 않은 GUID가 어셈블리에서 사용됩니다.
  • 클라이언트는 버전 정책에 지정된 어셈블리를 로드합니다.
  • 관리되는 클라이언트, 관리되는 서버, 고정 GUID가 사용됩니다.
  • 클라이언트가 클래스를 활성화하고 버전 정책을 사용하여 이전 어셈블리 버전으로 이동하면, 활성화하는 동안 코드에 있는 고정 GUID를 사용하여 카탈로그에서 서비스 정보를 추출합니다. 따라서, 이 GUID를 사용하여 마지막으로 등록된 어셈블리에서 추출한 정보를 사용하여 개체를 만듭니다. 실제로 개체가 최신 버전인 경우도 있기 때문에 만들어진 개체(v2)에서 코드에 있는 참조(v1)로 캐스트하려고 시도하면 형식 캐스트 예외가 발생될 수 있습니다.
  • 관리되는 클라이언트, 관리되는 서버, 고정되지 않은 GUID, 빌드 번호만 변경합니다.
  • 새로운 GUID가 생성되더라도 형식 라이브러리는 하나의 버전에 대해 두 개의 번호만 가질 수 있기 때문에 여전히 동일한 버전 번호를 갖습니다. 작동은 되지만, 버전 2를 버전 1 위에 설치하면 버전 1이 제거되고, 버전 2의 형식 라이브러리 등록이 취소됩니다. 해결 방법 1: 다음에 릴리스되는 .NET Framework(V1.1)에서는 형식 라이브러리를 사용하여 어셈블리 버전을 독립적으로 지정할 수 있게 하여 이 문제를 해결했습니다. 이것은 어셈블리 버전 번호를 변경하면 형식 라이브러리 버전도 함께 변경되어야 함을 의미합니다. 해결 방법 2: 주 버전 번호와 부 버전 번호만 사용합니다.
  • 관리되지 않는 클라이언트, 관리되는 서버, 고정되지 않은 GUID가 사용됩니다.
    • 클라이언트는 GUID를 사용하여 구성 요소를 만듭니다. Interop은 GUID에서 이름을 확인한 다음 버전 정책을 적용합니다. 동일한 어셈블리의 버전 1과 버전 2가 동일한 시스템에 있고 정책을 사용하여 버전 2로 이동하면 버전 2가 관리되지 않는 클라이언트가 됩니다.
    • 버전 1을 설치하고, 버전 2를 설치한 다음 버전 1을 제거합니다. 이제 버전 2로 리디렉션하는 버전 정책이 없으면 클라이언트가 구성 요소를 만들 수 없습니다. 또한, 버전 1 등록 정보에 대한 레지스트리 항목이 있어야 합니다. 제거된 버전 1에 대한 레지스트리 정보를 만드는 한 가지 방법은 Windows XP의 COM+ 별칭 기능을 사용하는 것입니다.

버전 지정은 동일한 COM+ 응용 프로그램에 있는 모든 구성 요소에 적용됩니다. 즉, 개별 응용 프로그램의 버전을 자동으로 지정할 수 있는 방법은 없습니다. 예를 들어, 버전 정책을 사용하여 응용 프로그램에 있는 역할의 버전을 지정할 수 없습니다. 응용 프로그램 이름 특성을 사용하여 응용 프로그램 버전을 지정합니다.

서비스되는 구성 요소

활성화

Enterprise Services 인프라는 컨텍스트의 개념을 기초로 합니다. 컨텍스트는 비슷한 실행 요구 사항을 갖는 개체를 위한 환경입니다. 서비스는 활성화하는 동안 및/또는 메서드 호출을 차단하는 동안 적용될 수 있습니다. COM+ 서비스는 관리되지 않는 코드로 작성되지만 .NET의 COM interop 기술을 사용할 때보다 .NET과 COM+ 서비스가 훨씬 깊게 통합됩니다. ServicedComponent에서 파생하지 않을 경우 등록 프로세스에서 원하는 효과를 얻을 수 없습니다.

서비스되는 구성 요소는 다양한 조합으로 활성화 및 호스트될 수 있습니다. 그림 3에서 설명한 것처럼 이 토론에서는 in-process(동일한 app-domain), 상호 app-domain(동일한 process), 상호 프로세스 활성화의 세 가지 경우를 참조합니다. 세 경우의 중요한 점은 구성 요소를 호출할 때 교차되는 범위입니다. in-process 활성화는 잠정적인 상호 컨텍스트 범위를 증가시키고, 상호 app-domain의 경우에는 상호 컨텍스트 범위와 상호 응용 프로그램 도메인 범위가 모두 있습니다. 반면에 상호 프로세스의 경우에는 상호 시스템/상호 프로세스와 상호 컨텍스트 범위가 함께 처리됩니다.

그림 3. 서비스되는 구성 요소의 활성화 호스트

서비스되는 구성 요소는 원격 .NET에 따라 구현되어 관리되지 않는 코드 또는 관리되는 코드로 작성된 서비스에 연결할 수 있는 포괄적인 메카니즘을 제공합니다. 서비스되는 구성 요소는 ContextBoundObject에서 파생되어 IDisposable와 같은 다양한 인터페이스를 구현합니다. ProxyAttribute 파생 사용자 지정 특성을 사용하면 CLR의 활성화 체인을 사용자에 맞게 쉽게 사용자 지정할 수 있습니다. 사용자 지정 실제 프록시를 작성하여 차단을 사용자에 맞게 설정할 수 있습니다. 새롭게 서비스되는 구성 요소 파생 클래스가 필요할 경우 활성화 호출이 CoCreateInstance에 대한 관리되는 C++ 래퍼를 실제로 호출하도록 활성화 체인을 사용자에 맞게 설정합니다. 이렇게 하면 COM+에서 이전에 등록된 어셈블리의 COM+ 카탈로그에 저장된 정보를 기반으로 하는 관리되지 않는 컨텍스트와 서비스를 설정할 수 있습니다. 이 단계는 지연 등록이 구현되는 단계이기도 합니다. 어셈블리를 등록하는 동안 InprocServer32 키는 mscoree.dll을 가리키므로, COM+ CreateInstance를 리디렉션하면 런타임으로 돌아와 실제 관리되는 개체가 만들어집니다. 따라서, 활성화하는 동안에 사용자 정의 실제 프록시 개체가 만들어집니다. 이 프록시의 in-process 버전을 서비스되는 구성 요소 프록시 또는 SCP라 합니다. 그림 4에 설명되어 있습니다.

그림 4. 활성화 경로

활성화 호출의 반환 경로에서는 관리되지 않는 COM+를 통해 관리되는 코드로부터 관리되는 참조를 마샬링한 다음 관리되는 코드로 돌아옵니다(그림 4, 라인 1의 역경로). 실제 개체가 만들어진 위치에 따라 클라이언트쪽에서 관련 양식으로 참조를 역마샬링합니다. in-process 활성화에서 그림 5는 참조가 투명한 프록시(TP)에 직접 참조로 역마샬링됨을 나타냅니다. 상호 app-domain 참조는 .NET 원격 프록시로 역마샬링됩니다. 상호 프로세스 또는 상호 시스템 참조(그림 6)에서는 추가 역마샬링이 필요합니다. 즉, COM interop은 활성화 및 역마샬링이 실행되는 동안 ServicedComponent에 의해 구현된 IManagedObject를 호출합니다. 원격 서비스되는 구성 요소 프록시(RSCP)는 활성화하는 동안 IServicedComponentInfo를 호출하여 서버 개체의 URI를 가져옵니다. 이것은 활성화하는 동안 두 개의 원격 호출이 만들어짐을 의미합니다. 메서드 수준에서 COM+ 역할 기반 보안이 필요할 경우 인터페이스를 역할에 연결해야 인프라가 해당 인터페이스를 호출할 때 역마샬링할 수 있습니다. 보안 절에서는 역할 기반 보안 구성에서 교차 프로세스 활성화와 마샬링이 갖는 함축된 의미에 대해 설명합니다.

그림 5. in-process 호출 인프라

그림 6. out of process 호출 인프라

사용자 지정 실제 프록시(차단용)를 만들고 관리되지 않는 컨텍스트를 만들기 위해 차단 서비스 구문을 실행하는 데 필요한 컨텍스트 인프라만 COM+에 남겨 두고 활성화 체인을 사용자에 맞게 설정했습니다. COM+ 컨텍스트는 이제 COM 개체가 아니라 관리되는 개체에 연결됩니다.

차단

그림 7에서는 in-process 메서드 호출 인프라를 보여 줍니다. 사용자 지정 프록시(SCP)를 사용하여 관리되는 호출을 차단할 수 있습니다. COM+ 컨텍스트 ID는 활성화하는 동안 SCP에 저장됩니다. 관리되는 개체가 서비스되는 구성 요소를 호출하면 대상 SCP에 저장된 컨텍스트 ID를 현재 컨텍스트의 컨텍스트 ID와 비교하여 두 컨텍스트 ID가 동일할 경우 실제 개체에서 호출이 직접 실행됩니다. 컨텍스트 ID가 서로 다르면 SCP가 COM+에 대한 호출을 만들어 컨텍스트를 전환한 다음 메서드 호출을 받을 서비스를 렌더링합니다. in-process 호출의 경우에는 AppDomain이 COM+가 되는 점을 제외하고는 AppDomain.DoCallBack과 비슷합니다. 'DoCallBack' 함수가 COM+(그림 7의 2단계)에 전달되어 컨텍스트를 전환하고 서비스를 렌더링한 다음 콜백 함수가 SCP를 호출합니다. SCP는 데이터 마샬링을 실행하고 실제 개체에서 메서드를 호출합니다. 메서드가 종료되면 반환 경로를 통해 COM+가 메서드 호출을 빠져나올 수 있는 구문을 렌더링할 수 있습니다(그림 7의 5단계). COM+는 서비스를 렌더링하는 데만 사용됩니다. 데이터 마샬링과 메서드 호출은 .NET 런타임에서 실행되므로, 메서드를 호출할 때 유형을 변환(예: String에서 BSTR로)할 필요가 없습니다. COM interop를 in-process 호출에 사용한 경우 데이터를 마샬링해야 합니다. in-process 호출의 경우 관리되지 않는 코드의 서비스 렌더링 요청은 COM interop 호출이 아닙니다.

그림 7. in-process 호출 인프라

정적 메서드에 대한 호출은 투명하고 실질적인 프록시에 전달되지 않습니다. 따라서, 정적 메서드에서는 차단 서비스를 사용할 수 없습니다. 정적 메서드는 클라이언트의 컨텍스트 내부에서 호출됩니다. 내부 메서드는 올바른 컨텍스트에서 호출됩니다. 이것은 새 트랜잭션을 위해 구성된 개체에 대한 내부 메서드를 호출하는 클라이언트가 새 트랜잭션에 포함됨을 의미합니다. 그러나, 메서드 수준 서비스는 COM+ 카탈로그(자세한 내용은 이 항목 다음에 있는 보안 항목 참조)에 인터페이스가 있어야 하기 때문에 내부 메서드는 메서드 수준 서비스를 위해 구성될 수 없습니다. 서비스는 속성에 적용될 수 있지만 메서드 수준 속성(예: AutoComplete)은 getter/setter 메서드에 개별적으로 배치되어야 합니다.

AutoComplete 특성을 활용하면 코드를 작성하지 않고 트랜잭션을 사용하여 서비스에 편리하게 액세스할 수 있습니다. ContextUtil.SetAbort 또는 ContextUtil.SetComplete를 사용할 수도 있습니다. 메서드의 속성에 있는 확인란 하나를 설정하여 COM+ 탐색기에서 이 서비스를 구성할 수 있습니다. 그러나, 관리되는 개체는 인터페이스를 구현할 필요가 없습니다. 서비스되는 구성 요소의 경우에도 마찬가지입니다. 인터페이스에서 메서드가 선언되지 않으면 등록 시 카탈로그에 메서드 수준 서비스를 구성할 수 없으므로, 구성이 메타데이터에만 저장될 수 있습니다. 메서드에 대한 인터페이스가 없으면 AutoComplete 특성이 있을 경우 IRemoteDispatch.RemoteDispatchAutoDone에 저장된 구성 정보를 사용하여 SCP에서 컨텍스트 전환이 실행됩니다. AutoComplete가 없을 경우 IRemoteDispatch.RemoteDispatchNotAutoDone이 사용됩니다. IRemoteDispatch는 ServicedComponent에 의해 구현되는 인터페이스입니다. 관리되지 않는 클라이언트는 IDispatch(런타임에 바인딩)를 사용하여 인터페이스가 없는 서비스된 구성 요소만 호출할 수 있으므로 이 경우 실제 프록시가 없기 때문에 AutoComplete 구문을 사용할 수 없습니다. 인터페이스가 사용되는 경우에도 AutoComplete의 구성은 관리되는 클라이언트의 매타데이터에 의해 제어됩니다. DCOM 메서드 호출은 out of process의 경우에만 RemoteDispatchAutoDone에 만들어집니다. Out-of-process 구성 요소는 DoCallBack 메카니즘을 사용하지 않는 대신 DCOM을 사용하여 호출을 배달하고 서비스를 렌더링합니다. 인터페이스에 메서드가 있으면 원격 서비스되는 구성 요소의 인터페이스 메서드는 DCOM을 사용하여 호출됩니다. 메서드가 없으면 호출이 ServicedComponent에 있는 IRemoteDispatch 인터페이스에 발송됩니다. 이것은 Dispose()와 같은 호출이 DCOM을 통해 호출됨을 의미합니다. 이것의 의미에 대해서는 나중에 설명합니다.

컨텍스트

ContextUtil 클래스를 사용하여 관련된 COM+ 개체 컨텍스트와 해당 속성을 액세스합니다. 이 클래스는 관리되지 않는 코드에서 CoGetObjectContext에 의해 반환되는 개체와 비슷한 기능을 제공합니다. 서비스되는 구성 요소와 연결된 관리되는 개체 컨텍스트는 관련있는 관리되지 않는 개체 컨텍스트와 다른 목적으로 사용됩니다. 이 클래스는 트랜잭션이 필요한 개체 하나(루트 역할)와 서비스되는 구성 요소에서 파생되지 않는 두 개체(컨텍스트 관리되는 개체를 빠르게 예시하는 자식 개체 역할)의 관리되는 세 개체를 작성하여 매니페스트됩니다. 서비스되지 않는 구성 요소는 Transactions이 지원하여 서비스되는 구성 요소처럼 동작합니다. 즉, 리소스 관리자를 호출하고 필요한 경우 ContextUtil.SetAbort를 사용할 수 있습니다. 루트 개체가 만들어지면 관련된 관리되지 않는 컨텍스트가 만들어진 다음 현재 스레드에 연결됩니다. 자식 개체가 호출되면 해당 개체는 관리되지 않는 컨텍스트와 관련이 없기 때문에 COM+ 컨텍스트를 변경할 필요가 없습니다. 따라서, 스레드는 루트의 관리되지 않는 컨텍스트 ID를 계속해서 유지합니다. 자식 개체가 리소스 관리자를 호출하면 리소스 관리자는 해당 자식 개체를 실행하는 스레드에서 관리되지 않는 컨텍스트를 추출합니다. 이 컨텍스트는 루트 개체의 관리되지 않는 컨텍스트입니다. 관리되지 않는 컨텍스트에 의존하는 것은 매우 위험하므로 이후 버전에서는 관리되지 않는 컨텍스트를 관리되는 컨텍스트에 병합하여 자식 개체를 잠정적으로 다른 관리되는 컨텍스트에 연결할 것입니다. 리소스 관리자는 루트 개체의 컨텍스트를 선택할 수 없습니다. 따라서, 새로운 버전의 .NET으로 업그레이드하면 이런 유형의 동작에 의존하는 코드가 중단될 수 있습니다.

성능 결과

이 절에서는 관리되는 클라이언트/관리되는 서버 서비스된 구성 요소 솔루션의 성능을 관리되지 않는 클라이언트/서버 솔루션과 비교합니다. in-process 메서드에 대해서는 다음 표에서 설명합니다. 트랜잭션에 대해 구성되는 ServicedComponent가 단지 번호만을 추가하는 단일 메서드를 사용하여 C#로 작성되었습니다. 비교를 위해 해당 C++ 구현을 사용했습니다. 이 비교에서는 실제로 작업하지 않고 관리되는 솔루션과 관리되지 않는 솔루션 사이의 차이점을 보여 줍니다. In-process 활성화 속도는 관리되는 솔루션에서 약 3.5배 더 느리고 메서드 호출 비용은 컨텍스트 스위치가 있을 때 약 2배 가량 더 비쌉니다. 그러나, 컨텍스트 전환이 필요한 서비스되는 구성 요소 메서드와 컨텍스트 전환이 필요하지 않는 서비스되는 구성 요소 메서드를 비교해 보면, in-process 서비스되는 구성 요소 차단 인프라의 성공을 나타내는 약 3등급의 차이가 발생합니다. out of process 솔루션의 경우 활성화 비용이 약 2배 더 비싸고, 상호 컨텍스트 메서드 호출 비용은 약 3배 가량 더 비쌉니다.

표 1에서는 관리되는 솔루션과 관리되지 않는 솔루션을 사용할 때의 in-process 활성화와 메서드 호출의 조정되는 시간을 비교하여 보여 줍니다.

표 1. In-process 활성화 및 메서드 호출

  관리되는 솔루션 관리되지 않는 솔루션
활성화 35 10
Cross-context-do-nothing 메서드 호출 2 1
Cross-context-do-work 메서드 호출 200 100

활성화는 'do-nothing' 메서드에 대한 메서드 호출보다 약 한 등급 정도 비용이 비쌉니다. 일부 작업에 추가하면 활성화 및 메서드 호출 시와 동일한 등급의 관련이 없는 DTC 트랜잭션만 가져옵니다. 메서드 호출이 풀링된 데이터베이스 연결을 열면 메서드 호출 작업의 등급이 활성화와 'do-nothing' 메서드 호출을 합한 것보다 한 등급 더 커지므로, 서비스되는 구성 요소 인프라의 오버헤드가 실제 작업이 실험에 추가될 때 이론적으로 일치됩니다.

개체 수명

Just-In-Time 활성화

JIT(just-in-time) 서비스는 격리에는 일반적으로 사용되지 않습니다. 트랜잭션 서비스 및 종종 개체 풀링과 함께 암시적으로 사용됩니다. 다음 예는 일부 관심 항목을 강조 표시하는 데 도움이 됩니다. 아래 코드에서 .NET 클래스는 JIT 서비스만을 사용하여 작성됩니다.

using System;
using System.EnterpriseServices;
[assembly: AssemblyKeyFile("Demos.snk")]
[assembly: ApplicationName("JITDemo")]

namespace Demos
{
[JustInTimeActivation]
public class TestJIT : ServicedComponent
{
public TestJIT()
{ // 호출되기 전에
}
[AutoComplete]
public void DoWork ()
{ // 다음을 사용하여 결과 표시 ..
// 1. 자동 완성 특성 또는
// 2. ContextUtil.DeactivateOnReturn = true 또는
// 3. ContextUtil.SetComplete();
}
public override void Dispose(bool b)
{ // 이 메서드를 선택적으로 재지정하고 사용자에 맞게 설정할 수 있는
// 삭제 논리를 실행합니다. b==true이고, Dispose()가
// 클라이언트에서 호출되었고 false이면 GC는 해당 개체를 정리합니다.
}
}
}

클래스는 ServicedComponent에서 파생되고 JIT 특성을 사용하여 필요한 특정 서비스를 나타냅니다. 관리되지 않는 코드에서 Activate 및 Deactivate 메서드를 재정의하려면 클래스에서 IObjectControl 인터페이스를 구현해야 합니다. ServicedComponent 클래스는 Activate 및 Deactivate 이벤트를 처리하기 위해 재정의될 수 있는 가상 메서드를 갖습니다. 그러나, ServicedComponent와 실제 프록시인 SCP 모두 IObjectControl을 구현하지 않습니다. 대신 SCP는 COM+에서 IObjectControl 인터페이스를 요청하면 프록시를 해체합니다. 해체시 COM+의 호출은 ServicedComponent의 가상 메서드에 전달됩니다. 메서드의 AutoComplete 특성을 사용하거나, ContextUtil.SetComplete(), ContextUtil.SetAbort()을 호출하거나 ContextUtil.DeactivateOnReturn을 설정하여 DeactivateOnReturn 비트를 설정합니다. 메서드가 호출될 때마다 DeactivateOnReturn 비트가 설정된다고 가정할 경우 메서드는 클래스 생성자, Activate, 실제 메서드 호출, Deactivate, Dispose(true), 클래스 종료자(있을 경우)의 순으로 호출됩니다. 다른 메서드가 호출되면 동일한 시퀀스가 반복됩니다. 우수한 디자인을 위해서는 Activate 및 Deactivate 메서드만 무시하여 개체를 제거한 다음 개체 풀로 다시 놓을 때를 잘 알고 있어야 합니다. Activate 및 Deactivate의 나머지 논리는 클래스 생성자와 Dispose(bool) 메서드에 놓여야 합니다. DeactivateOnReturn 비트는 다음과 같은 방법으로 설정될 수 있습니다.

  1. 클라이언트는 단일 메서드 호출에 대해서만 개체 상태를 사용합니다. 메서드에서 시작할 때 새로운 실제 개체가 만들어져 SCP에 첨부됩니다. 메서드가 종료될 때 Dispose(true)와 실제 개체 종료자(있을 경우)가 차례로 호출되어 실제 개체가 비활성화됩니다. 그러나, 연결된 COM+ 컨텍스트, SCP 및 TP는 활성 상태로 유지됩니다. 클라이언트 코드는 실제 개체라고 판단되는 참조를 그대로 유지합니다(투명한 프록시). 클라이언트가 동일한 참조에 대해 다음 메서드 호출을 만들면 새로운 실제 개체를 만들어 SCP에 첨부하여 메서드 호출 서비스를 제공합니다. 새 개체 만들기 요구사항을 제거하려면 개체 풀링 절을 참조하십시오. 실제 개체를 비활성화하려면 메서드 호출이 종료될 때 실제 개체가 완료 표시되어야 합니다. 이것은 다음을 사용하면 가능합니다.
    1. 클래스 메서드의 AutoComplete 특성
    2. ContextUtil class, DeactivateOnReturn 또는 SetComplete에 대한 두 메서드 호출 중 하나
  2. 클라이언트는 메서드를 종료하기 전에 완료 비트를 false로 설정하여 각 메서드 호출 후에 개체를 비활성화하지 않고 동일한 개체에서 여러 메서드를 호출합니다. 예를 들어, 양식 수준에서 JIT를 사용하는 서비스되는 구성 요소의 범위를 지정하고 메서드의 완료 비트를 false로 명시적으로 설정하여 두 양식 단추를 사용하여 동일한 개체 인스턴스에서 메서드를 호출합니다. 완료 비트가 true로 설정되어야 하는 경우도 있습니다. 이 접근 방법은 계약이 클라이언트와 개체 사이에 있다는 것을 나타냅니다. 이는 클라이언트에 의해 명시적 또는 암시적으로 실행될 수 있습니다.
    1. 클라이언트는 완료된 개체에서 특정 메서드를 호출하여 해당 개체를 비활성화하는 방법을 알고 있어야 합니다. 메서드 구현에서는 옵션 1의 아이디어를 사용합니다. 개체 참조는 동일한 호출 시퀀스를 사용하여 다시 호출될 수 있으며 이는 실제 개체를 새로 만들다는 것을 의미합니다.
    2. 개체는 클라이언트가 해당 개체에서 Dispose() 메서드를 호출하면 명시적으로 삭제됩니다. Dispose()는 ServicedComponent에 정의된 메서드이며, Dispose(true)와 클래스 종료자(있는 경우)를 차례로 호출한 다음 관련된 COM+ 컨텍스트를 해체합니다. 이 경우 개체 참조에 대한 추가 메서드 호출은 더 이상 만들어질 수 없습니다. 추가 메서드 호출을 시도하면 예외가 throw됩니다. 많은 클라이언트에서 동일한 개체를 사용할 경우 마지막 클라이언트에서 개체 작업이 완료되면 Dispose() 호출이 완료되어야 합니다. 그러나, JIT 개체의 상태 비저장 특성으로 인해 클라이언트 모델별로 단일 인스턴스를 사용하는 디자인 연습을 해야 합니다.
    3. 개체는 완료 비트를 절대 true로 설정하지 않고 클라이언트는 절대 Dispose()를 호출하지 않습니다. 실제 개체, 프록시 및 컨텍스트는 가비지 수집이 발생되면 삭제됩니다. GC에 의해 초기화되는 메서드 호출 순서는 Deactivate, Dispose(false), 클래스 종료자(있을 경우)입니다.

모든 서비스되는 구성 요소에는 SCP(원격의 경우 RSCP)에 참조로 저장되는 관련된 COM+ 컨텍스트가 있습니다. 참조는 GC가 발생되거나 클라이언트가 Dispose()를 호출하는 경우에만 릴리스됩니다. GC에 의존하여 컨텍스트를 정리하지 않는 것이 좋습니다. COM+ 컨텍스트는 하나의 OS 핸들을 잡고 있으며 일부 메모리에서는 GC가 발생할 때까지 핸들의 해제가 지연될 수 있습니다. 또한, ServicedComponent에 종료자가 없더라도 SCP는 종료자를 구현합니다. 즉, COM+ 컨텍스트 참조는 첫 번째 수집 단계에서 정확히 수집됨을 의미합니다. 실제로, SCP에서 종료자가 호출될 때 컨텍스트는 종료자 스레드에 의해 삭제되지 않고, 종료자 스레드로부터 컨텍스트 삭제 작업을 제거하여 내부 대기열에 놓습니다. 서비스되는 구성 요소의 생성, 사용 및 삭제가 빠르게 진행되는 업무가 가중되는 환경에서는 작업에 의해 종료자 스레드가 소비될 수 있기 때문에 이 작업이 수행됩니다. 대신, 내부 스레드가 대기열에 서비스를 제공하고 이전 컨텍스트는 삭제됩니다. 또한, 새 ServicedComponent를 만드는 응용 프로그램 스레드는 대기열에서 항목을 제거하려고 시도한 다음 이전 컨텍스트를 삭제합니다. 따라서, 클라이언트에서 Dispose()를 호출하면 클라이언트 스레드를 사용하여 COM+ 컨텍스트를 바로 해체하여 컨텍스트가 사용하는 핸들과 메모리 리소스를 해제합니다. Dispose()를 호출하면 예외가 throw되는 경우도 있습니다. 한 가지 경우는 개체가 루트가 아닌 중단된 트랜잭션 컨텍스트에서 활성화되는 경우입니다. 이 경우 Dispose()를 호출하면 CONTEXT_E_ABORTED 예외가 발생될 수 있습니다. 다른 경우에 대해서는 개체 풀링에 설명되어 있습니다.

성능의 관점에서는 ServicedComponent 파생 클래스에서 종료자를 구현하는 대신 이 논리를 Dispose(bool) 메서드에 놓는 것이 더 좋습니다. SCP는 종료자를 구현하지만 실제 개체의 종료자는 반사를 통해 호출됩니다.

다음은 JIT 사용에 대한 좋은 디자인 사례입니다.

  • 생성자 및 Dispose(bool) 메서드에 사용자 지정 활성 및 종료 코드를 배치하여 종료자를 구현하지 않고 메서드에 있는 AutoComplete 특성을 사용하여 종료를 나타내는 단일 호출 패턴을 사용합니다.
  • 클라이언트에서 개체 작업이 완료되면 클라이언트에서 Dispose()를 호출합니다.

이 토론에서는 클라이언트가 관리되고 구성 요소가 in-process인 것으로 가정합니다. 구성 요소가 out-of-process인 경우:(자세한 내용은 원격 절 참조)

  • GC는 클라이언트 활성 개체에 대한 .NET 원격 임대 기간이 만료되면 해당 개체를 정리합니다.
  • 앞에서 설명한 것처럼 out-of-process 구성 요소에서 메서드를 호출하면 DCOM을 사용하여 컨텍스트를 전환한 다음 메서드 호출을 전달합니다. 구성 요소가 JIT에 의해 비활성화된 다음 Dispose()가 호출되면 서버 컨텍스트가 입력되고 실제 개체가 다시 만들어져 DCOM을 호출한 다음 다시 비활성화됩니다. in-process 구성 요소의 경우 실제 개체가 비활성화되면 구성 요소를 재활성화하는 Dispose() 호출이 서비스되기 전에는 올바른 컨텍스트로 전환하려는 시도 없이 해당 컨텍스트만 삭제됩니다.

개체 풀링

개체 풀링의 기본 전제는 개체 재사용입니다. 개체 풀링은 대부분 JIT와 함께 사용됩니다. 풀링된 COM 구성 요소와 풀링된 .NET 구성 요소의 경우에도 마찬가지입니다.

using System;
using System.EnterpriseServices;
[assembly: AssemblyKeyFile("Demos.snk")]
[assembly: ApplicationName("OPDemo")]

namespace Demos
{
[ObjectPooling(MinPoolSize=2, MaxPoolSize=50, CreationTimeOut=20)]
[JustInTimeActivation]
public class DbAccount : ServicedComponent
{
[AutoComplete]
public bool Perform ()
{ // 작업을 실행합니다.
}
public override void Activate()
{ // .. 활성화 메시지를 처리합니다.
}
public override void Deactivate()
{ // .. 비활성화 메시지를 처리합니다.
}
public override bool CanBePooled()
{ // .. CanBe Pooled 메시지를 처리합니다.
// 기본 구현은 false를 반환합니다.
return true;
}
}
}

JIT를 사용하는 경우처럼 개체 풀링은 다음과 같은 두 가지 방법 중 하나로 사용될 수 있습니다.

  1. 단일 호출 패턴. 코드에서 개체는 클라이언트가 메서드를 호출하려고 시도할 때 풀로부터 검색된 다음 JIT가 개체 풀링과 함께 사용되고 메서드를 호출하는 동안 완료 비트가 true로 설정되는 단일 메서드 호출이 종료되면 풀로 다시 반환됩니다. JIT 사용과 동일한 단일 호출 접근법이 여기서도 적용됩니다. 생성자는 개체가 만들어져 풀에 배치될 때 한 번만 호출됩니다. JIT 및 풀링된 개체를 사용할 때 메서드 호출 순서는 활성화, 메서드 호출, 비활성화, CanBePooled입니다. CanBePooled가 true를 반환하면 개체가 풀에 다시 놓입니다. 컨텍스트는 앞에서 설명한 것처럼 활성 상태로 유지됩니다. 서비스되는 구성 요소는 매개 변수화된 생성자를 사용할 수 없기 때문에 임의의 개체가 풀로부터 추출된 후의 후속 메서드 호출에서는 생성자를 다시 호출하지 않고 동일한 메서드 호출 순서가 반복됩니다. 마지막으로, 클라이언트가 풀링된 개체에서 Dispose()를 호출하면 in-process 경우에서 컨텍스트만 삭제됩니다. out-of-process의 경우에는 앞에서 설명한 것처럼 Dispose()를 호출하여 개체를 다시 활성화할 수 있습니다. 개체가 풀링되면 해당 풀에서 개체를 가져와야 합니다. 즉, Dispose()는 CO_E_ACTIVATION_TIMEOUT과 함께 예외를 throw할 수 있습니다.
  2. 다중 호출 패턴. 비슷한 여러 메서드 호출 접근법을 사용하여 JIT 서비스를 강조 표시하면 많은 개체에 대한 많은 메서드 호출이 있은 후에만 풀로 다시 배치될 수 있습니다. 그러나, 클라이언트가 Dispose를 호출하지 않고 JIT가 사용되지 않으면 GC에 의해 개체가 풀에 다시 놓일 때 종료해야 하는 풀링된 개체의 자식 개체가 부활되는지 확인할 수 없습니다. 풀링된 개체가 가비지 수집되면 구성원이 여전히 유효한 비활성화가 보장되지 않습니다. 다음에 릴리스되는 .NET Framework(V1.1)에서는 canBePooled 및 Deactivate가 호출되지 않고 개체가 풀로 다시 놓이지 않습니다. 이 접근법에는 보다 일관성 있는 모델이 있습니다. Deactivate에서 자식 개체가 활성화되고, Dispose()에서는 자식 개체의 활성 상태가 보장되지 않습니다. 따라서, JIT를 사용하지 않는 풀링된 개체에 대해 Dispose()를 호출해야 합니다. 그렇지 않으면 개체가 풀로 반환되지 않습니다.

관리자는 어셈블리가 배포 및 등록된 후에 풀 크기와 시간 초과를 수정할 수 있습니다. 풀 크기 변경은 프로세스가 다시 시작될 때 적용됩니다. Windows XP 이상에서 풀 크기는 프로세스 내에 있는 각 응용 프로그램 도메인에 적용됩니다. Windows 2000에서 풀 크기는 기본 응용 프로그램 도메인에 존재하는 풀링된 개체의 프로세스 너비입니다. 동일한 프로세스 내의 다른 응용 프로그램 도메인에서 풀링된 개체를 필요로 할 경우 클라이언트는 응용 프로그램 도메인과 풀링된 개체 사이에서 효과적으로 통신합니다. 이것을 실현하는 방법은 각 IIS vroot가 개별 응용 프로그램 도메인에 하우징되는 ASP.NET 응용 프로그램 내부에서 COM+ 라이브러리 응용 프로그램에 정의된 풀링된 .NET 개체를 사용하는 것입니다.

서비스되는 구성 요소는 매개 변수화된 생성자를 사용할 수 없습니다.

보안

CAS(코드 액세스 보안)

.NET Framework 보안을 사용하면 실행 권한이 있는 경우에만 코드로 리소스를 액세스할 수 있습니다. 이것을 설명하기 위해 .NET Framework는 보호되는 리소스를 액세스할 수 있는 코드의 권한을 의미하는 사용 권한의 개념을 사용합니다. 코드는 필요한 사용 권한을 요청합니다. .NET Framework에는 코드 액세스 권한 클래스가 제공됩니다. 또한, 사용자 지정 권한 클래스를 작성할 수 있습니다. 이 사용 권한을 사용하면 .NET Framework에서 작업을 허용할 때 코드에 필요한 내용 및 코드 호출자에 필요한 권한을 나타낼 수 있습니다. System.EnterpriseServices를 통과하는 모든 코드 경로에는 관리되지 않는 코드 권한이 필요합니다.

.NET의 코드 액세스 보안은 웹으로부터 코드를 다운로드하여 작성자를 완전히 신뢰할 수 없는 응용 프로그램에서 가장 유용합니다. 일반적으로 서비스되는 구성 요소를 사용하는 응용 프로그램은 완전히 신뢰할 수 있고, 여러 프로세스 간에 적용되는 보안을 필요로 하며, 배포시에 역할 구성을 가능하게 합니다. 이 기능은 COM+ 역할 기반 보안에 노출되는 기능입니다.

System.EnterpriseServices를 통과하는 모든 코드 경로에는 관리되지 않는 코드 권한이 필요합니다. 이것은 다음과 같은 의미를 내포합니다.

  • 관리되지 않는 코드 사용 권한은 서비스되는 구성 요소에 대한 상호 컨텍스트 호출을 활성화 및 실행하는 데 필요합니다.
  • 서비스되는 구성 요소에 대한 참조가 신뢰할 수 없는 코드에 전달될 경우 신뢰할 수 없는 코드로부터 ServicedComponent에 정의된 메서드를 호출할 수 없습니다. 그러나, 일부 환경에서는 ServicedComponent에서 파생된 클래스에 정의된 사용자 지정 메서드를 신뢰할 수 없는 코드로부터 호출할 수 있습니다. 컨텍스트 전환, 차단 서비스 등이 필요하지 않거나 메서드 구현에서 System.EnterpriseServices의 구성원을 호출할 수 없는 경우에는 신뢰할 수 없는 코드로부터 호출을 만들 수 있습니다.

또한, .NET 버전 1에서는 스레드가 전환될 때 보안 스택이 복사되지 않기 때문에 서비스되는 구성 요소에서 사용자 지정 보안 권한을 사용할 수 없습니다.

RBS(약할 기반 보안)

System.EnterpriseServices에서는 COM+ 보안 메카니즘의 기능을 미러하는 .NET 개체에 보안 서비스를 제공합니다. COM+ 서버 응용 프로그램을 사용하여 구성 요소를 호스트할 경우 RBS는 DCOM 전송 프로토콜을 사용하여 원격 클라이언트에서 구성 요소를 활성화해야 합니다. 원격에 대한 자세한 내용은 다음 절을 참조하십시오. COM+의 보안 호출 컨텍스트와 ID는 관리되는 코드에서 사용할 수 있습니다. 또한, CoImpersonateClient, CoInitializeSecurity 및 CoRevertClient는 서버쪽에서 일반적으로 사용되는 익숙한 호출인 반면, CoSetProxyBlanket는 클라이언트쪽에서 일반적으로 사용됩니다.

특성 사용, 역할에 사용자 추가, 프로세스 보안 ID 설정 등과 같은 특정 보안 설정은 메타데이터에 저장되지 않습니다. 그러나, 어셈블리 수준 특성을 사용하여 COM+ 서버 응용 프로그램의 COM+ 탐색기의 보안 탭에 표시되는 내용을 구성할 수 있습니다.

  • 응용 프로그램에 대해 인증 사용
    (ApplicationAccessControlAttribute(bool)). 이 탭에서는 RBS를 실제로 지원해야 합니다.
  • 보안 수준 (ApplicationAccessControlAttribute(AccessChecksLevelOption)). AccessChecksLevelOption.Application으로 설정되면 응용 프로그램에서 역할에 지정된 사용자는 프로세스 보안 설명자에 추가되고 구성 요소, 메서드 및 인터페이스 수준에서의 미세 조정 검사 기능이 해제됩니다. 보안 검사는 응용 프로그램 수준에서만 실행되며 라이브러리 응용 프로그램의 프로세스 수준 보안은 호스트 프로세스에 따라 달라집니다. 특성이 AccessChecksLevelOption.ApplicationComponent로 설정되면 응용 프로그램에서 역할에 지정된 사용자가 프로세스 보안 설명자에 추가되고 응용 프로그램에서 역할 기반 보안 검사가 실행됩니다. 또한, ComponentAccessControl 특성을 클래스에 적용하여 RBS를 필요로 하는 모든 구성 요소에서 액세스 검사를 실행해야 합니다. 라이브러리 응용 프로그램에서 역할 기반 보안 검사는 서버 응용 프로그램처럼 실행됩니다. 보안 속성은 응용 프로그램 내에 있는 모든 개체의 컨텍스트에 포함되며 보안 호출 컨텍스트를 사용할 수 있습니다. 개체에 작성자의 컨텍스트와 호환되지 않는 구성이 있으면 자체 컨텍스트로 활성화됩니다. 프로그래밍 역할 기반 보안은 보안 호출 컨텍스트의 사용 가능성에 따라 달라집니다.

    의미 있는 액세스 검사를 위해 COM+ 라이브러리 응용 프로그램에서 작업하려면 프로세스 및 구성 요소 수준에서 액세스 검사를 실행하도록 선택합니다.

  • 가장인증 선택 항목은 ApplicationAccessControl 특성의 ImpersonationLevel 및 Authentication 속성과 해당합니다.

    SecurityRole 특성은 어셈블리, 클래스, 또는 메서드 수준에 적용될 수 있습니다. 어셈블리 수준에 적용될 경우 해당 역할에 있는 사용자는 응용 프로그램에 있는 모든 구성 요소를 활성화할 수 있습니다. 클래스 수준에 적용될 경우 해당 역할에 있는 사용자는 해당 구성 요소에 있는 모든 메서드를 호출할 수 있습니다. 응용 프로그램 및 클래스 수준 역할은 메타데이터에 구성되거나, COM+ 카탈로그에 액세스하여 관리 방식으로 구성될 수 있습니다.

    메타데이터를 사용하여 어셈블리 수준으로 RBS 구성:

    [assembly: ApplicationAccessControl(true,
    AccessCheckLevel=AccessChecksLevelOption.ApplicationComponent)]
    // 이 역할에 NTAuthority\everyone을 추가합니다.
    [assembly:SecurityRole("TestRole1",true)]
    // 관리를 위해 역할에 사용자를 추가합니다.
    [assembly:SecurityRole("TestRole2")]

    RBS를 메타데이터에 클래스 수준으로 구성:

    [assembly: ApplicationAccessControl(true,
    AccessCheckLevel=AccessChecksLevelOption.ApplicationComponent)]
    ?[ComponentAccessControl()]
    [SecurityRole("TestRole2")]
    public class Foo : ServicedComponent
    {
    public void Method1() {}
    }

    어셈블리 수준 또는 클래스 수준의 RBS는 어셈블리가 등록된 후 COM+ 카탈로그에 존재하므로 관리 방식으로 구성될 수 있습니다. 그러나, 앞에서 설명한 것처럼 클래스 메서드는 COM+ 카탈로그에 표시되지 않습니다. 메서드에서 RBS를 구성하려면 클래스가 인터페이스의 메서드를 구현하고 클래스 수준에서 SecureMethod 특성을 사용하거나, 메서드 수준에서 SecureMethod 또는 SecurityRole 특성을 사용해야 합니다. 또한, 특성이 인터페이스 정의의 인터페이스 메서드가 아니라 클래스 메서드 구현에 나타나야 합니다.

  • 메서드에서 RBS를 사용하는 가장 쉬운 방법은 SecureMethod 특성을 클래스 수준에 적용한 다음 역할을 구성(SecurityRole 특성을 메서드에 배치하거나 관리 방식으로)하는 것입니다.
    [assembly: ApplicationAccessControl(true,
    AccessCheckLevel=AccessChecksLevelOption.ApplicationComponent)]
    Interface IFoo
    {
    void Method1();
    void Method2();
    }
    [ComponentAccessControl()]
    [SecureMethod]
    public class Foo : ServicedComponent, IFoo
    {
    // 관리를 위해 이 메서드에 역할을 추가합니다.
    public void Method1() {}
    // "RoleX"가 이 메서드용 카탈로그에 추가됩니다.
    SecurityRole("RoleX")
    public void Method2() {}
    }

    클래스 수준에서 SecureMethod를 사용하면 클래스의 모든 인터페이스에 있는 모든 메서드가 COM+ 카탈로그의 역할에 관리 방식으로 구성될 수 있습니다. 클래스에서 이름이 같은 두 인터페이스를 구현하고 역할이 관리 방식으로 구성될 경우(클래스가 IFooMethod1과 같은 특정 메서드를 구현하지 않는 한) COM+ 카탈로그에 해당 역할이 표시되는 두 가지 메서드에 역할이 구성되어야 합니다. 그러나, 클래스 메서드에서 SecurityRole 특성이 사용될 경우에는 어셈블리가 등록될 때 이름이 같은 모든 메서드가 해당 역할에 자동으로 구성됩니다.

  • SecureMethod 특성은 메서드 레벨에도 배치될 수 있습니다.
    [assembly: ApplicationAccessControl(true, 
    AccessCheckLevel=AccessChecksLevelOption.ApplicationComponent)]
    Interface IFoo
    {
    void Method1();
    void Method2();
    }
    [ComponentAccessControl()]
    public class Foo : ServicedComponent, IFoo
    {
    // 관리를 위해 이 메서드에 역할을 추가합니다.
    [SecureMethod] // 또는 SecurityRole(SecureMethod++로 변환)을 사용합니다.
    public void Method1() {}
    public void Method2() {}
    }

    예에서 IFoo와 두 메서드는 COM+ 카탈로그에 표시되므로 관리상 두 메서드 모두에서 역할을 구성할 수 있지만, 메서드 수준 RBS는 Method1에만 적용됩니다. 메서드 수준 RBS에 참가해야 하는 모든 메서드에서 SecureMethod 또는 SecurityRole을 사용하거나 앞에서 설명한 것처럼 SecureMethod를 클래스 수준에 배치합니다.

RBS가 메서드 수준에 구성될 때마다 Marshaller 역할이 필요합니다. 메서드가 호출될 때 메서드에 RBS가 없을 경우 서비스되는 구성 요소 인프라가 IRemoteDispatch를 호출합니다. 메서드가 호출되고 RBS가 메서드에 구성되어 있을 경우(SecureMethod 특성이 있을 경우) 해당 메서드와 연결된 인터페이스를 사용하는 DCOM을 사용하여 메서드 호출이 만들어집니다. 따라서, DCOM은 RBS가 메서드 수준에 적용됨을 보장합니다. 그러나, 활성화 및 차단 절에서 설명한 것처럼 COM interop 및 RSCP는 IManagedObject(원격 활성기가 자체 공간으로 참조를 마샬링) 및 IServicedComponentInfo(원격 개체를 쿼리)를 호출합니다. 이들 인터페이스는 서비스되는 구성 요소에 연결됩니다. 구성 요소는 메서드 수준 검사를 실행하도록 구성되기 때문에 인프라가 성공적으로 호출하게 하려면 이들 인터페이스에 역할을 연결해야 합니다.

따라서, 어셈블리가 등록될 때 응용 프로그램에 Marshaller 역할이 추가된 다음 사용자를 이 역할에 추가해야 합니다. 대부분의 경우 모든 응용 프로그램 사용자가 이 역할에 추가됩니다. 이것은 메서드에 RBS를 구성하여 이 추가 구성 단계가 필요하지 않는 관리되지 않는 COM+의 경우와는 다소 차이가 있습니다. 등록하는 동안 이 역할에 '모든 사람'이 자동으로 추가되면 모든 사람이 활성화 권한 없이 구성 요소를 활성화할 수 있기 때문에(호출은 불가능) 보안 구멍이 생길 수 있습니다. 클라이언트가 개체를 삭제할 수 있도록 Marshaller 역할이 IDisposable 인터페이스에 추가됩니다. 사용자는 Marshaller 역할 대신 앞에서 설명한 세 인터페이스 각각에 대한 적절한 역할을 직접 추가하는 것입니다.

원격 구성 요소

ServicedComponent 클래스는 상속 트리에 MarshalByRefObject를 포함하고 있기 때문에 원격 클라이언트에서 액세스될 수 있습니다. 서비스되는 구성 요소를 원격으로 노출하는 방법은 여러 가지가 있습니다. 서비스되는 구성 요소를 원격으로 액세스하려면 다음과 같은 방법을 사용합니다.

  • ASP.NET에 기록되거나 ASP.NET에서 호출된 서비스되는 구성 요소가 있는 HTTP 채널은 높은 확장성 및 성능과 함께 우수한 보안 및 암호 옵션을 제공합니다. SOAP에서 사용될 경우 더 많은 상호 운용성 옵션이 있습니다. 서비스되는 구성 요소는 IIS/ASP.NET에 COM+ 라이브러리 응용 프로그램으로 호스트될 수 있습니다. COM+ 서버 응용 프로그램을 사용할 경우 IIS/ASP.NET 호스트는 DCOM을 사용하여 구성 요소에 액세스할 수 있습니다.
  • 서비스되는 구성 요소를 SOAP 끝점으로 노출하는 대체 방법에 대해서는 COM+ 웹 서비스: XML 웹 서비스의 확인란 사용에 설명되어 있습니다.
  • 서비스되는 구성 요소가 Dllhost에 호스트될 경우의 DCOM. 이 옵션은 최적의 성능과 보안, 시스템 간에 서비스 컨텍스트를 전달하는 기능 등을 제공합니다. 원격 기술을 선택할 때의 주요 디자인 문제는 서비스가 시스템 간에 제공되어야 하는지 여부입니다. 예를 들어, 한 시스템에서 트랜잭션을 만들고 해당 트랜잭션을 다른 시스템에서 지속해야 하는 서버 그룹 내에서 DCOM은 이 목적으로 사용될 수 있는 유일한 프로토콜입니다. 그러나, 클라이언트가 원격 ServicedComponent만 호출하면 될 경우 HTTP 채널 또는 SOAP 종점 접근법을 사용해도 좋습니다.
  • .NET 원격 채널(예: TCP, 사용자 지정 채널). TCP 채널을 사용하려면 소켓에서 프로세스에 대해 수신 대기해야 합니다. 일반적으로 사용자 지정 프로세스는 소켓에서 수신 대기한 다음 서비스되는 구성 요소를 COM+ 라이브러리 또는 서버 응용 프로그램으로 호스트하는 데 사용됩니다. Dllhost를 수신기로 사용할 수도 있습니다. 두 접근법 모두 증명된 성능, 확장성 및 보안을 갖는 사용자 지정 소켓 수신기를 작성해야 합니다. 따라서, ASP.NET 또는 DCOM 솔루션은 대부분의 프로젝트에 가장 알맞은 접근법입니다.

서비스되는 구성 요소를 DCOM을 사용하여 원격으로 액세스하여 Dllhost에 호스트하려면 먼저 어셈블리가 COM+ 서버 응용 프로그램에 등록되고 서버 시스템의 GAC에 배치되는지 확인합니다. 그런 다음 COM+ 응용 프로그램 내보내기 기능을 사용하여 응용 프로그램 프록시에 대한 MSI 파일을 만듭니다. 클라이언트에 응용 프로그램 프록시를 설치합니다. 관리되는 어셈블리가 응용 프로그램 프록시에 포함됩니다. 설치 관리자가 어셈블리를 등록하고 클라이언트 시스템의 GAC에 배치합니다. 따라서:

  • .NET Framework는 클라이언트와 서버에 설치되어야 합니다. 관리되지 않는 클라이언트만 원격 서비스되는 구성 요소에 액세스하는 경우에도 클라이언트 시스템에 설치되어야 합니다. Windows 2000 플랫폼에서는 서비스 팩 3도 필요합니다.
  • 프록시를 제거하면 해당 어셈블리도 GAC에서 제거되어야 합니다.

그림 6에서는 서버 구성 요소가 클라이언트쪽에서 관리되는 코드로 활성화된 후의 인프라를 보여 줍니다.

DCOM을 사용하는 것은 CLR이 Dllhost에 호스트된다는 의미를 함축하고 있으며, 응용 프로그램 구성 파일인 dllhost.exe.config가 system32 디렉터리에 있다는 의미입니다. 또한 구성 파일이 시스템에 있는 모든 Dllhost 프로세스에 적용됨을 의미합니다. 다음에 릴리스되는 .NET Framework(V1.1)에서는 COM+ 응용 프로그램 루트 디렉터리를 COM+ 응용 프로그램에 설정하여 해당 응용 프로그램의 구성 파일과 어셈블리를 찾는 데 사용할 수 있습니다.

클라이언트 활성 개체의 경우 개체의 URI가 요청될 때마다 해당 개체에 대한 수명 임대가 만들어집니다. 활성화 절에서 이미 설명한 것처럼 URI는 원격 서비스되는 구성 요소 프록시에 의해 요청됩니다. 이것은 기존의 in-process 서비스되는 구성 요소가 원격 프로세스에 마샬링되는 경우에도 발생할 수 있습니다. URI는 응용 프로그램 도메인 외부에서 .NET가 MBR 개체를 마샬링할 때마다 요청됩니다. URI는 .NET에 있는 개체 ID가 고유함을 보장하고 프록시 체인을 금지하는 데 사용됩니다. 따라서, 관리되는 클라이언트가 원격 서비스되는 구성 요소를 활성화하면 서버 개체에 임대 시간이 사용됩니다. 관리되지 않는 클라이언트는 클라이언트쪽에 원격 서비스되는 구성 요소 프록시가 없기 때문에 개체의 URI를 요청하지 않는다는 사실에 주의하십시오. 대신에 관리되지 않는 클라이언트는 DCOM을 사용하여 개체 ID를 확인합니다. 따라서, 관리되지 않는 클라이언트에서 활성화될 경우에는 서비스되는 구성 요소의 임대 시간이 사용되지 않습니다.

임대 시간이 서비스되는 구성 요소에 포함될 경우 InitialLeaseTime 및 RenewOnCallTime 시간 초과 값을 작은 값(예: 10초)으로 설정하는 것이 좋습니다. 서비스되는 구성 요소를 삭제하려면 Dispose()를 사용하거나 GC에서 개체를 정리합니다. Dispose()가 호출되면 원격 서비스되는 구성 요소 프록시는 DCOM 프록시에 있는 참조를 해제한 후 다음 GC에서 사용 가능하게 만듭니다. 서버 개체는 Dispose 호출을 처리하고 (또는 원격 호출을 Dispose()에 서비스하는 새로운 서버 개체를 만듬), 관련된 COM+ 컨텍스트를 삭제한 다음 임대 시간이 초과된 경우에만 다음 GC에서 사용 가능하게 만듭니다. 클라이언트가 Dispose()를 호출하지 않을 경우 서버는 클라이언트쪽 GC가 DCOM 프록시에 대한 참조를 해제하는 동안 대기한 다음 임대 시간이 만료된 후 다음 GC에서 해당 서버와 COM+ 컨텍스트를 사용할 수 있게 만듭니다. 따라서, Dispose()를 호출하고 기본 임대 시간을 축소합니다. 클라이언트가 활성 상태인 경우에는 임대 시간이 만료되더라도 해당 서버 개체에 대한 DCOM 참조는 서버 개체를 활성 상태로 유지합니다. 그러나, DCOM 참조를 사용해도 서비스되는 구성 요소가 항상 활성 상태를 유지하는 것은 아닙니다. 클라이언트가 CLR 원격 채널 또는 COM+ SOAP 서비스를 통해 개체에 액세스할 경우 임대가 만료되는 강력한 참조만 서비스되는 구성 요소를 활성 상태로 유지합니다.

결론

이 기사에서는 관리되는 코드에 사용할 수 있는 서비스 중 일부에 대해서만 설명했습니다. 관리되는 코드에서는 트랜잭션 격리 수준, 프로세스 초기화, 구성 요소 없는 서비스, 프로세스 재활용 등과 같은 모든 COM+ 서비스를 사용할 수 있습니다. 이제 .NET Framework에서도 모든 COM+ 서비스를 일관성 있고 논리적인 방법으로 액세스할 수 있습니다. 게다가, ASP.NET, Microsoft ADO.NET 및 Messaging과 같은 .NET Framework의 많은 혁신적인 부분들이 .NET Enterprise Services에 통합되어 트랜잭션 및 개체 풀링과 같은 서비스를 사용할 수 있게 되었습니다. 이 통합은 일관성 있는 아키텍처 및 프로그래밍 모델을 위해 제공됩니다. System.EnterpriseServices 네임스페이스는 관리되는 클래스에 서비스를 추가할 수 있는 프로그래밍 모델을 제공됩니다.



신고
Trackback 7 Comment 0
2008.08.27 17:40

웹서비스 .net compact framework 2,0 사용시 웹참조 동적으로 하기

- .net cf2.0에서 웹 참조를 동적으로 하기 위해서  reference.cs 파일에 웹참조 클래스에서 아래 그림과 같이 생성자를 하나 더 만들어 url을 동적으로 생성해 주었다.

사용자 삽입 이미지




















위 그림의 빨간색 박스가 추가한 생성자 이다.

url은 config 파일을 만들어 동적으로 수정할 수 있도록 하였으면 위 생성자를 호출하는 부분은 아래 그림과 같다.


사용자 삽입 이미지






















wsFile 은 웹참조 주소가 저장되어 있는 config 파일이며, setWsAddr()은 이 주소를 동적으로 셋팅해 주는 부분이다.

출처 : http://thinkinginhands.tistory.com/14
신고
Trackback 0 Comment 0
2008.08.20 15:56

닷넷 가비지 컬렉터의 동작원리



흔히 닷넷기반의 환경을 관리되는(Managed) 환경으로 부르곤 한다. 그렇다면 도대체 무엇이 관리가 된다는 것인가? 그 관리의 주체는 바로 메모리이 다. 이전 Native 시대에는 메모리를 할당하고 해제하는 부분을 개발자가 직접 처리하였지만 닷넷은 그 부분을 자동으로 관리해주는 것이다. 닷넷이 관리되는 환경의 수행이 가능할 수 있는 것은 바로 “가비지 컬렉터(Garbage Collector)”가 닷넷에 존재하기 때문이다. 관리환경의 장점을 최대한 활용하고 사용하기 위해서는 가비지 컬렉터에 대해서 잘 알아 두어야 하고 동작원리를 파악하고 있어야 한다. 필자는 가비지 컬렉터의 원리에 대한 내용을 2002년도 “Chappell의 .NET 여행”이라는 책에서 처음 접했었고, 이 내용은 닷넷의 메모리 관리를 이해하는데 어느 정도의 기반지식이 될 수 있었다.

1. 가비지 컬렉터와 가비지 컬렉션

가비지 컬렉터는 앞에서 설명한 것과 같이 메모리를 관리해주는 메카니즘이다. 많이 혼동하는 부분이 바로 가비지 컬렉터와 가비지 컬렉션일 수 있다. 가비티 컬렉터는 Mark&Compact 알고리즘을 이용하여 객체들의 관계를 추적한다. 즉, 인스턴스화 시켰던 DataSet에 null을 할당하면 DataSet은 사용하지 않는 객체로 간주되고 가비지 컬렉션시에 메모리 해제의 대상이 된다. 하지만 DataSet은 DataTable을 가지고 있고, DataRow, DataItem과 같은 여러 객체들을 참조하고 있다. 바로 DataSet이 해제가 되면 그와 상호관련이 있었던 모든 객체들 역시 메모리를 해제해야 할 것이고 바로 가비지 컬렉터는 이러한 복잡한 관계를 Mark&Compact 알고리즘을 이용해서 이해하고 각각 해제될 수 있는 것이다.

가비지 컬렉션은 바로 메모리를 해제하고 새롭게 재배치 하는 작업을 칭하고 자동으로 수행되지만 수동으로도 수행을 명령할 수도 있다. 가비지 컬렉션이 수행되는 시기는 개발자가 알 수 없지만 분명한 것은 메모리가 부족하면 분명 가비지 컬렉션이 일어난다는 것이다.

System.GC.Collect();

가비지 컬렉터는 세대별로 나누어서 메모리를 관리한다. 즉, 메모리를 관리하는 그릇이 3개가 존재한다고 보면 된다. 세대는 0,1,2 세대로 나누어지고 최초의 메모리는 무조건 0이라는 공간에서 관리가 된다고 보면 된다. 그리고 가비지를 한번 정리하였지만 해제되지 않은 객체는 바로 다음 세대로 이동하게 된다. 가비지 컬렉션은 세대별로 독립적이라고 보면 된다. 즉, 가비지 컬렉션이 일어날 때 0,1,2 세대 별로 동시에 발생하는 것이 아니라 개별적으로 가비지 컬렉션을 수행한다는 것이다. 최근의 생긴 메모리 수록 즉, 0세대일 수록 컬렉션이 많이 발생한다. 이것은 당연한 동작 원리이다. 보통 한번 사용한 객체는 꾸준히 많이 사용하지만 그렇지 않은 객체는 단기적으로 사용을 많이 하기 때문이다. 다음 [그림1]은 가비지 컬렉터가 가비지 컬렉션을 수행하는 장면을 보여주고 있다.

그림1
[그림1]가비지 컬렉션의 동작

이렇게 수행을 하고 남은 객체는 바로 그 위의 세대로 승격된다. 다음 [그림2]를 살펴보자.


그림2

[그림2]다음 세대로 승격


이 승격은 2세대까지 2번 승격된다. 0세대는 활발하게 가비지 컬렉션이 수행되지만 2세대는 거의 발생되지 않는다고 보면 된다.


2. 메모리의 효율적인 사용

앞에서 살펴본 가비지 컬렉터는 관리되는 환경의 모든 메모리를 책임지고 관리한다. 이전 Native 시대에서는 메모리 누수와 같은 문제가 발생하면 개발자의 책임이었지만 닷넷에서는 그 책임을 대신 주어준다는 것이다. 하지만 관리되어지는 환경이라고 해서 모든 것을 전적으로 가비지 컬렉터에 의존해서는 안 된다. 물론 가비지 컬렉터가 하드웨어가 무척 발전한 지금의 환경에 부응하고 있는 기능임은 틀림없다. 하지만 필자가 말하고자 하는 것은 가비지 컬렉션이 언제 어떤 객체를 수집하고 어떻게 동작하는 것임을 알아두고 그에 맞추어 코드를 작성해야 한다는 것이다. 이런 효츌적인 메모리 관리를 위해서 몇 가지 지침들을 적어보도록 하겠다.

- 코드에서 System.GC.Collect()를 이용해서 직접 가비지 컬렉션의 수행을 호출하는 것은 가급적 피한다.

닷넷에 연고가 있는 독자라면 가비지 컬렉션을 수동으로 수행하면 안좋다 라는 말을 많이 들어봤을 것이다. 그럼 이제 그 이유를 살펴보자. 이유는 크게 두 가지 이유가 있다. 첫 번째는 가비지 컬렉션이 수행하는 메카니즘이 생각보다 간단하지 않기 때문이므로 성능상 부하가 있을 수 있다. 왜냐하면 현재 객체가 사용 중인지 확인하는 작업이 필요하며 그 작업이 끝난 후에 객체를 파괴한다. 객체를 파괴할 때에는 참조되고 있는 객체 역시 파괴해야 한다는 것이다. 뿐만 아니라 파괴된 객체들의 빈자리를 매꾸기 위해서 가비지 컬렉터는 객체들을 재배치 작업(Compaction 작업)을 수행하게 되므로 메모리가 많을수록 그 부하가 클 수 있기 때문이다. 두 번째 이유는 객체가 승격되기 때문이다. 객체가 0세대에서 한번 승격되면 그 객체가 더 이상 사용하지 않는다 하더라도 자동적으로 그 객체가 정리될 확률이 줄어들기 때문이다. 그렇기 때문에 코드에서 직접 가비지 컬렉션의 수행의 호출을 추천하지 않는 것이다.

- 사용하지 않는 객체는 가비지 컬렉터가 수집할 수 있는 대상으로 설정한다.

그렇다면 가비지 컬렉터가 수집하는 대상은 어떤 대상인가? 좀 더 쉽게 이해하기 위해서 다음과 같은 클래스를 만들었다고 가정하자.
Class A
{
  string a="HOONS"
}
Main메서드()
{
  A aClass= new A();//클래스 생성
}


그렇다면 여기서 aClass객체를 사용하고 있는지 어떻게 계산할 것인가이다. 가장 쉬운 방법은 aClass에 null을 대입해 주면 되기도 하지만 가장 바람직한 방법은 using을 이용해서 객체를 사용하는 범위를 지정해주는 것이다. using은 IDisposable 인터페이스를 상속받아서 Dispose() 메서드를 구현해야 한다. 그리고 Dispose()에서는 클래스 안에서 사용했던 자원을 해제하는 것이다. finalizer(~생성자())를 구현한다면 가비지 컬렉터는 자원을 해제하기 바로 직전에 수행하게 될 것이다. 이 finalizer에서는 만약 Dispose()를 호출하지 않을 것을 대비해서 Dispose()를 수행하는 용도나 아니면 관리되지 않은 영역 즉, unmanaged 자원이 있다면 이 자원을 호출하면 된다.


- 전역변수는 가급적 초기화하지 않는다.

다음과 같은 코드가 있다고 가정하자.
Class A
{
  ArrayList a=new ArrayList();
  public A(int Length)
  {
      a=new ArrayList(Length);
  }
}



이 코드는 내부적으로 다음과 같이 동작된다.
Class A
{
  ArrayList a=new ArrayList();
  public A(int Length)
  {
      a=new ArrayList();
      a=new ArrayList(Length);
  }
}



그렇기 때문에 필요없는 가비지가 생기게 되는 것이다.

- 자주 사용하는 객체는 전역변수로 잡아서 가비지를 최소화한다.

자주 사용하는 객체는 여러 개를 만들어 사용할 없다는 것이다. 이 부분은 이론상 쉽게 이해할 수 있으므로 깊게 언급하지 않겠다.



3. CLR 프로필러

그렇다면 가비지 컬렉터에서 내부적으로 관리되고 있는 메모리를 어떻게 들여다 볼 수 있는 것인가? 이것은 Microsoft에서 제공하고 있는 CLR 프로필러(Profiler)라는 툴을 이용하면 된다. 이툴은 MS 다운로드 사이트에서 다운 받을 수 있으며 다음 [화면1]은 CLR 프로필러를 실행한 화면을 보여주고 있다.


화면1
[화면1] CLR 프로필러

이 프로필러는 각 세대별로 가비지 컬렉션이 일어난 횟수를 보여주고 있고 현재 각 세대별로 차지하고 있는 힙의 크기 그리고 할당되고 재할당 된 메모리 바이트를 보여주고 있다. 뿐만 아니라 참고 그래프를 제공하고 있기 때문에 메모리 사용을 분석하는데 큰 도움을 줄 수 있다. 이 툴의 자세한 내용은 이전 HOONS 닷넷에서 진행한 세미나에서도 언급한 적도 있고, 이전 마소에서도 다루었던 적이 있으므로 자세한 사용방법은 지면상 생략하도록 한다.

출처 : http://blog.hoons.kr/blog_post_7.aspx
신고
Trackback 0 Comment 0
2008.04.22 16:48

ActiveX 없이 간단한 AES 암호화 시스템 적용하기

개인적인 호기심에 Java Script 기반의 암호화 알고리즘 구현이 있을까 검색해보게 되었다. 혹시가 역시였는데 매우 다양한 버전의 Java Script 기반의 암호화 알고리즘을 찾을 수 있었다. 클라이언트에서 데이터를 암호화한 후 이것을 서버로 전송하고 이것을 서버에서 같은 비밀 키를 사용하여 해독한다는 개념은 이제는 너무 일반적인 것이라 굳이 언급하지 않아도 될 정도다. 하지만 놀랍게도 이런 구현 절차를 ActiveX에 너무 많이 기대고 있다는 것 또한 사실이다.

ActiveX 없이 다중 브라우저를 지원하면서도 일정 수준 이상의 보안을 충족하는 암호화 시스템을 어쩌면 간단히 구현해볼 수도 있지 않을까라는 호기심이 생겨서 몇 번 코드를 끄적여보았을 뿐인데 개인적으로나 일반적인 커뮤니티에 적용하기에는 딱 좋은 수준의 암호화 시스템이 나와서 글을 써본다. Security Critical한 서비스에서는 HTTPS, SSL, IP/sec 등을 사용하는 것이 100배 안전하지만 이것은 어디까지나 일상적인 암호화만을 다루는 것이다.

기본적인 원리는 간단하다. 같은 Java Script Source Code를 가지고 하나는 Internet Browser용 Java Script Code로 사용하고 또 하나는 JScript .NET 컴파일러용으로 패키징하여 ASP.NET에 통합하는 것이다. 이 글에서는 AES (Advanced Encryption Standard) 알고리즘을 채택하였다.

Java Script AES Implementation: http://www.movable-type.co.uk/scripts/aes.html

위의 사이트에서 핵심적인 AES 구현을 먼저 가져왔다. AES 알고리즘과 비밀 키를 통하여 평문을 암호문으로, 암호문을 평문으로 바꾸는 것이 정확하게 작동한다는 것을 테스트하였다. 하지만 암호문을 평문으로 바꾸는 과정에 있어서 암호문이 바이트의 배열로 출력된다는 점이 걸린다. 인코딩에 따라서는 완전히 다른 내용으로 읽혀져 손상될 가능성이 있기 때문이다. 그래서 BASE64 알고리즘을 다시 찾게 되었다.

Java Script BASE64 Implementation: http://rumkin.com/tools/compression/base64.php

AES 암호문을 BASE64로 보호하고, BASE64 디코딩으로 얻어낸 AES 암호문을 평문으로 바꾸는 것 또한 정확히 동작함을 확인하였다. 그리고 이번엔 정말 실용성이 있는지 확인해보고 싶어서 2바이트 문자열을 가지고 암호화 복호화를 시험해보았다. 하지만 이번엔 정확히 복원해내지 못한다는 것을 확인할 수 있었다. 다행히 2바이트 문자열에 대해서는 자바스크립트 내장 함수인 encodeURI와 decodeURI를 통하여 처리할 수 있었다. 평문에 관한 문제는 해결이 되었지만 비밀 키의 경우에는 이 방법도 의미가 없었다. 비밀 키는 1바이트 문자열 수준에서 해결해야 하는듯 보였다.

이러쿵 저러쿵하여 만든 JavaScript 코드와 바이너리를 ZIP 파일로 묶어서 올려본다. 여러 메서드가 있지만 이 글의 내용을 반영하여 만든 실질적인 메서드는 다음과 같다.

// JavaScript
AesEncrypt128(plainText, password);
AesEncrypt192(plainText, password);
AesEncrypt256(plainText, password):
AesDecrypt128(cipherText, password);
AesDecrypt192(cipherText, password);
AesDecrypt256(cipherText, password):

// C# (Need JScript.NET Runtime Assembly)
SecurityManaged.ComplexAes aesModule = new SecurityManaged.ComplexAes();
aesModule.AesEncrypt128(plainText, password);
aesModule.AesEncrypt192(plainText, password);
aesModule.AesEncrypt256(plainText, password);
aesModule.AesDecrypt128(cipherText, password);
aesModule.AesDecrypt192(cipherText, password);
aesModule.AesDecrypt256(cipherText, password);

클라이언트 측에서는 AesEncrypt로 보호할 내용을 암호화한 후 서버에 전송하고, ASP.NET - 또는 - 이를 받아들일 소켓 서버 등에서는 SecurityManaged.dll 파일을 레퍼런스로 참조하고 SecurityManaged.ComplexAes 클래스의 인스턴스를 만들어 위의 AesDecrypt로 내용을 복구하면 되겠다. 참고로 128/192/256은 키의 길이에 해당된다.

첨부된 파일 안의 .Managed.js 파일은 JScript.NET 문법에 맞게 재배치한 것으로 DLL로 컴파일하는 것을 가능하게 해준다. 안의 내용이나 코드 문법은 오리지널 Java Script과 전혀 차이가 없지만 닷넷 어셈블리로 만들기 위해서 package 구문과 class 구문을 추가하였을 뿐이다. 또한, 가능성만을 타진해보기 위함이었으므로 JScript.NET에서만 가능한 명시적인 형식 지정 또한 누락되어있다. 만약 좀 더 최적화를 원한다면 코드를 수정해서 사용할 것을 권하며 필요한 경우 어트리뷰트 지정을 통하여 지연된 서명을 추가해볼수도 있겠다.

닷넷 프레임워크 2.0을 설치하였다면 명령 프롬프트에서 다음과 같이 입력하면 원하는 어셈블리를 직접 얻을 수 있다. (이 작업을 위해서 Visual Studio 2005나 2008은 필요하지 않다.)

%windir%\microsoft.net\framework\v2.0.50727\jsc.exe /target:library AesImpl.managed.js

이제 우리들만의 일상적이고도 간단한 AES 암호화 알고리즘 시스템을 얻을 수 있게 되었다. 그 다음 활용 방안은 다시 고민해봐야 하겠다. 자바스크립트 기반에 닷넷까지 지원되니 ASP.NET AJAX와 Silverlight에도 잘 어울릴듯 하다.


출처 : http://rkttu.com/rkttublog/201?TSSESSIONrkttucom=5ba7c7e835b7d36631901491790d4f1e

신고
Trackback 0 Comment 0
2008.02.27 09:05

입문자에게... "프로그램 공부 어떻게 해야 하나요?"

몇몇 분들이 이제 .NET 을 시작하고 공부를 하면서 저에게 이런 문의를 하신 분들이 계셨습니다.
 
닷넷 공부를 어떻게 하세요?”
 
사실 공부하는 방법은 학창시절에서부터 사람마다 너무 다양하기 때문에, 마냥 “열심히 하세요”, “외우세요”, “직접 해보세요” 등등 의외로 성의 없는 답변이 될 수도 있을 것 같아요. 그도 그럴 것이, 달달 외워서 잘 하는 사람이 있기도 하는 반면, 매일 골목 뒷 편에서 같이 놀던 친구들도 시험 때면 성적이 상당히 좋은 사람도 있습니다(이런 친구는 수업 때만 잘 들으면 된다 라고 하더군요^^;).
 
부끄럽지만, 저는 사실 학창시설에 공부를 못했습니다. 공부를 어떻게 해야 하는지도 몰랐고, 더욱 중요한건 관심(?)이 없었습니다. 학업을 열심히 하는 것이 학생의 신분이라면, 전 좋은 학생은 아니였나 보네요. 그렇다고 너무 사춘기의 방황이 심해서 관심분야인 컴퓨터 공부도 내팽겨 쳐 버렸으니까요.
 
궁금해 하시는 분들도 계셔서 제가 지금까지 봤던 책을 나열해 봅니다. ( 프로그램과 관련 없는 책은 제외입니다 무작위 순서 )
 
l Taeyo’s ASP.NET V1.0 ( 영진출판사, 김태영 저 )
l ASP.NET 으로 구현하는 블로그 프로그래밍 ( 가남사, 장현희 저 )
l ASP.NET Website Programming ( 정보문화사, 김태역 역 )
l C# 을 이용한 ASP.NET 웹 프로그래밍 ( 크라운출판사, 황인균 저 )
l Effective C# ( 한빛미디어, 김영신 역 )
l 마이크로소프트 ASP.NET AJAX ( ITC, 이광수 역 )
l Visual C# .NET 2005 2nd Edition 실전 프로젝트 ( 영진출판사 최재규 저 )
l C# Programming Bible with .NET Framework 3.0 ( 영진출판사, 최재규 저 )
l XML For .NET ( 정보문화사, 김세현 역 )
l C# 디자인 패턴 ( 정보문화사, 전병선 역 )
l C# Web Sevices ( .NET 리모팅과 ASP.NET 으로 웹 서비스 구현하기 ) ( 정보문화사, 문건웅 역 )
l ASP.NET Distributed Data Application ( ASP.NET 분산 데이터 애플리케이션 ) ( 정보문화사, 강승규 역 )
l Professional ASP.NET Web Services ( 정보문화사, 송영덕 역 )
l 훈스닷넷과 함께하는 .NET Framework 3.0 ( 영진닷컴, 박경훈/서동진 저 )
l PROGRAMMING MICROSOFT WINDOWS WITH C# ( 정보문화사, 김태현/박한돌 역 )
l 찰스페졸드의 WPF ( 에이콘, 최세영/황상철/김인기/신희철 역 )
l Programming WCF Services(한글판) ( ITC, 박경훈 역 )
 
그리 많은 책은 아니지만, 제 경력에 일반적인 책 읽기의 권장량 정도를 본 것 같네요. E-Book(인터넷 PDF 도서)를 포함한다면 더 많은 양이 될 수 도 있습니다. 그렇다고, 이 책의 내용을 모두 독파하고 빠삭하게 마스터 한 것은 아닙니다. 언제나 옆에 끼고, 참고서 처럼 심심할 때 끄집어보고, 생각날 때 보고 하는 책들이 대부분 입니다.
 
1. 입문서는 반드시 마스터 하라.
 
그렇다면, 이 모든 책을 모두 이해하고 정독했느냐? 그렇지 않습니다. 이중에서 입문서 3권만을 6개월 이상 옆에 끼고 살았습니다. 워낙 닷넷에 대한 기초가 없었기 때문에, ASP.NET 과 OOP(객체지향)를 중심으로 공부를 했습니다. BASIC 이나 C 언어에 대해선 어느 정도 수준(?)이 되었으나, 처음 접해보는 OOP 는 내 머리 속을 가장 많이 혼란스럽게 하였고, 더 이상 다른 책들을 보지 못하고 덮어버리게 만든 장본이었답니다.
 
베스트셀러이자 최고의 입문서인 김태영님의 책을 본 후, 스스로 초급 탈출을 외치며 또 하나의 베스트셀러인 장현희님 책을 보았을 때, 너무나도 큰 좌절이었습니다. 장현희님의 책 중반부터 도저히 보고 또 봐도, 이해할 수가 없었기 때문입니다. 바로 이해할 수 없던 이유는, OOP 에 대한 텅 빈 지식 때문이었죠.
 
 
2. 입문서는 눈높이에 맞는 책을 직접 서점에서 골라라.
 
처음으로 김태영님의 책을 인터넷으로 주문하고, 한달 동안 집에 처박혀 공부했습니다. 그리고 두 권의 책을 인터넷으로 주문을 하게 되었죠. 하지만, 이 책 두 권은 OOP 의 기초를 모르고선, 도저히 책의 마지막 장을 찍을 수 가 없었습니다. 바로, 책 한 권으로 모든 것을 마스터한양 자신의 실력을 망각했던 것입니다.
 
당장 서점으로 달려가서 직접 읽어보고 자신에게 맞는 책을 고르세요. 똑같은 수학문제를 놓고, 푸는 방법을 10분 동안 알려준 후, 숫자만 다른 문제를 제시했을 때, 풀지 못하는 사람도 분명 있을 거라고 생각합니다. 또한 응용 문제를 제시했을 때, 손도 대지 못하는 사람도 분명 있을 거라고 생각합니다. 이것은 문제의 답이 중요한 것이 아니라, 문제를 푸는 방법을 배운 10동안 자신에게 맞는 수준으로 배우지 못했기 때문입니다.
 
똑같은 입문서 일지라도, 자신에겐 어렵거나 너무 쉽거나 할 수 있습니다. 어떤 사람은, 책의 글자 크기나, 폰트, 또는 종이 재질, 두께가 마음에 안들 수 도 있습니다. 반드시 입문서는 직접 서점에서 보고, 꾸준히 볼 수 있을 지를 확인사살 후에 구입해도 늦지 않습니다.
 
 
3. 인터넷 검색을 최대한 활용해라.
 
인터넷은 분야를 가리지 않고 많은 정보로 가득차 있습니다. 이 인터넷을 활용하는 두 명의 개발자를 예를 들어 보겠습니다.
 
첫 번째 개발자 - 개발자로서 인터넷을 잘 활용하지 못하는 사람은, 검색을 통해 원하는 답을 찾지 못합니다. 때문에, 옆 사람에게 물어보거나, 커뮤니티 게시판으로 바로 달려가서 질문하는 유형이 되겠습니다.
 
두 번째 개발자 - 인터넷을 잘 활용하는 개발자는 다양한 검색엔진을 통해 자신이 궁금한 질문의 요점을 몇 가지 단어로 추스려내어 검색결과를 통해 문제를 해결합니다.
 
위 두 명의 개발자 중 누가 문제를 빨리 해결할까요? 바로 첫 번째 개발자일 가능성이 큽니다. 단, 옆 사람 또는 자신이 속한 집단의 사람이 그 문제의 해결방법을 알고 있거나, 자신보다 뛰어난 기술을 보유한 사람이어야 할 가능성이 큽니다. 더욱 위험한 것은, 소위 고만고만한 사람들이라면 오히려 잘못된 지식을 배우게 될 가능성이 더 클 수 밖에 없습니다.
 
그럼 인터넷 검색을 어떻게 해야 멋진 해결방법을 얻을 수 있을까요? 만약 “비쥬얼 스튜디오 2008이 제대로 설치가 되지 않습니다” 라는 문제를 얻었을 때, 다양한 단어를 조합하여 검색할 수 있습니다.
 
“visual studio 2008 install error”
“visual studio setup 2008 error”
“vs 2008 install error”
“vs 2008 setup error”
 
좀더 분명한 오류를 유추할 수 있다면, 연상되는(또는 명확한) 단어를 끼워맞춰서 검색하시면 됩니다. 이런 유형의 개발자는 문제 해결의 과정이 오히려 굉장히 느릴 수 도 있습니다. 하지만, 검색을 통해 직접 겪어보지 못한 다양한 문제유형을 간접경험을 할 수 있게 되어, 비슷한 다른 유형의 오류를 보았을 때, 오히려 첫 번째 개발자 보다 빠른 문제해결 능력을 갖출 수 있게 됩니다.
 
참고로 저는 자는 시간 빼고 3일 동안 검색해서 답을 얻은 기억이 있답니다… ㅠ_ㅠ
 
 
4. 관심분야의 RSS FEED 에 가입하라.
 
이쯤 되면, 자신이 어떤 분야에 관심이 있는지, 무엇을 해야 하는지 약간은 명확해 지셨는지요. 인터넷 검색을 통해 원하는 답을 얻는 과정이 반복 되면서 내용이 상당히 유용한 블로그를 자주 접할 수 있을 것입니다. 그러한 블로그는 반드시 즐겨찾기 또는 RSS FEED 를 구독하시기 바랍니다.
 

[그림1] HanRSS 를 카테고리별로 분류하여 사용중인 필자
 
즐겨찾기는 해당 사이트를 방문하기 전에 사이트의 컨텐츠를 확인 할 수 없기 때문에, RSS FEED 를 구독하는 것을 권장합니다. 이렇게 만들어진, RSS FEED 목록은 해당 사이트에 새로운 컨텐츠가 게시되면 자신에게 사이트의 컨텐츠를 통보해 주기 때문에 최신의 내용을 앉은 자리에서 받아 볼 수 있습니다.
 
참고로 저는, 연모를 쓰다가 불편한 감이 있어 HanRss 로 바꾸었답니다.
 
5. 커뮤니티 활동을 하라.
 
이것은 옵션입니다. 이것은 해도 그만, 안해도 그만입니다. 커뮤니티 활동에 적극 참여해도 그만, 눈팅만 해도 그만입니다. 절대 커뮤니티에 목매이지 않아도 됩니다. 오히려 커뮤니티 활동에 너무 적극적이다 보면, 회사 업무에 지장을 초래할 수도 있게 됩니다. 하지만, 커뮤니티 활동을 통해 어떤 이로운 옵션이 있는지 보겠습니다.
 
첫 번째, 커뮤니티 활동의 질문답변을 통해 개발자들이 어떤 문제를 겪는지 알 수 있습니다. 특히나, 취업을 앞둔 초보 개발자들이라면 눈여겨 볼 만 합니다. 저 또한 그러했듯이, 실무 개발자들이 부딪히는 문제를 간접 경험할 수 있고, 그것을 해결해 주는 답변 글을 통해 미리 오류 유형을 연습함으로써 미래의(취업후에) 삽질의 시간을 줄여 줄 수 있습니다.
 
두 번째, 온라인, 오프라인을 통해 인맥을 쌓을 수 있습니다. 온라인 게임을 보면, 오프라인이 현실세계라면, 온라인은 가상의 세계라고 합니다. 온라인 게임상 같은 마음으로 뭉친 자신의 집단이 오프라인 모임으로 종종 이어지는 경우가 많습니다. 개발자의 세계도 마찬가지입니다. 여러 사람을 만날 수 있는 기회가 많아질 수 있습니다. 인맥이 왜 중요한지는 더 말 안해도 아실 듯 해서 생략합니다.
 
세 번째, 자신을 PR 할 수 있습니다. 적어도 이 글을 적고 있는 저는, 이왕 이 바닥(?)에 뛰어든 이상 유명해 지고 싶습니다. 평범한 인생은 적어도 저에겐 의미가 없기 때문입니다. 자신이 평범한 개발자로 남길 원하지 않는다면, 반드시 커뮤니티나 블로그를 개설하여 활동하세요. 아마 지금 말한 자기 PR 이 위에 열거한 1~5 번 중에 가장 어렵고 힘들거라 생각합니다. 왜냐하면, 꾸준해야 하기 때문입니다.
 
 
그렇다면 나는 어떻게?
 
위의 5가지 방법은 저의 주관적인 방법입니다. 위의 방법이 도움이 될 사람도 있겠지만, 전혀 그렇지 못한 사람도 있을 겁니다. 자신의 스타일은 자신이 만들어 가는 것이 가장 좋은 공부 방법입니다. 억지로 하는 스터디는 차라리 안하는 것보다 못합니다.
 
무었을 해야할 때, 마음의 준비와 자세가 되어있고, 그것이 잘 되지 않을 때는, 왜 잘 안되는지.. 주변의 어떤 방해요소가 있는 것은 아닌지 살펴보시기 바랍니다. 참고로 어떤 유명하신 분은, 집에 가면 뻘짓을 하게 되어, 회사에 남아서 하고 싶은 것을 하고 막차를 타고 들어간다고 합니다.
 
읽어주시느라 고생 많으셨고요. 새해에 모두 어떤 계획이 있는지는 모르겠지만, 하시는 일 모두 잘 되길 바라면서 이만 줄입니다.

출처 : http://blog.powerumc.kr/article/2008/01/27/programming-study-ways.aspx
신고
Trackback 0 Comment 0


티스토리 툴바