Пример удаленного взаимодействия. Поставщик приемников каналов

Этот раздел относится к технологии прежних версий, которая сохраняется для обеспечения обратной совместимости с существующими приложениями и не рекомендуется для разработки новых приложений. Сейчас распределенные приложения следует создавать с помощью  Windows Communication Foundation (WCF).

В следующем примере кода выполняется построение объекта ChannelSinkPropertySetterProvider, являющегося поставщиком приемников каналов, который может считывать значения в файле конфигурации клиентского приложения и проверять стек приемников на наличие приемников канала, которые содержат найденные в этом файле свойства. Этот объект удобно использовать при построении цепочки приемников с помощью файла конфигурации, а не программным образом с передачей в конструкторы объектов IDictionary.

Этот поставщик не вставляет в цепочку приемников канала каких-либо приемников. Он просто проверяет цепочку вызовов в поисках набора свойств, и его можно легко настроить, чтобы он принимал только свойства, поддерживаемые конкретным приемником.

2ckwbt8a.Caution(ru-ru,VS.100).gifВнимание!
По умолчанию при удаленном взаимодействии .NET Framework проверка подлинности и шифрование не выполняются. Поэтому рекомендуется принять все необходимые меры для проверки удостоверений клиентов и серверов до удаленного взаимодействия с ними. Поскольку для запуска приложений, использующих удаленное взаимодействие .NET Framework, требуются разрешения FullTrust, если неавторизованный клиент получит доступ к серверу, клиент сможет запускать код так, как если бы он был полностью доверенным. Всегда выполняйте проверку подлинности конечных точек и шифрование потоков взаимодействия, либо разместив типы, поддерживающие удаленное взаимодействие, в службах IIS, либо создав пользовательскую пару приемников каналов для выполнения этих задач.

2ckwbt8a.note(ru-ru,VS.100).gifПримечание
В этом примере используются пароли, жестко заданные в коде. Жестко задавать пароли в коде не рекомендуется, и в данном примере это сделано исключительно в качестве демонстрации. Лучший способ получить пароль пользователя — запросить его у пользователя.

Компиляция и выполнение образца

  1. В командной строке введите следующие команды:

    vbc /t:library /r:System.Runtime.Remoting.dll /out:PropSink.dll ChannelSinkPropertySetterProvider.vb
    vbc /t:library /r:System.Runtime.Remoting.dll serviceclass.vb
    vbc /r:System.Runtime.Remoting.dll /r:ServiceClass.dll /r:PropsSink.dll client.vb
    vbc /r:System.Runtime.Remoting.dll /r:ServiceClass.dll server.vb
    
    csc /t:library /r:System.Runtime.Remoting.dll /out:PropSink.dll ChannelSinkPropertySetterProvider.cs
    csc /t:library /r:System.Runtime.Remoting.dll serviceclass.cs
    csc /r:System.Runtime.Remoting.dll /r:ServiceClass.dll /r:PropSink.dll client.cs
    csc /r:System.Runtime.Remoting.dll /r:ServiceClass.dll server.cs
    
  2. Откройте два окна командной строки и перейдите в них в один каталог. В первом окне введите server. Пока выполняется эта команда, введите в другом окне командной строки client.

    Поставщик приемников каналов поддерживает атрибут writeToConsole, чтобы указать, нужно ли выводить действия поставщика на консоль клиента. В данном примере этот атрибут имеет значение true.

    Это приложение выполняется на одном компьютере или в сети. Чтобы выполнить это приложение в сети, необходимо в конфигурации клиента заменить "localhost" на имя удаленного компьютера.

ChannelSinkPropertySetterProvider

Imports System
Imports System.Collections
Imports System.IO
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels

