Italian [Risolto][Server] Far dialogare Server e app Client - Errore

Elric

Well-Known Member
Licensed User
Ciao a tutti!

Ho riscritto l'esempio che c'è nel tutorial come base per il mio studio e la mia personalizzazione.


PROBLEMA 1
Ho questo errore quando dal client mando dei dati (un integer) al server.

B4X:
Emulated network latency: 100ms
My ip is: 192.168.1.15
New Connection
State: Connected
My intScreenColor is 0
Error occurred on line: 70 (Main)
java.lang.RuntimeException: java.lang.ClassNotFoundException: b4j.example.b4xmainpage$_mymessage
    at anywheresoftware.b4a.randomaccessfile.B4XSerializator.readType(B4XSerializator.java:314)
    at anywheresoftware.b4a.randomaccessfile.B4XSerializator.readObject(B4XSerializator.java:374)
    at anywheresoftware.b4a.randomaccessfile.B4XSerializator.ReadObject(B4XSerializator.java:129)
    at anywheresoftware.b4a.randomaccessfile.B4XSerializator.ConvertBytesToObject(B4XSerializator.java:99)
    at b4j.example.main._astream_newdata(main.java:272)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:629)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:237)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:167)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:111)
    at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:100)
    at anywheresoftware.b4a.BA$2.run(BA.java:250)
    at anywheresoftware.b4a.keywords.SimpleMessageLoop.runMessageLoop(SimpleMessageLoop.java:47)
    at anywheresoftware.b4a.StandardBA.startMessageLoop(StandardBA.java:43)
    at anywheresoftware.b4a.shell.ShellBA.startMessageLoop(ShellBA.java:121)
    at anywheresoftware.b4a.keywords.Common.StartMessageLoop(Common.java:180)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:309)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:167)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:111)
    at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:100)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:98)
    at b4j.example.main.main(main.java:29)
Caused by: java.lang.ClassNotFoundException: b4j.example.b4xmainpage$_mymessage
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:340)
    at anywheresoftware.b4a.randomaccessfile.RandomAccessFile.readTypeClass(RandomAccessFile.java:546)
    at anywheresoftware.b4a.randomaccessfile.B4XSerializator.readType(B4XSerializator.java:291)
    ... 32 more

Per il client sono partito da un nuovo progetto B4XPages

Per il server sono partito da un nuovo progetto server in B4J che ha come template "Guess my number" (motivo per cui si trova anche il websocket; l'idea è di avere un solo server che abbia come client sia un'app dedicata sia per mezzo di un browser, ma quello sarà un passaggio successivo).

Cosa ho sbagliato?

Per il server ho fatto direttamente lo zip della cartella non vedendo alcun link interno all'IDE per la creazione dello zip.

