다음을 통해 공유


원격 서비스 예: 채널 싱크 공급자

다음 코드 예에서는 응용 프로그램 구성 파일에서 값을 읽고 이 파일에서 찾은 속성을 지원하는 채널 싱크를 싱크 스택에서 찾을 수 있는 채널 싱크 공급자인 ChannelSinkPropertySetterProvider를 빌드합니다. 이 예는 체인을 프로그래밍 방식으로 만들고 IDictionary 개체를 생성자에 전달하는 대신 구성 파일을 사용하여 싱크 체인을 빌드하는 데 유용합니다.

이 공급자는 채널 싱크 체인에 싱크를 삽입하지 않으며, 단지 싱크 체인에서 호환되는 속성을 찾습니다. 이 공급자는 특정 싱크에서 지원하는 속성만 받아들이도록 쉽게 사용자 지정할 수 있습니다.

또한 이 샘플 공급자는 Visual Basic으로 작성되고 컴파일되는 반면 인프라의 나머지 부분은 C# 프로그래밍 언어로 작성되어 있습니다. C# 버전의 공급자도 포함되어 있지만 주어진 명령줄로 컴파일되지는 않습니다.

경고

.NET Remoting에서는 기본적으로 인증이나 암호화 작업을 수행하지 않습니다. 따라서 원격으로 클라이언트나 서버와 상호 작용하기 전에 클라이언트나 서버의 ID를 확인하는 데 필요한 모든 단계를 수행하는 것이 좋습니다. .NET Remoting 응용 프로그램을 실행하려면 FullTrust 권한이 필요하므로 권한이 없는 클라이언트에게 서버에 대한 액세스 권한을 부여하면 해당 클라이언트는 완전 신뢰된 상태처럼 코드를 실행할 수 있습니다. IIS(인터넷 정보 서비스)에서 원격화된 형식을 호스팅하거나 사용자 지정 채널 싱크 쌍을 만들어서 항상 끝점을 인증하고 통신 스트림을 암호화하십시오.

이 샘플을 컴파일하고 실행하려면

  1. 명령 프롬프트에서 다음 명령을 입력합니다.

    vbc -r:System.Runtime.Remoting.dll -t:library /out:PropsSink.dll ChannelSinkPropertySetterProvider.vb

    csc /r:System.Runtime.Remoting.dll /t:library /out:ServiceClass.dll serviceclass.cs

    csc /r:System.Runtime.Remoting.dll /r: ServiceClass.dll /r:PropsSink.dll client.cs

    csc /r:System.Runtime.Remoting.dll /r:ServiceClass.dll server.cs

  2. 동일한 디렉터리를 가리키는 두 개의 명령 프롬프트를 엽니다. 한 명령 프롬프트에는 server를 입력하고, 이 명령이 실행되면 다른 명령 프롬프트에 client를 입력합니다.

이 채널 싱크 공급자는 클라이언트 콘솔에서 해당 공급자의 작업에 대한 콘솔 덤프를 원하는지 여부를 나타내기 위해 writeToConsole 특성을 지원합니다. 이 예의 경우에는 이 특성이 true로 설정되어 있습니다.

이 응용 프로그램은 한 컴퓨터나 네트워크에서 실행됩니다. 이 응용 프로그램을 네트워크에서 실행하려면 클라이언트 구성의 **"localhost"**를 원격 컴퓨터의 이름으로 바꿉니다.

ChannelSinkPropertySetterProvider.vb

Imports System
Imports System.Collections
Imports System.IO
Imports System.Reflection
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Http
Imports System.Runtime.Remoting.Messaging
Imports System.Runtime.Remoting.MetadataServices
' The following is required only for command-line compilation.
' In the IDE it is implicit.
Imports Microsoft.VisualBasic

