Feedback

C# - TCP-Server mit einer Zeile Code!

Veröffentlicht von am 4/11/2007
(7 Bewertungen)
Viele Leute sagen: "Verteilte Anwendungen zu entwickeln ist schwierig und aufwändig!".

Das muss aber nicht sein. Folgendes Snippet reduziert den Aufwand auf eine Zeile Code auf der Seite der Server-Anwendung und eine weitere Zeile Code auf der Seite des Clients.
Da man auch für die Erzeugung eines lokalen Objekts eine Zeile Code braucht kann man also fast nicht von einem "Mehraufwand" sprechen.

Das Snippet besteht aus einer Klasse, die ein paar statische öffentliche Methoden enthält. Am besten packen Sie diese Klasse in ein Library-Projekt, binden System.Runtime.Remoting als Verweis ein und kompilieren das Ganze. Die nun entstandene Assembly können Sie bei all Ihren verteilten Applikationen verwenden.

Anwendungsbeispiel:

Angenommen ich habe eine Klasse mit Namen ServerSideCalc geschrieben, die bestimmte Berechnungen ausführt. Diese soll in einer Server-Anwendung laufen und ihre Funktionalität beliebig vielen Client-Programmen über TCP-Netzwerkzugriff zugänglich machen. Der Server soll über den TCP-Port 9400 angesprochen werden und die übertragenen Daten automatisch verschlüsseln. All diese Anforderungen werden mit folgender Zeile C#-Code umgesetzt:

// ServerSideCalc-Objekt unter dem Namen "Uschi" auf TCP-Port 9400 veröffentlichen
RemotingHelper.PublishObjectOverTCP(calc, "Uschi", 9400, true, true);

Auf der Client Seite kann das Objekt verwendet werden, als wäre es ein herkömmliches lokales Objekt. Dazu muss lediglich eine Verbindung zum entfernten Objekt aufgebaut werden. Das geht so:

// Verbindung zu entferntem Objekt mit dem Namen "Uschi" über TCP auf Server 192.168.0.100 herstellen
ServerSideCalc calc =
(ServerSideCalc)RemotingHelper.GetRemoteObjectOverTCP(typeof(ServerSideCalc), "Uschi", "192.168.0.100", 9400, true, true);

Es gibt eigentlich nur eine Sache zu beachten: Objekte die veröffentlicht werden sollen müssen direkt oder indirekt von MarshalByRefObject abgeleitet sein! Damit die Objekte nicht nach ablauf der Standard-Remoting-Lease von 15 Minuten entsorgt werden, muss man die Methode InitializeLifetimeService überschreiben und null zurückgeben.

public override object InitializeLifetimeService ()
{
// null zurückgeben, damit das Objekt unbegrenzte Lebensdauer hat.
return null;
}

Das Snippet funktioniert ab .NET Framework 2.0 oder höher. Entwickelt wurde es unter Windows Vista, läuft auch garantiert auf Windows XP und Windows Server 2003. Es sollte auch unter allen Versionen von Windows 2000 laufen (Habe es allerdings noch nicht getestet!). Auf Windows 9x und Windows Me läuft es NICHT.

Ein komplette Projektmappe mit Testprojekten gibts unter:
http://www.mycsharp.de/wbb2/thread.php?postid=191606#post191606

Sollte jemand Probleme mit dem Snippet haben, bitte E-Mail an: hagen.siegel@the-rainbird.de.
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Serialization.Formatters;

namespace Rainbird.Tools.EasyRemoting
{
    // NICHT VERGESSEN: System.Runtime.Remoting.dll ALS VERWEIS ZUFÜGEN!

