RMI - Und Submodule

Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Neue Antwort erstellen

Silver_Knee

Betreff: RMI - Und Submodule

BeitragMo, Okt 27, 2014 12:52
Antworten mit Zitat
Benutzer-Profile anzeigen
Hi, hatte mir schon länger vorgenommen RMI in BlitzMax nachzuprogrammieren.

Remote Method Invocation bedeutet, dass man eine Methode eines Objektes aufruft, dass man selbst gar nicht im Prozess hat, also beispielsweise sich in einenem anderen Prozess oder auf einem anderen Rechner befindet.
Damit kann man die ganze Netzwerkproblematik mit Protokoll schreiben, Lese- und Schreibemethoden abstimmen usw. sich sparen. Das übernimmt die RMI-Implementierung für einen.

Da BlitzMax bringt dazu einiges mit, hat aber auch viele defizite. Daher ist eine ganze Modulsammlung entstanden.

TBufferedStream
Das ist mein früherer TCollectingStream mit einigen Verbesserungen und als Modul. Es ist ein vollwertiger Ersatz für BRL.SocketStream, der sich an der Implementierung von BlitzBasic orientiert.

Proxy
Mit dem Modul FSCOM.Proxy lassen sich Stellvertreter-Objekte erstellen. Das ist eigentlich eine Sache, die die Programmiersprache unterstützen muss, um wirklich komfortabel zu sein. Ich habe mich dabei an die Implementierung von Java orientiert: Alle Methodenaufrufe werden bei dieser Implementierung an eine Funktion weitergeleitet, in der man die Stellvertreteraufgabe übernehmen kann. Bei RMI wird dann zum Beispiel bei dem Methodenaufruf eine Netwerknachricht geschickt. Damit man nicht die Proxies umständlich selbst schreiben muss, liegt das Programm "buildproxies" bei. Das durchsucht (mit dem Lexer aus der BlitzMaxIde-Community-Edition) deinen Quelltext nach Klassen, die mit {proxy} gekennzeichnet sind und erstellt dafür eine Kind-Klasse, die ein Proxy implementiert.
Das Modul FSCOM.Proxy selbst beinhaltet nur eine Funktion, die einem die Initialisierung eines solchen Objekts abnimmt.

Serialisierung
FSCOM.Serialization beinhaltet ein Interface zum serialisieren und deserialisieren von Objekten und eine Implementierung für einfache Objekte. Die Implementierung kommt mit zyklischen Referenzen klar und kann Objekt-Bäume richtig aufbauen.
Komplexere Standard-Objekte wie Streams, Listen, Maps, Images, etc. werden in FSCOM.SerialisationEx folgen. Als Beispiel ist die Implementierung für TBank schon enthalten.
Serialisierte Objekte kann man in Dateien abspeichern oder eben über das Netzwerk verschicken und anschließend wieder deserialisieren.

RMI
Die RMI-Implementierung nutzt diese Module um die Methodenaufrufe von gewissen Objekten auf dem Client an den Server zu übertragen. Im Wesentlichen brauch man dafür 3 Dateien:

Client und Server müssen naturlich beide wissen wovon sie reden. Daher müssen die Klassen um die es beiden geht in einer Datei liegen, die beide Importen/Includen. Für die Proxy-Unterstützung muss diese Datei mit buildproxies gestartet werden: C:\...\>buildproxies C:\...\shared.bmx. Dann wird sich am Ende des Types ein Include zur Proxy-Klasse finden.
Man kann bei größeren Projekten auch die Hauptdatei mit buildproxies starten. Das Programm geht dann rekursiv allen Import und Include-Befehlen nach. Es muss immer gestartet werden, wenn sich die Methodensignatur (Name, Rückgabewert und Parameter) ändert.
BlitzMax: [AUSKLAPPEN]
'shared.bmx
Type TService {proxy}
Method GetInfo:String(id:Int) Abstract
End Type

'Diese Datei mit buildproxies starten


Der Server muss ein Service-Objekt erstellen, dessen Methoden von Clients aufgerufen werden und es bei RMI regestrieren.
BlitzMax: [AUSKLAPPEN]
'server.bmx
Framework fscom.rmi
Import "shared.bmx"