' This class implements a client-side channel sink provider that 
' walks the channel sink chain, looking for channel sink
' properties that match those specified in the configuration file. If it 
' finds them, the provider sets them to the values specified in the 
' configuration file. This is a simple helper provider that returns no 
' channel itself. Instead, it merely returns the next channel sink it can ' find, or Nothing.
Public Class ChannelSinkPropertySetterProvider
   Implements IClientChannelSinkProvider 
   
   Private _next As IClientChannelSinkProvider = Nothing
   Private _channelSinkProperties As IDictionary = Nothing
   Private _providerData As ICollection = Nothing
   
   ' Sets the writeToConsole attribute on this provider element in the 
   ' configuration file to "True"; otherwise, information is not written to the console.
   Private _consoleDump As Boolean = False
   
   
   ' Default constructor.
   Public Sub New()
      Console.WriteLine("Default constructor called.")
   End Sub 'New
    
   ' ChannelSinkPropertySetterProvider.
   ' Constructor with properties. If writeToConsole attribute is "True", 
   ' this constructor will dump all custom configuration properties set in 
   ' the configuration file. 
   Public Sub New(properties As IDictionary, providerData As ICollection)
      
      _channelSinkProperties = properties
      
      ' Sets the private console dump property for this provider.
      If Not (properties("writeToConsole") Is Nothing) Then
         _consoleDump = [Boolean].Parse(properties("writeToConsole").ToString())
      End If 
      _providerData = providerData
      
      If _consoleDump Then
         Console.WriteLine("ChannelSinkPropertySetterProvider custom constructor called.")
         
         Dim sinkData As SinkProviderData
         For Each sinkData In providerData
            
                Console.WriteLine("SinkProvider element: " & sinkData.Name)
            Dim prop As DictionaryEntry
            For Each prop In  sinkData.Properties
                    Console.WriteLine("Prop name: " & prop.Key.ToString() & " value: " & prop.Value.ToString())
            Next prop
            
            Dim child As Object
            For Each child In sinkData.Children
                    Console.WriteLine("Child: " & child.GetType().Name)
            Next child
         Next sinkData
         
         Dim entry As DictionaryEntry
         For Each entry In properties
                Console.WriteLine("channel sink properties: " & entry.Key.ToString() & ", " & entry.Value.ToString())
         Next entry
         
         Console.WriteLine()
      End If
   End Sub 'New
    
   ' ChannelSinkPropertySetterProvider.
   
   ' Called by the channel. Normally, this method takes any other sinks 
   ' created by other providers in the chain, links them together, and 
   ' then returns its own sink to the channel. In this case, this provider 
   ' merely sets matching properties on each channel sink in the chain, 
   ' and then returns the **next** channel sink to the channel or returns 
   ' null, indicating to the channel that it is the end of the custom 
   ' channel sink chain.
    Public Function CreateSink(ByVal channel As IChannelSender, ByVal url As String, ByVal remoteChannelData As Object) As IClientChannelSink Implements IClientChannelSinkProvider.CreateSink

        If _consoleDump Then
            Console.WriteLine("CreateSink is called.")
            ' To invoke GetType on an interface, cast the
            ' interface to type Object, using CType.
            Console.WriteLine("By " & CType(channel, Object).GetType.Name)
        End If

        Dim nextSink As IClientChannelSink = Nothing

        If Not (_next Is Nothing) Then

            nextSink = _next.CreateSink(channel, url, remoteChannelData)

            If nextSink Is Nothing Then
                If _consoleDump Then
                    Console.WriteLine("Next sink is null!")
                End If
                Return Nothing
            End If

            WalkSinkChain(nextSink)
        End If

        Return nextSink
    End Function 'CreateSink


    ' This call walks the sink chain, setting properties as it goes.
    ' The channelSinkProperties are the SinkProviderData dictionaries
    ' that contain the name of the subnode in the configuration file, and 
    ' a dictionary entry of attribute/value entries on that element.
    Private Sub WalkSinkChain(ByVal thisSink As IClientChannelSink)

        If thisSink Is Nothing Then
            Return
        End If
        If _consoleDump Then
            Console.WriteLine(ControlChars.CrLf & ControlChars.Tab & "Walking the sink chain to find sink properties... " & ControlChars.CrLf)
        End If
        While Not (thisSink Is Nothing)
            If _consoleDump Then
                Console.WriteLine(New [String]("_"c, 80))
                ' To invoke GetType on an interface, cast the
                ' interface to type Object, using CType.
                Console.WriteLine("Next sink is : " & CType(thisSink, Object).GetType().Name)
                DumpSinkProperties(thisSink)
            End If
            SetSinkProperties(thisSink)
            thisSink = thisSink.NextChannelSink
        End While

        Return
    End Sub 'WalkSinkChain


    Private Sub DumpSinkProperties(ByVal sink As IClientChannelSink)

        If sink.Properties Is Nothing Then
            ' To invoke GetType on an interface, cast the 
            ' interface to type Object, using CType.
            Console.WriteLine("There are no properties available on the " & CType(sink, Object).GetType().Name & " channelsink.")
            Return
        End If

        Dim entry As DictionaryEntry
        For Each entry In sink.Properties
            Console.Write("ChannelSink property: " & entry.Key.ToString() & " value: ")
            If entry.Value Is Nothing Then
                Console.WriteLine("No value.")
            Else
                Console.WriteLine(entry.Value.ToString())
            End If
        Next entry
    End Sub 'DumpSinkProperties


    ' This method sets properties on the sink. The algorithm is
    ' that in the absence of instance attribute/value entries, the
    ' provider element template attribute/value entries will be set.
    ' This is a simple implementation that does not care about the 
    ' element name underneath the provider element.
    Private Sub SetSinkProperties(ByVal sink As IClientChannelSink)

        If sink.Properties Is Nothing Then
            ' To invoke GetType on an interface, cast the
            ' interface to type Object, using CType.
            Console.WriteLine("There are no properties available on the " & CType(sink, Object).GetType().Name & " channelsink.")
            Return
        End If

        Dim entry As DictionaryEntry
        For Each entry In sink.Properties
            If _channelSinkProperties.Contains(entry.Key) Then

                If _consoleDump Then
                    ' To invoke GetType on an interface, cast
                    ' the interface to type Object, using CType.
                    Console.WriteLine("Setting sink property template on " & CType(sink, Object).GetType().Name & "." & entry.Key.ToString())
                End If
                sink.Properties(entry.Key) = _channelSinkProperties(entry.Key)
            End If
        Next entry

        Dim provider As SinkProviderData
        For Each provider In _providerData
            Dim configEntry As DictionaryEntry
            For Each configEntry In provider.Properties
                If sink.Properties.Contains(configEntry.Key) Then
                    If _consoleDump Then
                        ' To invoke GetType on an interface, 
                        ' cast the interface to type Object, using CType.
                        Console.WriteLine("Setting Instance override on " & CType(sink, Object).GetType().Name & "." & configEntry.Key)
                    End If
                End If
                sink.Properties(configEntry.Key) = configEntry.Value
            Next configEntry
        Next provider

        If _consoleDump Then
            DumpSinkProperties(sink)
        End If
    End Sub 'SetSinkProperties 

    ' "Next" is a Visual Basic keyword; to use it as a
    ' property name you must place it in square brackets.
    Public Property [Next]() As IClientChannelSinkProvider Implements IClientChannelSinkProvider.Next
        Get
            Return _next
        End Get

        Set(ByVal Value As IClientChannelSinkProvider)
            _next = Value
        End Set
    End Property


    ' This can be called in the constructor in case this provider is 
    ' intended to build its own channel sink provider chain. Without 
    ' providing such a chain, this provider must be specified in a 
    ' configuration file with other providers.
    Private Function CreateDefaultClientProviderChain() As IClientChannelSinkProvider

        Dim chain = New SoapClientFormatterSinkProvider()
        Dim sink As IClientChannelSinkProvider = chain

        sink.Next = New BinaryClientFormatterSinkProvider()
        sink = sink.Next

        Return chain
    End Function 'CreateDefaultClientProviderChain ' CreateDefaultClientProviderChain