    /// <summary>
    /// Enthält eine Sammlung von statischen Funktionen, die einfaches
    /// Remoting ermöglichen.
    /// </summary>
    public class RemotingHelper
    {
        /// <summary>
        /// Veröffentlicht ein beliebiges von MarshalByRef abgeleitetes Objekt über einen TCP-Kanal für entfernten Zugriff.
        ///
        /// Bei aktivierung der Sicherheit über den Parameter "enableSecurity", wird die Kommunikation 
        /// vom Absender signiert, um die Integrität sicherzustellen und zusätzlich verschlüsselt. 
        /// Über den Parameter "impersonate" kann Impersonierung eingeschaltet werden. Bei eingeschalteter
        /// Impersonierung, wird der Remoteaufruf im Kontext des Client-Benutzers ausgeführt.
        /// </summary>
        /// <remarks>
        /// Der TCP-Kanal wird automatisch konfiguriert und registriert. Die Kanal-Registrierung bleibt 
        /// über diesen Prozeduraufruf hinaus gültig. Wenn sie die selbe TCP-Anschlussnummer mehrmals an
        /// diese Methode übergeben, wird der bestehene Kanal verwendet. Die Sicherheitskonfiguration des 
        /// ersten Aufrufs ist deshalb entscheidend. Wenn sie für spätere Aufrufe andere Werte für die
        /// Parameter "enableSecurity" oder "impersonate" angeben, werden diese nicht berücksichtigt, 
        /// da der bestehende Kanal verwendet wird!
        /// </remarks>
        /// <param name="instance">Zu veröffentlichendes Objekt</param>
        /// <param name="publicName">Öffentlicher Name (Über diesen Namen greifen Clients entfernt auf das Objekt zu!)</param>
        /// <param name="tcpPort">TCP-Anschlussnummer</param>
        /// <param name="enableSecurity">Schalter für Sicherheit</param>
        /// <param name="impersonate">Schalter für Impersonierung</param>
        public static void PublishObjectOverTCP(MarshalByRefObject instance,string publicName,int tcpPort, bool enableSecurity,bool impersonate)
        { 
            // Kanal einrichten (Falls dies noch nicht geschehen ist!)
            SetupServerChannel(tcpPort, enableSecurity, impersonate);

            // Objektinstanz über den TCP-Kanal für entfernten Zugriff veröffentlichen
            RemotingServices.Marshal(instance, publicName);            
        }

        /// <summary>
        /// Gibt einen Proxy auf ein entferntes Objekt zurück. 
        /// 
        /// Das entfernte Objekt wird über eine TCP-Verbindung ferngesteuert. 
        /// Der Proxy vermittelt lokale Methodenaufrufe an das entfernte Objekt.
        /// Rückgabe-Werte können lokal über den proxy entgegen genommen werden.
        /// 
        /// Bei aktivierung der Sicherheit über den Parameter "enableSecurity", wird die Kommunikation 
        /// vom Absender signiert, um die Integrität sicherzustellen und zusätzlich verschlüsselt. 
        /// Über den Parameter "impersonate" kann Impersonierung eingeschaltet werden. Bei eingeschalteter
        /// Impersonierung, wird der Remoteaufruf im Kontext des Client-Benutzers ausgeführt.
        /// </summary>
        /// <param name="remoteType">Typ-Informationen des entfernten Objekts (übergeben Sie entweder den Typ der Klasse oder den typ einer, vom entfernten Objekt, implementierten Schnittstelle an!)</param>
        /// <param name="publicName">Öffentlicher Name des entfernten Objekts</param>
        /// <param name="serverName">DNS-Computername des Servers oder dessen IP-Adresse</param>
        /// <param name="tcpPort">TCP-Anschlussnummer des Servers</param>
        /// <param name="enableSecurity">Schalter für Sicherheit</param>
        /// <param name="impersonate">Schalter für Impersonierung</param>
        /// <returns>Transparentes Proxy-Objekt</returns>
        public static object GetRemoteObjectOverTCP(Type remoteType, string publicName, string serverName, int tcpPort, bool enableSecurity, bool impersonate)
        {
            // Kanal einrichten (Falls dies noch nicht geschehen ist!)
            SetupClientChannel(enableSecurity, impersonate);

            // URI für die Adressierung des entfernten Objekts generieren
            StringBuilder uriBuilder = new StringBuilder(@"tcp://");
            uriBuilder.Append(serverName);
            uriBuilder.Append(':');
            uriBuilder.Append(tcpPort);
            uriBuilder.Append('/');
            uriBuilder.Append(publicName);

            // Verbindung zum entfernten Objekt herstellen und einen Proxy erzeugen
            return Activator.GetObject(remoteType, uriBuilder.ToString());
        }