Type TServerService Extends TService
Field info:String[]

Method GetInfo:String(id:Int)
Return info[id]
End Method
End Type

Local server:TServerService = New TServerService
server.info=["ABC","123","!§$"]

RMIStartServer
RMIRegisterObjectService server, "InfoService"

Repeat
RMIPollNetwork
PollSystem
Forever


Der Client braucht ein Proxy-Objekt, dass mit dem Service verlinkt ist. RMIGetServiceObject übernimmt das fast komplett; man muss nur noch das Objekt casten.
BlitzMax: [AUSKLAPPEN]
'client.bmx
Framework fscom.rmi
Import BRL.StandardIO
Import "shared.bmx"

RMIConnect "localhost"
Local client:TService=TService(RMIGetServiceObject("InfoService","TService"))

Print client.GetInfo(0)
Print client.GetInfo(1)
Print client.GetInfo(2)

Input


Das RMI-Modul ist nochmal in Submodule aufgeteilt, sodass man auf dem Server wirklich nur den Server-Code und auf dem Client den Client-Code importen kann, oder auch den Client ohne Proxy-Unterstützung.
Dokumentation und Windows-Binaries liegen bei.

Hier ist der Download.

Ausblick

  • [Erledigt V0.2] Aktuell kann nur der Client nur vom Server Methoden aufrufen. Damit ist er bei Ereignisabfragen auf Polling angewiesen. Wünschenswert wäre wenn der Client selbst eine Art Server startet und über die Leitung rückwärts vom Server angesprochen werden kann.
  • [Erledigt V0.2]Der Service kann nicht den Client erkennen. Er muss z.B. eine Login-Funktion implementieren, die ein Cookie zurück gibt, das der Client bei allen Anfragen mitschicken muss. Ich hätte gern, dass man eine Klasse registrieren kann, sodass RMI pro Client ein Server-Service-Objekt anlegt.
  • Der TBufferedStream hat einen "endlosen" buffer implementiert. Der Arbeitsspeicher ist allerdings begrenzt. Besser wäre eine Implementierung, bei der man den Buffer wenn gewollt begrenzen kann. Ich denke da an jemand, der versucht eine 10GB-Datei zu verschicken und dann den Arbeitsspeicher sprängt
  • Bei der Serialisierung ist die Objekt-Baum "Ich erkenne ein Objekt, dass ich bereits verschickt habe"-Implementierung noch arg verbesserungswürdig
  • Ich habe bereits einen RMI-Service, der Infos über alle RMI-Services gibt implementiert. Es wäre wünschenswert, wenn der Client beim Start der Verbindung die Server-Implementierung mit der Client-Implementierung vergleicht und Alarm schlägt bei Differenzen. Aktuell ist der Ausgang unbestimmt bei nicht übereinstimmender Implementierung: Vielleicht geht's gut, vielleicht nicht.
  • UDP-Untertützung
  • [Teilweise V0.3]Threading: Die Aufrufe werden jetzt an einen Executor gegeben, der theoretisch die Aufrufe in verschiedenen Threads ausführen kann. Noch nicht ausgiebig getestet.


~EDIT: 01.06.2015~
Beispiel-Code an die aktuelle API angepasst.
  • Zuletzt bearbeitet von Silver_Knee am Mo, Jun 01, 2015 21:45, insgesamt 4-mal bearbeitet

DAK

BeitragMo, Okt 27, 2014 15:34
Antworten mit Zitat
Benutzer-Profile anzeigen
Sehr coole Sache! Hast dir ordentlich was vorgenommen. Finds aber gut, dass die ganzen coolen Java-Konzepte langsam ihren Weg nach BM finden.
Gewinner der 6. und der 68. BlitzCodeCompo

Silver_Knee

Betreff: Update V0.2

BeitragSo, Nov 02, 2014 2:39
Antworten mit Zitat
Benutzer-Profile anzeigen
So. Ich hatte die letzten Tage, bevor der Arbeitsalltag wieder anfängt, mich noch mal hingesetzt und einige der geplanten Dinge umgesetzt:

