다음을 통해 공유


InstanceContextSharing

이 샘플에서는 IInstanceContextProvider 인터페이스를 사용하여 여러 호출과 클라이언트 간에 InstanceContext 개체를 공유하는 방법을 보여 줍니다. 또한 클라이언트 응용 프로그램에서 고유 식별자를 만들어 서비스로 보내는 방법을 보여 줍니다. 그러면 서비스는 이 식별자를 특정 InstanceContext 개체와 연결합니다. 그런 다음 클라이언트는 식별자를 다른 클라이언트에 전달할 수 있으며, 이 클라이언트는 동일한 서비스로 보내는 헤더에 컨텍스트 식별자를 넣을 수 있습니다. 이 서비스는 컨텍스트 식별자를 사용하여 두 번째 호출을 첫 번째 인스턴스 컨텍스트 개체 및 서비스 개체와 연결합니다.

참고

이 샘플의 설치 절차 및 빌드 지침은 이 항목의 끝부분에 나와 있습니다.

시스템에 적절한 InstanceContext 개체를 제공하려면 IInstanceContextProvider 인터페이스를 구현합니다. 일반적으로 이 인스턴스는 공유 세션을 지원하거나, 서비스 인스턴스 풀링을 사용하거나, 서비스 인스턴스의 수명을 제어하거나, 클라이언트 간에 컨텍스트를 그룹화하기 위해 구현합니다.

사용자 지정 IInstanceContextProvider를 삽입하려면 IEndpointBehavior 또는 IServiceBehavior와 같은 동작을 만들고 이 동작을 사용하여 IInstanceContextProvider 개체를 System.ServiceModel.Dispatcher.DispatchRuntime.InstanceContextProvider 속성에 할당합니다.

이 샘플에서는 사용자 지정 ShareableAttribute 특성에 IServiceBehavior를 사용하여 IInstanceContextProvider를 삽입합니다.

ShareableAttributeCalculatorExtension 개체를 인스턴스화하여 IInstanceContextProvider를 구현하고, 각 EndpointDispatcher를 반복하고, 각 InstanceContextProvider 속성을 방금 만든 CalculatorExtension 개체로 설정합니다. 이 과정은 다음 샘플 코드에 나와 있습니다.

//Apply the custom IInstanceContextProvider to the EndpointDispatcher.DispatchRuntime
public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
    CalculatorExtension extension = new CalculatorExtension();
    foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
    {
        ChannelDispatcher dispatcher = dispatcherBase as ChannelDispatcher;
        foreach (EndpointDispatcher endpointDispatcher in dispatcher.Endpoints)
        {
            endpointDispatcher.DispatchRuntime.InstanceContextProvider = extension;
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(extension);
        }
    }
}

클라이언트가 보내는 각 호출은 CalculatorExtension 개체를 거쳐 특정 메시지를 처리하는 데 사용할 InstanceContext를 결정합니다.

이 샘플에서는 Client1Client2라는 두 개의 클라이언트를 사용하여 공유를 설명합니다. 다음 코드에는 두 클라이언트가 상호 작용하는 시퀀스가 나와 있습니다. 클라이언트와 서버는 모두 CustomHeader를 사용하여 InstanceContext의 고유 식별자를 전달하고 난수 생성기 유틸리티를 사용하여 32바이트 고유 식별자를 생성합니다.

public static class CustomHeader
{
    public static readonly String HeaderName = "InstanceId";
    public static readonly String HeaderNamespace = "http://Microsoft.ServiceModel.Samples/Sharing";
}