'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
    Implements IClientChannelSinkProvider

    Private _next As IClientChannelSinkProvider = Nothing
    Private _channelSinkProperties As IDictionary = Nothing
    Private _providerData As ICollection = Nothing

    ' Set 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

    'Constructor with properties. If writeToConsole attribute is "true", 
    'this constructor will dump all custom configuration properties set 
    'in the configuration file. 
    Public Sub New(ByVal properties As IDictionary, ByVal providerData As ICollection)
        _channelSinkProperties = properties

        ' Sets the private console dump property for this provider.
        If (properties.Contains("writeToConsole")) Then
            _consoleDump = Boolean.Parse(properties.Item("writeToConsole").ToString)
        End If

        _providerData = providerData

        If (_consoleDump = True) 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

                Dim child As Object
                For Each child In sinkData.Children
                    Console.WriteLine("Child: " & child.GetType().Name)
                Next

            Next

            Dim entry As DictionaryEntry
            For Each entry In properties

                Console.WriteLine("channel sink properties: " & entry.Key.ToString() & ", " & entry.Value.ToString())
            Next

            Console.WriteLine()
        End If

    End Sub

    '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 System.Runtime.Remoting.Channels.IChannelSender, ByVal url As String, ByVal remoteChannelData As Object) As System.Runtime.Remoting.Channels.IClientChannelSink Implements System.Runtime.Remoting.Channels.IClientChannelSinkProvider.CreateSink
        If (_consoleDump) Then
            Console.WriteLine("CreateSink is called.")
            Console.WriteLine("By " & channel.ChannelName)
        End If

        Dim nextSink As IClientChannelSink = Nothing

        If (_next IsNot Nothing) Then

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

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

            WalkSinkChain(nextSink)

        End If
        Return nextSink
    End Function

    ' 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("Walking the sink chain to find sink properties... ")
        End If

        While (thisSink IsNot Nothing)
            If (_consoleDump) Then
                Console.WriteLine(New String("_", 80))

                Console.WriteLine("Next sink is : " & CType(thisSink, Object).GetType().Name)
                    DumpSinkProperties(thisSink)

            End If
            SetSinkProperties(thisSink)
            thisSink = thisSink.NextChannelSink
        End While
        Return
    End Sub

    Private Sub DumpSinkProperties(ByVal sink As IClientChannelSink)
        If (sink.Properties Is Nothing) Then
            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
    End Sub

    ' 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
            Console.WriteLine("There are no properties available on the " & CType(sink.Properties, 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
                    Console.WriteLine("Setting sink property template on " & CType(sink, Object).GetType().Name & "." & entry.Key.ToString())
                End If
                sink.Properties.Item(entry.Key) = _channelSinkProperties.Item(entry.Key)
            End If
        Next

        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
                        Console.WriteLine("Setting Instance override on " & CType(sink, Object).GetType().Name & "." & configEntry.Key)
                    End If
                End If
                sink.Properties.Item(configEntry.Key) = configEntry.Value
            Next
        Next

        If (_consoleDump) Then
            DumpSinkProperties(sink)
        End If
    End Sub
    Public Property [Next]() As System.Runtime.Remoting.Channels.IClientChannelSinkProvider Implements System.Runtime.Remoting.Channels.IClientChannelSinkProvider.Next
        Get
            Return _next
        End Get
        Set(ByVal value As System.Runtime.Remoting.Channels.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 As IClientChannelSinkProvider = New SoapClientFormatterSinkProvider()
        Dim sink As IClientChannelSinkProvider = chain

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

        Return chain
    End Function
End Class
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 = true;

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

        // 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();
            }
        } 

        // 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;
        } 
    } 

Клиент

Imports System
Imports System.Runtime.Remoting
Imports [Shared]

Public Class Client

    Shared Sub Main()
        RemotingConfiguration.Configure("Client.exe.config", False)
        Dim service As ServiceClass = New ServiceClass()
        Console.WriteLine(service.GetServerTime())
    End Sub

End Class
using System;
using System.Runtime.Remoting;

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

Сервер

Imports System
Imports System.Runtime.Remoting

Class Server

    Shared Sub Main()
        RemotingConfiguration.Configure("server.exe.config", False)
        Console.WriteLine("Press enter to stop this process.")
        Console.ReadLine()
    End Sub

End Class
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

Imports System
Imports System.Runtime.Remoting

Public Class ServiceClass
    Inherits MarshalByRefObject

    Private starttime As DateTime

    Public Sub New()
        Console.WriteLine("A ServiceClass has been created.")
        starttime = DateTime.Now
    End Sub

    Protected Overrides Sub Finalize()
        Console.WriteLine("This object is being collected after " & (New TimeSpan(DateTime.Now.Ticks - starttime.Ticks)).ToString() & " seconds.")
    End Sub

    Public Function GetServerTime() As DateTime
        Console.WriteLine("Time requested by a client.")
        Return DateTime.Now
    End Function
End Class
using System;
using System.Runtime.Remoting;

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="http://localhost:8080/RemoteObject"
         />
      </client>
      <channels>
         <channel ref="http">
         <clientProviders>
            <formatter ref="soap"/>
            <provider ref="propsetter" username="bob" writeToConsole="true">
               <myElement 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, PropSink" 
         />
      </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>

См. также

Основные понятия

Каналы
Приемники и цепочки приемников

Другие ресурсы

Примеры удаленного взаимодействия