End Class 'ChannelSinkPropertySetterProvider


' Class ChannelSinkPropertySetterProvider.

ChannelSinkPropertySetterProvider.cs

이 파일은 정보용으로만 포함된 것입니다.

using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.MetadataServices;

// This class implements a client-side channel sink provider that
// walks the channel sink chain, looking for channel sink
// properties that match those specified in the configuration file. If it 
// finds them, the provider sets them to the values specified in the 
// configuration file. This is a simple helper provider that returns no 
// channel itself. Instead, it merely returns the next channel sink it can // find, or null.
public class ChannelSinkPropertySetterProvider : IClientChannelSinkProvider{

   private IClientChannelSinkProvider _next = null;
   private IDictionary _channelSinkProperties = null;
   private ICollection _providerData = null;

   // Set the writeToConsole attribute on this provider element in the 
   // configuration file to "true"; otherwise, information is not written to the console.
   private bool _consoleDump = false;

   // Default constructor.
   public ChannelSinkPropertySetterProvider(){
      Console.WriteLine("Default constructor called.");

   } // ChannelSinkPropertySetterProvider.

   // Constructor with properties. If writeToConsole attribute is "true", 
   // this constructor will dump all custom configuration properties set 
   // in the configuration file. 

   public ChannelSinkPropertySetterProvider(IDictionary properties, ICollection providerData){

      _channelSinkProperties = properties;

      // Sets the private console dump property for this provider.
      if (properties["writeToConsole"] != null)
         _consoleDump = Boolean.Parse(properties["writeToConsole"].ToString());

      _providerData = providerData;

      if (_consoleDump){
         Console.WriteLine("ChannelSinkPropertySetterProvider custom constructor called.");

         foreach(SinkProviderData sinkData in providerData){

            Console.WriteLine("SinkProvider element: " + sinkData.Name);
            foreach(DictionaryEntry prop in sinkData.Properties){
               Console.WriteLine("Prop name: " + prop.Key.ToString() + " value: " + prop.Value.ToString());
            }

            foreach(object child in sinkData.Children){
               Console.WriteLine("Child: " + child.GetType().Name);
            }
         }
   
         foreach (DictionaryEntry entry in properties){
            Console.WriteLine("channel sink properties: " + entry.Key.ToString() + ", " + entry.Value.ToString());
         }

         Console.WriteLine();
      }

   } // ChannelSinkPropertySetterProvider.