static string NewInstanceId()
{
    byte[] random = new byte[256 / 8];
    randomNumberGenerator.GetBytes(random);
    return Convert.ToBase64String(random);
}
  1. Client1은 헤더를 추가할 수 있도록 OperationContextScope를 만듭니다. 그런 다음 고유 ID를 생성하여 이 ID를 보내는 헤더 목록에 값으로 추가합니다.

    //Create a new 1028 bit strong InstanceContextId that we want the server to associate 
    //the InstanceContext that processes all messages from this client.
    String uniqueId = NewInstanceId();
    
    MessageHeader Client1InstanceContextHeader = MessageHeader.CreateHeader(
        CustomHeader.HeaderName,
        CustomHeader.HeaderNamespace,
        uniqueId);
    
    try
    {
        using (new OperationContextScope(client1.InnerChannel))
        {
            //Add the header as a header to the scope so it gets sent for each message.
            OperationContext.Current.OutgoingMessageHeaders.Add(Client1InstanceContextHeader);
            ...
        }
    }
    

    그런 다음 원격 서버에서 여러 작업을 호출하는 DoCalculations를 호출합니다. 호출된 각 작업에 대해 사용자 지정 헤더 및 생성된 ID가 보내집니다.

  2. Client1Add 작업을 호출하고 이는 이 채널에서의 첫 번째 호출입니다.

  3. 서버는 메시지를 수신하고 채널 및 메시지와 함께 CalculatorExtension.GetExistingInstanceContext를 호출합니다. 확장은 채널이 InstanceContext에 연결되었는지 확인합니다. 연결되어 있지 않으면 확장은 사용자 지정 헤더를 찾으려 하고 캐시에 해당 ID의 InstanceContext가 포함되어 있는지 확인합니다. 이 경우에는 캐시가 비어 있으므로 null이 반환됩니다. InstanceContext가 만들어지기 전에 확장은 InstanceContext에 대한 추가 정보 유지 및 여러 스레드 간 조정을 위해 실제로 AddressableInstanceContextInfo(이 소스의 뒷부분에 정의됨)를 저장합니다.

    // If the channel has a session, we bind the session to a particular InstanceContext
    // based on the first message, and then route all subsequent messages on that session to
    // the same InstanceContext.
    bool hasSession = (channel.SessionId != null);
    if (hasSession)
    {
        info = channel.Extensions.Find<AddressableInstanceContextInfo>();
        if (info != null)
        {
            ...
        }
    }
    
    // If this is the first message of a session, or is using a datagram channel, look in
    // the message headers to see if there is a header with an instance id.
    int headerIndex = message.Headers.FindHeader(CustomHeader.HeaderName, CustomHeader.HeaderNamespace);
    
    // If there was a header, extract the instanceId.
    string instanceId = null;
    if (headerIndex != -1)
    {
        instanceId = message.Headers.GetHeader<string>(headerIndex);
    }
    
    ...
    
    // Check our table to see if we recognize the instance id.
    lock (this.ThisLock)
    {
        if ((instanceId == null) || !this.contextMap.TryGetValue(instanceId, out info))
        {
            isNew = true;
            ...
        }
        ...
    }
    ...
    if (isNew)
    {
        // This tells WCF to create a new InstanceContext and call InitializeInstanceContext.
        return null;
    }
    
  4. 서버는 새 InstanceContext를 만들고 CalculatorExtension.InitializeInstanceContext를 호출합니다. InitializeInstanceContext 메서드는 새로 만든 InstanceContext를 확장에 알립니다. 그런 다음 채널(세션 채널의 경우) 또는 캐시(데이터그램 채널의 경우)에서 AddressableInstanceContextInfo를 검색하여 InstanceContext의 확장 컬렉션에 추가합니다. 이렇게 하면 서버가 InstanceContext의 ID를 신속하게 검색할 수 있습니다. 채널에 세션이 있는 경우 확장은 이 채널을 InstanceContextIncomingChannels 컬렉션에 추가하여 채널이 닫힐 때까지 WCF(Windows Communication Foundation)에서 InstanceContext를 닫지 않도록 합니다. 이 샘플은 또한 InstanceContext개체가 명시적으로 닫힐 경우 캐시에서 제거될 수 있도록 이 개체의 Closed 이벤트와 후크되어 있습니다. 이제 이 InstanceContext를 사용하여 메시지를 처리하고 회신합니다.

    if (hasSession)
    {
        // Since this is a new InstanceContext, we could not add the channel in
        // GetExistingInstanceContext, so add it here.
        instanceContext.IncomingChannels.Add(channel);
    
        // If we have a session, we stored the info in the channel, so just look it up
        // there.
        info = channel.Extensions.Find<AddressableInstanceContextInfo>();
    }
    else
    {
        // Otherwise, if we don't have a session, look the info up again in the table.
        ...
    }
    
    // Now that we have the InstanceContext, we can link it to the
    // AddressableInstanceContextInfo and vice versa.
    if (info != null)
    {
        instanceContext.Extensions.Add(info);
        info.SetInstanceContext(instanceContext);
    }
    
    // When the InstanceContext starts closing, remove it from the table.
    //
    // Generally we will already have the lock because Close will happen inside
    // CallIdleCallback.  However, if someone just closes the InstanceContext explicitly
    // before it goes idle, we will not have the lock.  Since modifying Dictionary is not
    // thread-safe, we lock here.
    instanceContext.Closing += delegate(object sender, EventArgs e)
    {
        lock (this.ThisLock)
        {
            this.contextMap.Remove(info.InstanceId);
        }
    };
    
  5. 그런 다음 Client1은 두 번째 작업인 Subtract를 호출합니다. 다시 한 번 새 메시지와 함께 CalculatorExtension.GetExistingInstanceContext가 호출됩니다. 헤더가 검색되고 이번에는 조회가 성공합니다. 그 결과 캐시에서 InstanceContext가 반환됩니다. WaitForInstance는 첫 번째 호출에서 InitializeInstanceContext 호출을 마쳤는지 확인합니다. WCF에서는 이 InstanceContext를 사용하여 메시지의 나머지 부분을 처리합니다.

    if (hasSession)
    {
        info = channel.Extensions.Find<AddressableInstanceContextInfo>();
        if (info != null)
        {
            // We may be processing a second message before the first message has finished
            // initializing the InstanceContext.  Wait here until the first message is
            // done.  If the first message has already finished initializing, this returns
            // immediately.
            info.IncrementBusyCount();
            return info.WaitForInstanceContext();
        }
    } 
    
  6. Client1SubtractDelete 작업을 호출하고 이 작업은 5단계를 반복하여 동일한 InstanceContext를 사용합니다.

  7. 그런 다음 클라이언트는 또 다른 채널(Client2)을 만들고 다시 한 번 OperationContextScope를 만들어 OutgoingMessageHeaders 컬렉션에 동일한 ID를 추가합니다. 따라서 Client1에서 사용한 동일한 ID가 이제 Client2에서 수행되는 모든 호출과 함께 보내집니다.

  8. Client2Add(), Subtract(), Multiply()Divide() 작업을 호출하며, 7단계와 동일한 논리를 사용하여 첫 번째 클라이언트가 만든 InstanceContext에서 이러한 작업이 모두 서비스됩니다. 서버는 CalculatorExtension.GetExistingInstanceContext를 호출합니다. 이 확장은 헤더를 찾고 헤더와 연결된 InstanceContext를 조회합니다. 이 InstanceContext는 메시지를 디스패치하는 데 사용됩니다.

    // If this is the first message of a session, or is using a datagram channel, look in
    // the message headers to see if there is a header with an instance id.
    int headerIndex = message.Headers.FindHeader(CustomHeader.HeaderName, CustomHeader.HeaderNamespace);
    
    // If there was a header, extract the instanceId.
    string instanceId = null;
    if (headerIndex != -1)
    {
        instanceId = message.Headers.GetHeader<string>(headerIndex);
    }
    
    // Remember if we created a new AddressableInstanceContextInfo.
    bool isNew = false;
    
    // Check our table to see if we recognize the instance id.
    lock (this.ThisLock)
    {
        if ((instanceId == null) || !this.contextMap.TryGetValue(instanceId, out info))
        {
            ...
        }
        ...
    }
    ...
    if (isNew)
    {
        // ...
    }
    else
    {
        InstanceContext instanceContext = info.WaitForInstanceContext();
        ...
        return instanceContext;
    }
    

샘플을 설치, 빌드 및 실행하려면

  1. Windows Communication Foundation 샘플의 일회 설치 절차를 수행했는지 확인합니다.

  2. 솔루션을 빌드하려면 Windows Communication Foundation 샘플 빌드의 지침을 따릅니다.

  3. 단일 컴퓨터 또는 다중 컴퓨터 구성에서 샘플을 실행하려면 Windows Communication Foundation 샘플 실행의 지침을 따릅니다.

Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.