PROBLEMA 2
Se mi è chiaro come inviare un dato al server (una volta risolto l'errore) come faccio a mandare il comando di reset?
 

Attachments

  • SimpleClientB4XPages-240111.zip
    16.8 KB · Views: 108
  • SimpleServerB4J-240111.zip
    105.3 KB · Views: 100

Elric

Well-Known Member
Licensed User
PROBLEMA 1
...
Cosa ho sbagliato?
L'esempio del client nel tutorial è in B4J ma una versione precedente alle B4XPages.

Nel tutorial Erel dice chiaramente che il Type va dichiarato nel Main ma io, sapendo che con le B4XPages non devo toccare il Main ma lavorare solo su B4XMainPage, non l'ho fatto.

Dopo vari tentativi e revisione del tutorial mi son detto che tentare al massimo mi avrebbe dato errore e, quindi, ho dichiarato il type nel Main del progetto del client in B4XPages e ha magicamente funzionato!

Ora affronto il Problema 2 ma se qualcuno vuol dare suggerimenti sono bene accetti.
 

LucaMs

Expert
Licensed User
Longtime User
Nel tutorial Erel dice chiaramente che il Type va dichiarato nel Main ma io, sapendo che con le B4XPages non devo toccare il Main ma lavorare solo su B4XMainPage, non l'ho fatto.

Dopo vari tentativi e revisione del tutorial mi son detto che tentare al massimo mi avrebbe dato errore e, quindi, ho dichiarato il type nel Main del progetto del client in B4XPages e ha magicamente funzionato!
Se non ricordo male, dovrebbe funzionare anche se lo dichiari nella B4XMainPage. Comunque, è vero che sia meglio non modificare il Main, ma un'eccezione come questa può andare bene.

[Il motivo per cui la dichiarazione va fatta lì è dovuto al fatto che altrimenti non sarebbe possibile la serializzazione degli oggetti di quel tipo]
 

LucaMs

Expert
Licensed User
Longtime User
Ma... al link che hai riportato nel primo post, ci sono più progetti, client e server, ma c'è anche il suggerimento di usare un'altra versione, B4XPages, il relativo link che porta ad un thread in cui il progetto è uno soltanto, per cui suppongo che quello possa svolgere le funzioni di server o di client, senza dover avere due progetti ben distinti.

Quindi, cosa stai usando? I vecchi file che hai trasformato in progetti B4XPages?


Ho riletto il primo post (la prima volta lo avevo fatto stamattina) e quindi capito; o meglio, quasi, perché anche per il server avresti potuto sì scrivere un progetto a parte, ma sempre partendo da quello di Erel nel thread [B4X] [B4XPages] Network + AsyncStreams + B4XSerializator se, come suppongo, il progetto allegato a quello possa svolgere entrambe le funzioni, client e/o server.
 
Last edited:

Elric

Well-Known Member
Licensed User
Vero, sarei potuto partire da quello di Erel nel thread [B4X] [B4XPages] Network + AsyncStreams + B4XSerializator, la cui app cross platform può agire sia da server sia da client ma ero partito con le webapp e ho lavorato su un progetto B4J server vergine (anche perché penso a un server che non ha bisogno di interfaccia grafica).

Però sono sempre fermo al Problema 2, ossia usare il client per dire al server di far partire una sua subroutine. ?
 

LucaMs

Expert
Licensed User
Longtime User
Però sono sempre fermo al Problema 2, ossia usare il client per dire al server di far partire una sua subroutine. ?
PROBLEMA 2
Se mi è chiaro come inviare un dato al server (una volta risolto l'errore) come faccio a mandare il comando di reset?
Se sai come inviare un dato dal client al server, che problema c'è? Gli mandi un dato stringa che abbia un formato "NomeSubDaEseguire,Parametro1/Parametro2...".
Meglio ancora, visto che da ciò che hai scritto suppongo che tu possa serializzare un oggetto di tipo custom type ed inviarlo, crei un custom type tComando.
 

Elric

Well-Known Member
Licensed User
Ci avevo pensato... anzi, avevo pensato ad una o più map o a map multidimensionali.

Ma immagino che questo comporti che per un progetto anche solo un pelino più complesso di "Guess my number", lato server dovrei implementare una sfilza di "if" o di "Select Case" o sbaglio?

Stavo anche pensando a più istanze AsyncStreams (Private astream1 As AsyncStreams, Private astream2 As AsyncStreams etc.) ma non ho ancora provato.
 

LucaMs

Expert
Licensed User
Longtime User
Ma immagino che questo comporti che per un progetto anche solo un pelino più complesso di "Guess my number", lato server dovrei implementare una sfilza di "if" o di "Select Case" o sbaglio?
No. Quando avrai "decodificato" il comando arrivato dal client, avrai una variabile col nome della Sub (NomeSub) da chiamare e una List o Map di parametri (Parametri).
A quel punto userai CallSubDelayed2 per far eseguire la Sub del server:

B4X:
CallSubDelayed2(Me, NomeSub, Parametri)

[Per ora non ti complico le cose e ti faccio usare Me, il che significa che le Sub da eseguire dovranno stare nello stesso modulo che riceve ed elabora il comando]
 

Elric

Well-Known Member
Licensed User
Mi stai "scrivendo" di fare qualcosa di questo tipo?

Nel client e nel server dichiarare:
B4X:
Type tpCallSub(tpNameSub as String, tpParameter1 as String, tpParameter2 as Long)

Nel server avere qualcosa così:
B4X:
Private Sub Astream_NewData (Buffer() As Byte)
    Dim cs As tpCallSub
    cs = ser.ConvertBytesToObject(Buffer)
    CallSubDelayed2(Me, cs.tpNameSub, tpParameter1, tpParameter2) ' Perché qui CallSubDelayed2 e subito dopo CallSub(Me, "StateChanged")?
    CallSub(Me, "StateChanged")
End Sub

Private Sub MessageFromClient(myString as String, myLong as Long)
    Log($"The client says ${myString} is ${myLong}"$)
End Sub

Private Sub ClientGivesTheNumbers(myString as String, myLong as Long)
    Log($"${myString} + ${myLong} * ${myString} - ${myString} = ${myLong}"$)
End Sub

Quindi, il client potrebbe avere:
B4X:
Private Sub Button1_Click
    Dim cs As tpCallSub
    cs.Initialize
    cs.tpNameSub =$"MessageFromClient"$
    tpParameter1 = $"I've found € "$
    tpParameter2 = 1000000

    Dim myByte() As Byte
    myByte = ser.ConvertObjectToBytes(cs)
    SendData(myByte)
End Sub

Private Sub Button2_Click
    Dim cs As tpCallSub
    cs.Initialize
    cs.tpNameSub =$"MessageFromClient"$
    tpParameter1 = $"7"$
    tpParameter2 = 1000000

    Dim myByte() As Byte
    myByte = ser.ConvertObjectToBytes(cs)
    SendData(myByte)
End Sub

Private Sub SendData(data() As Byte)
    If ynIsConnected Then astream.Write(data)
End Sub
 

LucaMs

Expert
Licensed User
Longtime User
Sì, più o meno.

Il type lo farei diversamente:
Type tpCallSub(tpNameSub as String, tpParameter1 as String, tpParameter2 as Long)
Se lo fai così, potrai avere solo due parametri. Se, invece:
B4X:
Type tpCallSub(tpNameSub as String, tpParameters As Map)
poi potrai chiamare sia, ad esempio:
B4X:
CallSubDelayed2(Me, cs.tpNameSub, cs.tpParameters) ' hai dimenticato cs.tp...
se la sub ricevesse una Map come parametro, sia:
B4X:
CallSubDelayed2(Me, cs.tpNameSub, cs.tpParameters.Get("LaChiaveCheVuoi"))
o ancora:
B4X:
CallSubDelayed3(Me, cs.tpNameSub, cs.tpParameters.Get("LaChiaveCheVuoi"), cs.tpParameters.Get("AltraChiave"))
se le Sub da chiamare dovranno ricevere uno o due parametri.
 

Elric

Well-Known Member
Licensed User
Wow! Ora sono pronto a conquistare il mondo!
album-di-default-549419.jpg


Grazie! ☕
 

Elric

Well-Known Member
Licensed User
[Per ora non ti complico le cose e ti faccio usare Me, il che significa che le Sub da eseguire dovranno stare nello stesso modulo che riceve ed elabora il comando]
Ready Player 1!

Livello 2:
Loading...
 
Last edited:

Elric

Well-Known Member
Licensed User
Ready Player 1!

Livello 2:
Loading...
B4X:
    Type tpCallSub(strNameModule as String, strNameSub as String, strParameters As Map)
e
B4X:
    Dim myObj As Object
    myObj = cs.strNameModule
    CallSub2(myObj, cs.strNameSub, cs.mapParameters)
K1.jpg
 

Elric

Well-Known Member
Licensed User
In B4A pare che non possa neanche richiamare un modulo semplice ma devo necessariamente richiamare una classe!

Ma è legale questa cosa?

Battute a parte, @LucaMs:
[Per ora non ti complico le cose e ti faccio usare Me, il che significa che le Sub da eseguire dovranno stare nello stesso modulo che riceve ed elabora il comando]
come dovrei fare a richiamare modulo/classe dinamicamente e relative sub?
 

LucaMs

Expert
Licensed User
Longtime User
Questa soluzione pare funzionare solo in B4J ma non in B4A...
In B4A pare che non possa neanche richiamare un modulo semplice ma devo necessariamente richiamare una classe!
In B4J i moduli di codice sono simili alle classi, dal punto di vista funzionale, in B4A no.

Questa soluzione pare funzionare solo in B4J ma non in B4A...
Veramente quella soluzione non dovrebbe funzionare.

La soluzione migliore sarebbe NON usare moduli di codice in B4A (e direi nemmeno in B4J e B4i), sostituirli con classi (ovviamente dovrai avere UNA variabile-oggetto di quel tipo di classe, a disposizione di tutto il progetto, come lo sono i moduli di codice).

Altrimenti potresti fare in questo modo:
B4X:
Type tpCallSub(strNameModule as String, IsCodeModule As Boolean, strNameSub as String, mapParameters As Map)
'
'
'
objCommand As tpCallSub = [... from server...]

If objCommand.IsCodeModule Then

    Select Case objCommand.strNameModule

        Case "ModuleUno"
            Select Case objCommand.strNameSub
                 Case "NomeSubUno"
                     ModuleUno.NomeSubUno(objCommand.mapParameters)
                 Case "AltraSub"
                     ModuleUno.AltraSub(objCommand.mapParameters)
             End Select

        Case "ModuleDue"
            Select Case objCommand.strNameSub
                 Case "QualcheSub"
                     ModuleDue.QualcheSub(objCommand.mapParameters)
                 Case "Ancora"
                     ModuleDue.Ancora(objCommand.mapParameters)
             End Select

    End Select

Else ' Class

   ' Qui userai CallSub2 per oggetti (classi)

     Dim objTarget As Object

     Select objCommand.strNameModule
         Case "B4XMainPage"
              objTarget = B4XPages.MainPage
         Case "Articoli"
               objTarget = B4XPages.MainPage.pagArticoli ' Se hai un oggetto-pagina pagArticoli dichiarato Public nella MainPage
         Case "AltroOggetto"
               objTarget = B4XPages.MainPage.AltroOggetto ' come sopra ma per una "normale" classe.
         Case "OggettoCheSostituisceIlTuoCodeModule" ' <--- se deciderai di "trasformare" un modulo di codice in uno di classe, così non ti servirà tutta la parte precedente.
               objTarget = B4XPages.MainPage.OggettoUtilities ' come sopra ma per una "normale" classe.
     End Select

     CallSub2(objTarget, objCommand.strNameSub, objCommand.mapParameters)

End If
Come vedi, è molto meno dinamico e più complesso; conviene la prima soluzione: non usare moduli di codice, solo classi.

[Ovviamente codice non testato, scritto qui direttamente]
 
Last edited:

Elric

Well-Known Member
Licensed User
Grazie. Ho provato anche con le classi (da ora in poi solo classi!) ma non riesco a farlo funzionare. O meglio: se dal client arriva la stringa "clsUnaClasse", con che comando richiamo la classe in base alla variabile string che arriva dal client?
 
Top