   // Called by the channel. Normally, this method takes any other sinks 
   // created by other providers in the chain, links them together, and 
   // then returns its own sink to the channel. In this case, this
   // provider merely sets matching properties on each channel sink in the 
   // chain, and then returns the **next** channel sink to the channel or 
   // returns null, indicating to the channel that it is the end of the 
   // custom channel sink chain.
   public IClientChannelSink CreateSink(IChannelSender channel, string url, object remoteChannelData){
         
      if (_consoleDump){
         Console.WriteLine("CreateSink is called.");
         Console.WriteLine("By " + channel.GetType().Name);
      }

      IClientChannelSink nextSink = null;

      if (_next != null){

         nextSink = _next.CreateSink(channel, url, remoteChannelData);

         if (nextSink == null){
            if (_consoleDump)
                  Console.WriteLine("Next sink is null!");
            return null;
         }

         WalkSinkChain(nextSink);

      }

      return nextSink;

   }

   // This call walks the sink chain, setting properties as it goes.
   // The channelSinkProperties are the SinkProviderData dictionaries
   // that contain the name of the subnode in the configuration file, and 
   // a dictionary entry of attribute/value entries on that element.

   private void WalkSinkChain(IClientChannelSink thisSink){

      if (thisSink == null)
         return;

      if (_consoleDump)
         Console.WriteLine("\r\n\tWalking the sink chain to find sink properties... \r\n");

      while(thisSink != null){
         if (_consoleDump){
            Console.WriteLine(new String('_',80));
            Console.WriteLine("Next sink is : " + thisSink.GetType().Name);
            DumpSinkProperties(thisSink);
         }
         SetSinkProperties(thisSink);
         thisSink = thisSink.NextChannelSink;
      }

      return;
   }

   private void DumpSinkProperties(IClientChannelSink sink){

      if (sink.Properties == null){
         Console.WriteLine("There are no properties available on the " + sink.GetType().Name + " channelsink.");
         return;
      }

      foreach(DictionaryEntry entry in sink.Properties){
         Console.Write("ChannelSink property: " + entry.Key.ToString() + " value: ");
         if (entry.Value == null)
            Console.WriteLine("No value.");
         else
            Console.WriteLine(entry.Value.ToString());
      }
   
   }

   // This method sets properties on the sink.
   // The algorithm is that in the absence of instance attribute/value 
   // entries, the provider element template attribute/value entries will 
   // be set. This is a simple implementation that does not care about the 
   // element name underneath the provider element.