        /// <summary>
        /// Richtet einen TCP-Serverkanal ein. Serverkanäle nehmen Anfragen von Clients entgegen.
        /// 
        /// Bei aktivierung der Sicherheit über den Parameter "enableSecurity", wird die Kommunikation 
        /// vom Absender signiert, um die Integrität sicherzustellen und zusätzlich verschlüsselt. 
        /// Über den Parameter "impersonate" kann Impersonierung eingeschaltet werden. Bei eingeschalteter
        /// Impersonierung, wird der Remoteaufruf im Kontext des Client-Benutzers ausgeführt.
        /// </summary>
        /// <param name="tcpPort">TCP-Anschlussnummer</param>
        /// <param name="enableSecurity">Schalter für Sicherheit</param>
        /// <param name="impersonate">Schalter für Impersonierung</param>
        public static void SetupServerChannel(int tcpPort, bool enableSecurity,bool impersonate)
        {
            // Kanalnamen erzeugen
            string channelName = "RainbirdEasyRemotingServer" + Convert.ToString(tcpPort);

            // Kanal suchen
            IChannel channel = ChannelServices.GetChannel(channelName);

            // Wenn der Kanal nicht gefunden wurde ...
            if (channel == null)
            {
                // Konfiguration für den TCP-Kanal erstellen
                System.Collections.IDictionary channelSettings = new System.Collections.Hashtable();
                channelSettings["name"] = channelName;
                channelSettings["port"] = tcpPort;
                channelSettings["secure"] = enableSecurity;

                // Wenn Sicherheit aktiviert ist ...
                if (enableSecurity)
                {
                    // Impersonierung entsprechend der Einstellung aktivieren oder deaktivieren
                    channelSettings["tokenImpersonationLevel"] = impersonate ? System.Security.Principal.TokenImpersonationLevel.Impersonation : System.Security.Principal.TokenImpersonationLevel.Identification;

                    // Signatur und Verschlüssung explizit aktivieren
                    channelSettings["protectionLevel"] = System.Net.Security.ProtectionLevel.EncryptAndSign;
                }
                // Binäre Serialisierung von komplexen Objekten aktivieren
                BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
                provider.TypeFilterLevel = TypeFilterLevel.Full;

                // Neuen TCP-Kanal erzeugen
                channel = new TcpChannel(channelSettings, null, provider);

                // Kanal registrieren
                ChannelServices.RegisterChannel(channel, enableSecurity);
            }
        }

        /// <summary>
        /// Richtet einen TCP-Clientkanal ein. Clientkanäle senden Anfragen an Serverkanäle.
        /// 
        /// Bei aktivierung der Sicherheit über den Parameter "enableSecurity", wird die Kommunikation 
        /// vom Absender signiert, um die Integrität sicherzustellen und zusätzlich verschlüsselt. 
        /// Über den Parameter "impersonate" kann Impersonierung eingeschaltet werden. Bei eingeschalteter
        /// Impersonierung, wird der Remoteaufruf im Kontext des Client-Benutzers ausgeführt.
        /// <remarks>
        /// Achtung!
        /// 
        /// Die Sicherheitseinstellungen des Client-Kanals müssen mit denen des Server-Kanals genau 
        /// übereinstimmen. Ansonsten werden Sicherheitsprobleme auftreten.
        /// </remarks>
        /// </summary>      
        /// <param name="enableSecurity">Schalter für Sicherheit</param>
        /// <param name="impersonate">Schalter für Impersonierung</param>
        public static void SetupClientChannel(bool enableSecurity, bool impersonate)
        {
            // Kanalnamen erzeugen
            string channelName = "RainbirdEasyRemotingClient"; 

            // Kanal suchen
            IChannel channel = ChannelServices.GetChannel(channelName);

            // Wenn der Kanal nicht gefunden wurde ...
            if (channel == null)
            {
                // Konfiguration für den TCP-Kanal erstellen
                System.Collections.IDictionary channelSettings = new System.Collections.Hashtable();
                channelSettings["name"] = channelName;                
                channelSettings["secure"] = enableSecurity;

                // Wenn Sicherheit aktiviert ist ...
                if (enableSecurity)
                {
                    // Impersonierung entsprechend der Einstellung aktivieren oder deaktivieren
                    channelSettings["tokenImpersonationLevel"] = impersonate ? System.Security.Principal.TokenImpersonationLevel.Impersonation : System.Security.Principal.TokenImpersonationLevel.Identification;

                    // Signatur und Verschlüssung explizit aktivieren
                    channelSettings["protectionLevel"] = System.Net.Security.ProtectionLevel.EncryptAndSign;
                }
                // Binäre Serialisierung von komplexen Objekten aktivieren
                BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
                provider.TypeFilterLevel = TypeFilterLevel.Full;

                // Neuen TCP-Kanal erzeugen
                channel = new TcpChannel(channelSettings, null, provider);

                // Kanal registrieren
                ChannelServices.RegisterChannel(channel, enableSecurity);
            }
        }
    }
}

1 Kommentare zum Snippet

DaSchroeter schrieb am 4/12/2007:
Hab's noch nicht getestet, bin aber 'impressed' :)
Sehr schöne Idee!
 

Logge dich ein, um hier zu kommentieren!