Man kann jetzt Typen-Services anlegen: Für die wird pro Client ein Objekt angelegt. Eine Initialisierungsmethode wird mit einem Client-Objekt versorgt, das es ermöglicht auf dem Client Services aufzurufen. Dazu hat der Client eine eigene Service Registry bekommen und überwacht nun selbst seinen Stream.

Der wesentliche Unterschied zwischen Client und Server ist jetzt nur noch, dass der Server das TCP-Server-Socket handling übernimmt und eben die Type-Services. Das macht ja auf dem Client keinen Sinn.

Im Chat hat ohaz noch einige interessante Punkte angebracht, die ich mal mit auf die Liste nehme:

  • Verwendung von standardisierten Aufruf-Semantiken anstelle von Marke-Eigenbau "blocking" / "nonblocking"
  • Nutzung von Proxy-Objekten, die als Parameter übergeben, oder als Rückgabewert übertragen werden.


Download
  • Zuletzt bearbeitet von Silver_Knee am Mi, März 11, 2015 6:19, insgesamt 2-mal bearbeitet

Silver_Knee

BeitragDi, März 10, 2015 1:17
Antworten mit Zitat
Benutzer-Profile anzeigen
Ich hatte bei meinem aktuellen Spieleprojekt die Gelegenheit, meine RMI-Implementierung in der Praxis zu testen.

Das hatte zu einigen Verbesserungen geführt. Die sind allerdings alle hinter den Kulissen passiert. Als einzige Änderung nach Außen hin, ist der Aufruf von RMIPollNetwork in der Hauptschleife. Der war leider zu fordernd für Timer.

Neu hinzu gekommen sind fscom.async und fscom.asycex. RMI gib jetzt die Methodenaufrufe an einen Executor, der dann zum Beispiel die Aufrufe im Threadpool abarbeitet. Ein Modul dafür plane ich auch noch. Orientieren will ich mich wie bei RMI allgemein bei Java. Hier am ExecutorService. Wird aber sicher nicht so komplex. Schon allein, weil keine Interfaces zur Verfügung stehen.

Download

Mein Testcode für die, die es interessiert:
Client (nutzt bah.maxunit)
BlitzMax: [AUSKLAPPEN]
SuperStrict

Framework bah.maxunit
Import fscom.rmi
Import "rmi-test-interface.bmx"

RMIConnect "localhost"

New TTestSuite.Run()

Type TMyTest Extends TTest
Method simple() {test}
'DebugStop
Local simple:TSimpleServerside = TSimpleServerside(RMIGetServiceObject("Single" , "TSimpleServerside") )

assertNotNull simple

Local test:GenObject = New GenObject
test.integer=4711
Local ignored:Byte = simple.set(test)

'set returns true serverside and is nonblocking
'so it should return 0 here as the return value is discarded in nonblocking
assertEqualsI 0, ignored

Local newTest:GenObject = simple.get()
assertEquals test, newTest
End Method

Method complex() {test}
Local multiple:TMultipleServerside = TMultipleServerside(RMIGetServiceObject("Multiple" , "TMultipleServerside") )

assertNotNull multiple

Local localService:TClientsideImpl = New TClientsideImpl
RMIRegisterObjectService localService, "TESTSERVICE"

Local testString:String = "123456"

multiple.CallMe("TESTSERVICE" , teststring)

'Wait for serverside code and poll network
StandardIOStream.WriteString "1"
StandardIOStream.Flush()

Local calledLast:Int=localService.calledLast
Repeat
RMIPollNetwork
Until calledLast<>localService.calledLast

'check for new value set by remote code
assertEquals "123456",localService.lastRef

'Wait for serverside code and poll network
StandardIOStream.WriteString "2"
StandardIOStream.Flush()

calledLast=localService.calledLast
Repeat
RMIPollNetwork
Until calledLast<>localService.calledLast

'check if return value caused server to call again
assertEquals "34" , localService.lastRef

'Wait for serverside code and poll network
StandardIOStream.WriteString "3"
StandardIOStream.Flush()

calledLast=localService.calledLast
Local timeout:Int=MilliSecs()+1000
Repeat
RMIPollNetwork
Until MilliSecs()>timeout