   private void SetSinkProperties(IClientChannelSink sink){

      if (sink.Properties == null){
         Console.WriteLine("There are no properties available on the " + sink.GetType().Name + " channelsink.");
         return;
      }

      foreach(DictionaryEntry entry in sink.Properties){
         if (_channelSinkProperties.Contains(entry.Key)){

            if (_consoleDump)
               Console.WriteLine("Setting sink property template on " + sink.GetType().Name + "." + entry.Key.ToString());
            sink.Properties[entry.Key] = _channelSinkProperties[entry.Key];
         }
      }

      foreach(SinkProviderData provider in _providerData){
         foreach(DictionaryEntry configEntry in provider.Properties){
            if (sink.Properties.Contains(configEntry.Key))
               if (_consoleDump)
                  Console.WriteLine("Setting Instance override on " + sink.GetType().Name +  "." + configEntry.Key);
               sink.Properties[configEntry.Key] = configEntry.Value;
         }
      }

      if (_consoleDump)
         DumpSinkProperties(sink);
   }
   
   public IClientChannelSinkProvider Next{
      get { 
         return _next; 
      }

      set { 
         _next = value; 
      }
   }

   // This can be called in the constructor in case this provider is 
   // intended to build its own channel sink provider chain. Without 
   // providing such a chain, this provider must be specified in a 
   // configuration file with other providers.
   private IClientChannelSinkProvider CreateDefaultClientProviderChain(){

      IClientChannelSinkProvider chain = new SoapClientFormatterSinkProvider();            
      IClientChannelSinkProvider sink = chain;

      sink.Next = new BinaryClientFormatterSinkProvider();
      sink = sink.Next;

      return chain;
   } // CreateDefaultClientProviderChain.
} // Class ChannelSinkPropertySetterProvider.

Client.cs

using System;
using System.Runtime.Remoting;

public class Client{
   
   public static void Main(string[] Args){
      
      RemotingConfiguration.Configure("Client.exe.config");
      ServiceClass service = new ServiceClass();
        Console.WriteLine(service.GetServerTime());   
   }
}

Server.cs

using System;
using System.Diagnostics;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

public class ServerProcess{

   public static void Main(string[] Args){

        RemotingConfiguration.Configure("server.exe.config");
      Console.WriteLine("Press enter to stop this process.");
      Console.ReadLine();
   
   }
}

ServiceClass.cs

using System;
using System.Diagnostics;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;

public class ServiceClass : MarshalByRefObject{

    private DateTime starttime;

   public ServiceClass(){
      Console.WriteLine("A ServiceClass has been created.");
       starttime = DateTime.Now;
   }

    ~ServiceClass(){
        Console.WriteLine("This object is being collected after " + (new TimeSpan(DateTime.Now.Ticks - starttime.Ticks)).ToString() + " seconds.");
    
    }

   public DateTime GetServerTime(){
       Console.WriteLine("Time requested by a client.");
      return DateTime.Now;
   }

}

Client.exe.config

<configuration>
  <system.runtime.remoting>
    <application>
      <client>
         <wellknown 
            type="ServiceClass, ServiceClass"
            url="https://localhost:8080/RemoteObject"
         />
      </client>
      <channels>
         <channel ref="http">
         <clientProviders>
            <formatter ref="soap"/>
            <provider ref="propsetter" username="bob" writeToConsole="true">
               <endpoint allowAutoRedirect="true"/>
               <endpoint preauthenticate="true"/>
               <endpoint url="example.com:9000" password="xyz" />
               <endpoint url="example.com:9001" password="123" />
               <endpoint timeout="10000"/>
               <endpoint url="example.com:*" username="bob2" password="qwerty" domain="hello" />
            </provider>
         </clientProviders>
         </channel>
      </channels>
   </application>
   <channelSinkProviders>
      <clientProviders>
         <provider 
            id="propsetter" 
            type="ChannelSinkPropertySetterProvider, PropsSink" 
         />
      </clientProviders>
   </channelSinkProviders>
   <debug loadTypes="true" />
  </system.runtime.remoting>
</configuration>

Server.exe.config

<configuration>
  <system.runtime.remoting>
    <application>
      <service>
        <wellknown mode="SingleCall" 
           type="ServiceClass, ServiceClass" 
           objectUri="RemoteObject" 
        />
      </service>
      <channels>
        <channel port="8080" ref="http" />
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

참고 항목

개념

채널
싱크 및 싱크 체인

기타 리소스

원격 서비스 예