B4J Tutorial [WebApp] Chatroom - Threads, Sessions and Server events

SS-2014-04-16_14.40.15.png


Online example: http://basic4ppc.com:51042/chat/index.html

This example demonstrates several important concepts of the web app framework.

Multiple pages

The chatroom is made of two pages. The login page and the chat page.
The WebSocket is broken when the browser navigates to a different page (or refreshes the current page). The http session allows us to pass data from one page to another. In this example the user name is stored in the login page and then retrieved in the chat page:
B4X:
'login
ws.Session.SetAttribute("name", name)
WebUtils.RedirectTo(ws, "chat.html")

'chat page
If ws.Session.HasAttribute("name") = False Then
   'name not found. Go to the login page. This will happen if the user goes to the chat page directly.
   WebUtils.RedirectTo(ws, "index.html")
Else
   name = ws.Session.GetAttribute("name")
   ws.Session.RemoveAttribute("name") 'remove the attribute
   'the user will be redirected to the login page next time.

   CallSubDelayed3(ChatShared, "NewConnection", Me, name)
   txt.RunMethod("select", Null)
End If

Threads

Each WebSocket class instance runs in a different thread (in release mode). This allows many users to be connected at the same time.
CallSubDelayed was modified in B4J v2.00 to be executed on the target object thread. This allows us to safely call subs in other modules or instances.
In this example we have a code module named ChatShared. This module holds references to all the active connections and whenever there is a new message it uses CallSubDelayed to notify each of the instances about the new message:
B4X:
'ChatShared code module
'notify each of the connected users about the new message
For Each c As Chat In connections.Values
   CallSubDelayed2(c, "NewMessage", msg)
Next

'Chat WebSocket class
Sub NewMessage(Msg As String)
   ChatTxt.RunMethod("append", Array As Object(Msg))
   ws.RunFunction("scrollDown", Null)
   ws.Flush
End Sub

The code in code modules will run in the main thread if we call it with CallSubDelayed.
In this example we call WebUtils methods directly as these methods do not rely on any shared data.

Server events

One of the great features of WebSockets is the simple support for server events. Unlike ajax based solutions, the clients don't need to poll the server with endless requests. At any given time the server can send messages to the client.
There is one extra step with server events. You must call ws.Flush at the end of the event. Flush is called internally in the case of client events.
 

Attachments

  • Chatroom.zip
    6.8 KB · Views: 1,168
Last edited:

vfafou

Active Member
Licensed User
Hello!
Is it possible to place in a queue the Events called from clients?
I have a database congestion problem when many concurrent clients are trying to login, because I do some queries in order to return to the clients their current status.
Thank you in advance!
 

udg

Expert
Licensed User
Hi,
will it suffice to have a B4A app with some code in like
B4X:
Web.LoadUrl("http://192.168.11.27:51042/index.html")
to access the b4J server and have a chat?
Or should I go the websocket way on the B4A side too?

Thanks
 

udg

Expert
Licensed User
Thanks, Erel.
So, to have a dedicated B4A app, I should initiate a ws channel client-side and have the server dispatch the received messages to all the active clients. Am I right?
 

udg

Expert
Licensed User
Note that it might be simpler to use the custom push framework instead.
I fully agree. I didn't mention it here because this thread is about the chatroom example.. anyway thank you, Erel.
 

LucaMs

Expert
Licensed User
Please, do not shoot me :D
I'm recovering my project developed over a year ago and I would prefer to clarify my mind (it's hard to do :p).

Threads

Each WebSocket class instance runs in a different thread (in release mode). This allows many users to be connected at the same time.
CallSubDelayed was modified in B4J v2.00 to be executed on the target object thread. This allows us to safely call subs in other modules or instances.
In this example we have a code module named ChatShared. This module holds references to all the active connections and whenever there is a new message it uses CallSubDelayed to notify each of the instances about the new message:
B4X:
'ChatShared code module
'notify each of the connected users about the new message
For Each c As Chat In connections.Values
   CallSubDelayed2(c, "NewMessage", msg)
Next

'Chat WebSocket class
Sub NewMessage(Msg As String)
   ChatTxt.RunMethod("append", Array As Object(Msg))
   ws.RunFunction("scrollDown", Null)
   ws.Flush
End Sub
The code in code modules will run in the main thread if we call it with CallSubDelayed.
In this example we call WebUtils methods directly as these methods do not rely on any shared data.