'TClientsideImpl.Call should have returned "",
'so the server should Not have called
assertEqualsI calledLast, localService.calledLast
End Method

Method recursive() {test}
Local service:TFactorial = TFactorial(RMIGetServiceObject("Factorial" , "TFactorial") )

Local clientService:TFactorialImpl=New TFactorialImpl
clientService.service=service
RMIRegisterObjectService clientService,"Factorial"

'DebugStop

assertEqualsI 120 , service.Fac(5)
End Method
End Type

Type TClientsideImpl Extends TClientside
Field lastRef:String
Field calledLast:Int

Method Call:String(ref:String)
calledLast=MilliSecs()
If lastref
lastRef = ref
Return ""
Else
lastRef = ref
Return ref[2..ref.length-2]
EndIf
End Method
End Type

Type TFactorialImpl Extends TFactorial
Field service:TFactorial

Method Fac:Int(i:Int)
If i = 0 Or i = 1
Return 1
Else
Return service.Fac(i - 1) * i
End If
End Method
End Type


Der Server
BlitzMax: [AUSKLAPPEN]
SuperStrict

Framework brl.standardio
Import fscom.rmi
Import "rmi-test-interface.bmx"

RMIStartServer
'DebugStop
RMIRegisterObjectService New TSimpleServersideImpl,"Single"
RMIRegisterTypeService "TMultipleServersideImpl","Multiple"
RMIRegisterTypeService "TFactorialImpl","Factorial"

Repeat
'DebugStop
RMIPollNetwork
Forever

Type TSimpleServersideImpl Extends TSimpleServerside
Field obj:GenObject

Method set:Byte(obj:GenObject)
Self.obj = obj
Return True
End Method

Method get:GenObject()
Return obj
End Method
End Type

Type TMultipleServersideImpl Extends TMultipleServerside
Field client:TRMIClient

Method OnConnect(client:TRMIClient)
Self.client = client
DebugLog client.ToString()
End Method

Method CallMe(localService:String , ref:String)
Local cservice:TClientside = TClientside(client.GetRemoteServiceObject(localService,"TClientside"))
Repeat
'DebugStop
Delay 1000
DebugLog "calling "+ref
ref = cservice.Call(ref)
Until ref = ""
DebugLog "end calling"
End Method
End Type

Type TFactorialImpl Extends TFactorial
Field client:TRMIClient
Field cservice:TFactorial

Method OnConnect(client:TRMIClient)
Self.client = client
cservice=TFactorial(client.GetRemoteServiceObject("Factorial","TFactorial"))
DebugLog client.ToString()
End Method

Method Fac:Int(i:Int)
If i = 0 Or i = 1
Return 1
Else
Return cservice.Fac(i - 1) * i
End If
End Method
End Type


Die gemeinsamen Interfaces. Hier muss man buildproxies drüber laufen lassen.
BlitzMax: [AUSKLAPPEN]
SuperStrict

Type TSimpleServerside {proxy}
Method set:Byte(obj:GenObject) Abstract {rminonblocking}
Method get:GenObject() Abstract {rmiblocking}
End Type
'*** PROXY ***
Include "TSimpleServerside-proxy.bmx"


Type TMultipleServerside {proxy}
Method CallMe(localService:String, ref:String) Abstract {rminonblocking}
End Type
'*** PROXY ***
Include "TMultipleServerside-proxy.bmx"


Type TClientside {proxy}
Method Call:String(ref:String) Abstract {rmiblocking}
End Type
'*** PROXY ***
Include "TClientside-proxy.bmx"


Type TFactorial {proxy}
Method Fac:Int(i:Int) Abstract
End Type
'*** PROXY ***
Include "TFactorial-proxy.bmx"



Type GenObject
Field integer:Int

Method Compare:Int(with:Object)
If GenObject(with) And GenObject(with).integer = integer
Return 0
Else
Return Super.Compare(with)
EndIf
End Method
End Type


Der größte Trick finde ich die Berechung der Fakultät von 5 bei der abwechselnd der Server und der Client die Methode des jeweils anderen aufrufen. Dabei bilden sie dann einen rekursiven Callstack über das Netzwerk.

~EDIT: 11.03.~
Fixed Download...

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group