TcpClient und TcpListener können zwar eine schöne Tcp-Verbindung herstellen, aber die besteht dann nur aus einem Stream, wo man Bytes reinschreiben bzw. auslesen kann. Damit kann man eiglich noch nichts tun, denn es fehlt die Info, was mit den Bytes zu geschehen hat: Soll das nun eine Mitteilung sein (und an wen), oder will man einen ChatRoom betreten oder sonstwas.
Also muß ein BefehlsProtokoll her, also eine Art Meta-Sprache, die festlegt, welche Bytes was bedeuten.
Etwa kann man Messages immer so aufbauen, dass 2 Bytes den Befehl festlegen, 4 Bytes die folgende Länge des Datenpakets, und dann folgt halt das Datenpaket.
Und im Server musses einen Select Case geben, wo dem Befehl entsprechend reagiert wird, und im Client auch ein Select Case, aber anderen Inhalts, denn der Client tut ja anderes als der Server.
Also nicht unkompliziert das.
Remoting ist nicht so
Bei Remoting kann man direkt (Remote-)Methoden der Gegenseite aufrufen. Sogar Functions mit beliebigen Rückgabewerten! Welche Methoden das sind wird in einem Interface festgelegt, also alles fein hardcodet und compiler-geprüft - da kann kein Mismatch auftreten, wie beim Befehls-Protokoll.
Um das Theater mit den Datenpaketen, und was die Sachen zu bedeuten haben kümmert sich irgendeine Hexerei im Namespace
Aber leider ist Remoting auch nicht einfach
Zum Beispiel das mit den Rückgabewerten: das kann man fast gleich wieder vergessen. Denn wenn man eine Function der Gegenseite aufruft, und die Verbindung lahmt, dann ist der Aufrufer direkt blockiert. Also die ausgehende Kommunikation immer asynchron ausführen, und damit ist das Thema Rückgabewert gegessen.
Und die eingehende Kommunikation kommt natürlich immer im NebenThread des Empfängers an, muß also üblicherweise erst in den MainThread gewechselt werden, um Threading-Probleme bei der Anzeige, aber auch bei der Datenverarbeitung zu vermeiden.
Da kann man mit
In meinem Sample gehe ich mit der
Schon die Anlage des Programmier-Systems ist bei Inter-Prozess-Kommunikation immer ziemlich umständlich: Eigentlich kann man nicht die eine Seite ohne die andere programmieren, aber beim Test müssen dann doch 2 gänzlich unabhängige Prozesse gestartet werden.
Aber mit einer Solution kann man halt nur einen Prozess debuggen.
Also ich hab das so gelöst, dass ich mit einem Mini-Launcher wählen kann, ob ich die Client- oder die Server-Anwendung debuggen will. Der jeweils andere Part wird dann direkt als Exe gestartet, mittels
Und 3 Projekte braucht man immer: Server, Client und Common, wobei letzteres nur die Kommunikations-Schnittstelle definiert.
Auf Common wird von den beiden anderen verwiesen, und damit ist die Kompatiblität von Server und Client gewährleistet.
Ich hab ins Common-Projekt noch 3 weitere Klassen gepackt, einmal ServerInfo, von wo Information zur Remoting-Konfiguration abgerufen wird (müssen ja auch beide wissen). Und eine ServerBase und eine ProxyFactory.
ServerBase ist im Server zu beerben, und dort sind dann die SchnittstellenMember dranzuprogrammieren.
ProxyFactory wird nicht beerbt, sondern der Client instanziert son Ding, und kann dann über den Channel einen (oder theoretisch auch mehrere) Server-Objekt-Proxies abrufen.
So funktioniert das halt: Im Server existiert ein ServerObject, was die Methoden der IServerObject-Schnittstelle implementiert. Der Client kann sich einen Proxy davon holen, und was er auf den Methoden des Proxies herumorgelt, kommt direkt - quasi Computer-Vodoo - beim Server wieder raus.
Die Gegenrichtung habich noch unheimlicher implementiert:
Beim Handshake muss der Client Delegaten aller Methoden übergeben, die er remoted haben möchte. Diese Delegaten bunkert der Server in seiner Datenverarbeitung, und so kann er ganz gezielt jeden einzelnen Client ansprechen, auf jeder der bereitgestellten Remote-Methoden.
Der Server könnte auch Events versenden (ja, das geht auch über Remoting!), aber damit würde er immer unterschiedslos an alle Clients senden, was zB bei einem Chat mit ChatRooms ühaupt nicht wünschenswert ist.
Ungelöste Probleme
Das ServerObject muss von
Aber ich hab noch nicht herausgefunden, wie man damit umgeht, und ob man damit ühaupt umgehen muß - wäre ja schon gut, wenn man den Datenbestand bei Gelegenheit aufräumen könnte.
Besonders eigenartig finde ich, dass ein Client sich nicht disconnecten kann. Ich führe alles aus, um ihn vom Netz zu nehmen - allein, das Server-Object-Voodoo funktioniert einfach weiter.
Ganz besonders ungelöst ist die Sicherheitsfrage: Prinzipiell ermöglicht die Channelkonfiguration auch eine TLS-gesicherte Kommunikation, also das wäre perfekt wasserdicht. Leider hab ich das noch nie gebacken gekriegt, weil TLS zu implementieren erfordert iwie ein Zertifikat zu installieren, glaub sowohl auffm Server als auch auffm Client - also mit der Zertifiziererei komme ich bislang nicht klar.
Die Anwendung
Wie gesagt: Interprozess-Kommunikation tuts kaum je unter 3 Projekten. Bei mir kommt da noch das Launcher-Projekt "RemotingTester" hinzu sowie ein Helpers-Projekt mit Sachen, ohne die ich garnet mehr coden mag.
Man sieht, wie die Projekte aufeinander verweisen, Common ist am kleinsten, Server und Client verweisen je auf Common, und "RemotingTester" muß sowohl Server als auch Client kennen, damit der Launcher davon auswählen kann.
Also muß ein BefehlsProtokoll her, also eine Art Meta-Sprache, die festlegt, welche Bytes was bedeuten.
Etwa kann man Messages immer so aufbauen, dass 2 Bytes den Befehl festlegen, 4 Bytes die folgende Länge des Datenpakets, und dann folgt halt das Datenpaket.
Und im Server musses einen Select Case geben, wo dem Befehl entsprechend reagiert wird, und im Client auch ein Select Case, aber anderen Inhalts, denn der Client tut ja anderes als der Server.
Also nicht unkompliziert das.
Remoting ist nicht so
Bei Remoting kann man direkt (Remote-)Methoden der Gegenseite aufrufen. Sogar Functions mit beliebigen Rückgabewerten! Welche Methoden das sind wird in einem Interface festgelegt, also alles fein hardcodet und compiler-geprüft - da kann kein Mismatch auftreten, wie beim Befehls-Protokoll.
Um das Theater mit den Datenpaketen, und was die Sachen zu bedeuten haben kümmert sich irgendeine Hexerei im Namespace
System.Runtime.Remoting
.Aber leider ist Remoting auch nicht einfach
Zum Beispiel das mit den Rückgabewerten: das kann man fast gleich wieder vergessen. Denn wenn man eine Function der Gegenseite aufruft, und die Verbindung lahmt, dann ist der Aufrufer direkt blockiert. Also die ausgehende Kommunikation immer asynchron ausführen, und damit ist das Thema Rückgabewert gegessen.
Und die eingehende Kommunikation kommt natürlich immer im NebenThread des Empfängers an, muß also üblicherweise erst in den MainThread gewechselt werden, um Threading-Probleme bei der Anzeige, aber auch bei der Datenverarbeitung zu vermeiden.
Da kann man mit
Synclock
optimieren, um ohne Threadwechsel in den MainThread auszukommen, aber das muß sehr sehr sachkundig geschehen, und u.U. an vielen verschiedenen Stellen im Programm.In meinem Sample gehe ich mit der
Control.Invoke
-Keule drüber, dann bin ich im Mainthread, und damit auf der sicheren Seite Schon die Anlage des Programmier-Systems ist bei Inter-Prozess-Kommunikation immer ziemlich umständlich: Eigentlich kann man nicht die eine Seite ohne die andere programmieren, aber beim Test müssen dann doch 2 gänzlich unabhängige Prozesse gestartet werden.
Aber mit einer Solution kann man halt nur einen Prozess debuggen.
Also ich hab das so gelöst, dass ich mit einem Mini-Launcher wählen kann, ob ich die Client- oder die Server-Anwendung debuggen will. Der jeweils andere Part wird dann direkt als Exe gestartet, mittels
Process.Start()
.Und 3 Projekte braucht man immer: Server, Client und Common, wobei letzteres nur die Kommunikations-Schnittstelle definiert.
Auf Common wird von den beiden anderen verwiesen, und damit ist die Kompatiblität von Server und Client gewährleistet.
Ich hab ins Common-Projekt noch 3 weitere Klassen gepackt, einmal ServerInfo, von wo Information zur Remoting-Konfiguration abgerufen wird (müssen ja auch beide wissen). Und eine ServerBase und eine ProxyFactory.
ServerBase ist im Server zu beerben, und dort sind dann die SchnittstellenMember dranzuprogrammieren.
ProxyFactory wird nicht beerbt, sondern der Client instanziert son Ding, und kann dann über den Channel einen (oder theoretisch auch mehrere) Server-Objekt-Proxies abrufen.
So funktioniert das halt: Im Server existiert ein ServerObject, was die Methoden der IServerObject-Schnittstelle implementiert. Der Client kann sich einen Proxy davon holen, und was er auf den Methoden des Proxies herumorgelt, kommt direkt - quasi Computer-Vodoo - beim Server wieder raus.
Die Gegenrichtung habich noch unheimlicher implementiert:
Beim Handshake muss der Client Delegaten aller Methoden übergeben, die er remoted haben möchte. Diese Delegaten bunkert der Server in seiner Datenverarbeitung, und so kann er ganz gezielt jeden einzelnen Client ansprechen, auf jeder der bereitgestellten Remote-Methoden.
Der Server könnte auch Events versenden (ja, das geht auch über Remoting!), aber damit würde er immer unterschiedslos an alle Clients senden, was zB bei einem Chat mit ChatRooms ühaupt nicht wünschenswert ist.
Ungelöste Probleme
Das ServerObject muss von
MarshalByRefObject
erben, sonst geht es nicht durch den Channel. MarshalByRefObject
's Hauptaufgabe scheint zu sein, einen LifeTimeService anzubieten, der im Hintergrund regelmäßig prüft, ob die Gegenseite ühaupt noch da ist. Weil ein Client könnte sich ja verabschieden, ohne bescheid zu geben. Da würden sich mit der Zeit lauter tote Client-Datensätze im Server sammeln, und v.a., wenn er die Toten ansprechen will, dürfte das jedesmal jeweils einen Thread blockieren für ich weiß nicht, wie lange - also das geht nicht, da soll sich gerne das MarshalByRefObject
drum kümmern, dass die Toten auch begraben werden können.Aber ich hab noch nicht herausgefunden, wie man damit umgeht, und ob man damit ühaupt umgehen muß - wäre ja schon gut, wenn man den Datenbestand bei Gelegenheit aufräumen könnte.
Besonders eigenartig finde ich, dass ein Client sich nicht disconnecten kann. Ich führe alles aus, um ihn vom Netz zu nehmen - allein, das Server-Object-Voodoo funktioniert einfach weiter.
Ganz besonders ungelöst ist die Sicherheitsfrage: Prinzipiell ermöglicht die Channelkonfiguration auch eine TLS-gesicherte Kommunikation, also das wäre perfekt wasserdicht. Leider hab ich das noch nie gebacken gekriegt, weil TLS zu implementieren erfordert iwie ein Zertifikat zu installieren, glaub sowohl auffm Server als auch auffm Client - also mit der Zertifiziererei komme ich bislang nicht klar.
Die Anwendung
Wie gesagt: Interprozess-Kommunikation tuts kaum je unter 3 Projekten. Bei mir kommt da noch das Launcher-Projekt "RemotingTester" hinzu sowie ein Helpers-Projekt mit Sachen, ohne die ich garnet mehr coden mag.
Man sieht, wie die Projekte aufeinander verweisen, Common ist am kleinsten, Server und Client verweisen je auf Common, und "RemotingTester" muß sowohl Server als auch Client kennen, damit der Launcher davon auswählen kann.
Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()