[Example to notify each "client"...]
"CallSubDelayed was modified in B4J v2.00 to be executed on the target object thread. This allows us to safely call subs in other modules or instances."

then...

[Calls to WebUtils - code module]
"The code in code modules will run in the main thread if we call it with CallSubDelayed"

I'm tired but... is this a contradiction?

-----

More difficult (as they say at Circus :D):

4.Why you use CallSubDelayed instead of call it directly for chat class? Are they the same ?
B4X:
CallSubDelayed3(ChatShared, "NewConnection", Me, name)
ChatShared.NewConnection(Me,name)

4. This is very important point. See the "threads" section in the first post. It is only relevant to server (or WebApps) projects. Each class instance runs in its own thread. If you call ChatShared.NewConnection then this sub will be executed in the class instance thread.
If you call CallSubDelayed then the target thread will be used to execute the code. In the case of code modules it is always the main thread.

I don't know if my problem is that I'm become too old (< 2 weeks you should wish me happy birthday... and donate 10€ x >75,000 members... uhm... :D) or it's my english or I have misunderstood some terms, in particular "target".

"If you call ChatShared.NewConnection then this sub will be executed in the class instance thread"

Do you meant "...will be executed in ChatShared code module, then in the Main thread"? Since I understand the above:
"If you call CallSubDelayed then the target thread will be used to execute the code"
as "using CallSubDelayed, the sub inside the target class instance will be executed there, in the target, which is Chat".

Uhm... I need to draw, like children...

upload_2015-9-29_19-52-35.png



upload_2015-9-29_20-11-52.png
 
Last edited:

Roycefer

Well-Known Member
Licensed User
Place the following code in your various code modules and classes and in your Main module. Then call it using the various methods (CallSub, CallSubDelayed, direct call from within class, direct call from outside class, telepathy, etc...) and report back on the results.
B4X:
Public Sub SomeSub
    Dim jo as JavaObject = Me
    Log("Current Thread Name: " & jo.RunMethod("getCurrentThreadName", Null))
End Sub

#If JAVA
public String getCurrentThreadName()
{
    return Thread.currentThread().getName();
}
#End If
 

Roycefer

Well-Known Member
Licensed User
Yes, that code will work in B4A and B4J. You might have to use jo.InitializeContext instead of jo = Me if you put that code in an Activity module but the thread-related stuff will work unchanged.
 

LucaMs

Expert
Licensed User
I started the server in release mode:
*** "direct" call ***
Calling [Chat instance].SomeSub from ChatShared (code module) I get "main" as current thread.

*** CallSubDelayed call ***
Current Thread Name: pool-1-thread-3

*** CallSub call ***
main
 
Last edited:

Roycefer

Well-Known Member
Licensed User
Here are some cleaner versions that should work cross-platform and from within code modules:
B4X:
Sub SomeSubJO
    Dim t As JavaObject
    Log("Current Thread: " & t.InitializeStatic("java.lang.Thread").RunMethodJO("currentThread", Null).RunMethod("getName", Null))
End Sub

Sub SomeSubReflection
    Dim r As Reflector
    r.Target = r.RunStaticMethod("java.lang.Thread", "currentThread", Null, Null)
    Log("Current Thread: " & r.RunMethod("getName"))
End Sub

To kill a Java process, you can call ExitApplication from within the process or you can kill java.exe in your TaskManager. If you have multiple java processes running and you don't know which one to kill, use the jAWTRobot library to Log a process' process ID and then use that PID to identify which instance of java.exe to kill in TaskManager.

Your results seem to square with what we suspected. CallSubDelayed causes the method to run in the Thread of its owning Chat instance. Calling it directly causes it to run in the calling Thread. CallSub also causes it run in the main Thread. But I wonder if you use CallSub from a non-main Thread, would SomeSub still run in the main Thread or would it run in that calling Thread? In other words, is CallSub just sending SomeSub to the back of the main Thread's message queue, regardless of from within which Thread it is used?

Anyhow, I hope these experiments are telling you what you need to know.
 

Erel

Administrator
Staff member
Licensed User
There is a difference between the behavior of handler classes and static code modules.

When you use CallSubDelayed to call a handler class then the sub will run on the thread that owns this specific class instance. When you use CallSubDelayed to call a sub in a static code module it will run on the main thread.

Using CallSub is the same as calling the sub directly. It will run on the same thread.

Note that you can use Server.CurrentThreadIndex to get the thread number.
 
Top