B4A Library WebSocket Client Library

Status
Not open for further replies.
This library allows you to create WebSocket connections with servers that support WebSockets.
It is based on this open source project: http://autobahn.ws/android/#

The main benefit of this library is that you can use it to communicate with B4J WebApp solutions.

The attached example includes a class named WebSocketHandler. With this class you can send events to the server and the server can send events to the device.

upload_2014-4-23_15-54-47.png


The server code is very simple:
B4X:
Sub Class_Globals
   Private ws As WebSocket
   Private timer1 As Timer
End Sub

Public Sub Initialize

End Sub

Private Sub WebSocket_Connected (WebSocket1 As WebSocket)
   ws = WebSocket1
   timer1.Initialize("timer1", 1000)
   timer1.Enabled = True
End Sub

Sub Timer1_Tick
'This method will raise the event on the device
   ws.RunFunction("ServerTime", Array As Object(DateTime.Time(DateTime.Now)))
   ws.Flush
End Sub

'event from the device
Sub Device_Message(Params As Map)
   Log("Device message: " & Params.Get("message"))
End Sub

Private Sub WebSocket_Disconnected
   timer1.Enabled = False
   Log("disconnected")
End Sub

Sending events to the server

WebSocketHandler.SendEventToServer takes the event name (not prefix in this case) and the data. The data is a Map that is converted to a JSON string. Note that the event name must include an underscore.

B4X:
Sub btnSend_Click
   Dim data As Map
   data.Initialize
   data.Put("message", EditText1.Text)
   wsh.SendEventToServer("Device_Message", data)
End Sub

Receiving events from the server

B4X:
Sub wsh_ServerTime(Params As List)
   'example of a server push message
   lblServerTime.Text = "Server Time: " & Params.Get(0)
End Sub

The sub name in this case is made of the prefix set in the Initialize method and the event name as received from the server. There should be a single parameter which is a List.

How to run this example?

You need the latest version of B4J: http://www.b4x.com/android/b4j.html
Download and run the server example.
You will need to open the firewall port (51042).

Set the address in the client code to match the server address.

Updates

v2.11: New Headers Map that can be used to add headers to the upgrade request (https://www.b4x.com/android/forum/threads/websocketclient-authorization.116574/#post-729213)
v2.10: New BinaryMessage event and SendBinary method.
v2.01: Fixes an issue with ssl disconnections that can throw the network on main thread exception.

v2.00: Based on the latest version of autobahn-java: https://github.com/crossbario/autobahn-java
Adds support for SSL connections (wss://) including using custom trust managers (mainly to accept self signed certificates).
SSL support is problematic on devices prior to Android 5. To support Android 4+ devices you need to update the security provider: https://www.b4x.com/android/forum/threads/ssl-websocket-client.88472/page-2#post-560044
 

Attachments

  • ServerExample.zip
    997 bytes · Views: 3,596
  • ClientExample.zip
    8.3 KB · Views: 4,061
  • WebSocket.zip
    39.6 KB · Views: 2,304
Last edited:

woniol

Active Member
Licensed User
Longtime User
Thanks Erel,
I was waiting for it.

Will it work with b4j?

I have problems running in Release mode. I doesn't connect.
Works fine in Debug mode.
 
Last edited:

woniol

Active Member
Licensed User
Longtime User
When I run it i Debug mode and the server side is not running I get this in log while trying to connect:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4876)
at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:980)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:4127)
at android.view.View.invalidate(View.java:10328)
at android.view.View.invalidate(View.java:10283)
at android.widget.TextView.checkForRelayout(TextView.java:6601)
at android.widget.TextView.setText(TextView.java:3727)
at android.widget.TextView.setText(TextView.java:3585)
at android.widget.TextView.setText(TextView.java:3560)
at anywheresoftware.b4a.objects.TextViewWrapper.setText(TextViewWrapper.java:39)
at b4a.example.main._updatestatus(main.java:492)
at b4a.example.main._wsh_closed(main.java:519)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:174)
at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:858)
at anywheresoftware.b4a.keywords.Common.CallSubNew2(Common.java:815)
at b4a.example.websockethandler._ws_closed(websockethandler.java:166)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:174)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:158)
at anywheresoftware.b4a.objects.WebSocketWrapper$1.onClose(WebSocketWrapper.java:60)
at de.tavendo.autobahn.WebSocketConnection.onClose(WebSocketConnection.java:366)
at de.tavendo.autobahn.WebSocketConnection.access$2(WebSocketConnection.java:352)
at de.tavendo.autobahn.WebSocketConnection$WebSocketConnector.run(WebSocketConnection.java:94)
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at android.app.Dialog.<init>(Dialog.java:107)
at android.app.AlertDialog.<init>(AlertDialog.java:114)
at android.app.AlertDialog.<init>(AlertDialog.java:98)
at android.app.ProgressDialog.<init>(ProgressDialog.java:77)
at anywheresoftware.b4a.debug.Debug.wait(Debug.java:195)
at anywheresoftware.b4a.debug.Debug.reachBP(Debug.java:260)
at anywheresoftware.b4a.debug.Debug.ErrorCaught(Debug.java:145)
at b4a.example.main._updatestatus(main.java:505)
at b4a.example.main._wsh_closed(main.java:519)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:174)
at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:858)
at anywheresoftware.b4a.keywords.Common.CallSubNew2(Common.java:815)
at b4a.example.websockethandler._ws_closed(websockethandler.java:166)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:174)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:158)
at anywheresoftware.b4a.objects.WebSocketWrapper$1.onClose(WebSocketWrapper.java:60)
at de.tavendo.autobahn.WebSocketConnection.onClose(WebSocketConnection.java:366)
at de.tavendo.autobahn.WebSocketConnection.access$2(WebSocketConnection.java:352)
at de.tavendo.autobahn.WebSocketConnection$WebSocketConnector.run(WebSocketConnection.java:94)
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
java.lang.RuntimeException: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:871)
at anywheresoftware.b4a.keywords.Common.CallSubNew2(Common.java:815)
at b4a.example.websockethandler._ws_closed(websockethandler.java:166)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:174)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:158)
at anywheresoftware.b4a.objects.WebSocketWrapper$1.onClose(WebSocketWrapper.java:60)
at de.tavendo.autobahn.WebSocketConnection.onClose(WebSocketConnection.java:366)
at de.tavendo.autobahn.WebSocketConnection.access$2(WebSocketConnection.java:352)
at de.tavendo.autobahn.WebSocketConnection$WebSocketConnector.run(WebSocketConnection.java:94)
Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at android.app.Dialog.<init>(Dialog.java:107)
at android.app.AlertDialog.<init>(AlertDialog.java:114)
at android.app.AlertDialog$Builder.create(AlertDialog.java:913)
at anywheresoftware.b4a.BA.ShowErrorMsgbox(BA.java:221)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:201)
at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:858)
... 10 more
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
 

woniol

Active Member
Licensed User
Longtime User
Ok,
After adding
B4X:
AddPermission(android.permission.INTERNET)
to the Manifest it works fine in Release
 

MaFu

Well-Known Member
Licensed User
Longtime User
I tried the sample app: compiling with Debug (legacy) settings and running in emulator (Nexus 4 AVD).
But on connect the following error log occurs:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.


at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4609)
at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:867)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:4066)
at android.view.View.invalidate(View.java:10250)
at android.view.View.invalidate(View.java:10205)
at android.widget.TextView.checkForRelayout(TextView.java:6288)
at android.widget.TextView.setText(TextView.java:3547)
at android.widget.TextView.setText(TextView.java:3405)
at android.widget.TextView.setText(TextView.java:3380)
at anywheresoftware.b4a.objects.TextViewWrapper.setText(TextViewWrapper.java:39)
at b4a.example.main._updatestatus(main.java:489)
at b4a.example.main._wsh_closed(main.java:516)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)


at anywheresoftware.b4a.BA.raiseEvent2(BA.java:174)
at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:858)
at anywheresoftware.b4a.keywords.Common.CallSubNew2(Common.java:815)
at b4a.example.websockethandler._ws_closed(websockethandler.java:166)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:174)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:158)
at anywheresoftware.b4a.objects.WebSocketWrapper$1.onClose(WebSocketWrapper.java:60)
at de.tavendo.autobahn.WebSocketConnection.onClose(WebSocketConnection.java:366)
at de.tavendo.autobahn.WebSocketConnection.access$2(WebSocketConnection.java:352)
at de.tavendo.autobahn.WebSocketConnection$WebSocketConnector.run(WebSocketConnection.java:94)
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.


at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4609)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:835)
at android.view.View.requestLayout(View.java:15129)
at android.view.View.requestLayout(View.java:15129)
at android.view.View.requestLayout(View.java:15129)
at android.view.View.requestLayout(View.java:15129)
at android.view.View.requestLayout(View.java:15129)
at android.view.View.requestLayout(View.java:15129)
at android.view.View.requestLayout(View.java:15129)
at android.view.View.requestLayout(View.java:15129)
at android.view.View.requestLayout(View.java:15129)
at android.widget.TextView.checkForRelayout(TextView.java:6303)
at android.widget.TextView.setText(TextView.java:3547)
at android.widget.TextView.setText(TextView.java:3405)
at android.widget.TextView.setText(TextView.java:3380)


at android.app.ProgressDialog.setMessage(ProgressDialog.java:316)
at anywheresoftware.b4a.debug.Debug.wait(Debug.java:208)
at anywheresoftware.b4a.debug.Debug.reachBP(Debug.java:260)
at anywheresoftware.b4a.debug.Debug.ErrorCaught(Debug.java:145)
at b4a.example.main._updatestatus(main.java:502)
at b4a.example.main._wsh_closed(main.java:516)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:174)
at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:858)
at anywheresoftware.b4a.keywords.Common.CallSubNew2(Common.java:815)
at b4a.example.websockethandler._ws_closed(websockethandler.java:166)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:174)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:158)
at anywheresoftware.b4a.objects.WebSocketWrapper$1.onClose(WebSocketWrapper.java:60)
at de.tavendo.autobahn.WebSocketConnection.onClose(WebSocketConnection.java:366)
at de.tavendo.autobahn.WebSocketConnection.access$2(WebSocketConnection.java:352)
at de.tavendo.autobahn.WebSocketConnection$WebSocketConnector.run(WebSocketConnection.java:94)
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.


java.lang.RuntimeException: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()


at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:871)
at anywheresoftware.b4a.keywords.Common.CallSubNew2(Common.java:815)
at b4a.example.websockethandler._ws_closed(websockethandler.java:166)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:174)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:158)
at anywheresoftware.b4a.objects.WebSocketWrapper$1.onClose(WebSocketWrapper.java:60)
at de.tavendo.autobahn.WebSocketConnection.onClose(WebSocketConnection.java:366)
at de.tavendo.autobahn.WebSocketConnection.access$2(WebSocketConnection.java:352)
at de.tavendo.autobahn.WebSocketConnection$WebSocketConnector.run(WebSocketConnection.java:94)
Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at android.app.Dialog.<init>(Dialog.java:107)
at android.app.AlertDialog.<init>(AlertDialog.java:114)
at android.app.AlertDialog$Builder.create(AlertDialog.java:913)
at anywheresoftware.b4a.BA.ShowErrorMsgbox(BA.java:221)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:201)
at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:858)
... 10 more
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
 

bluedude

Well-Known Member
Licensed User
Longtime User
I guess the biggest benefit would actually be that it would run with things like Socket.io, PubNub etc. Not tested it, maybe it works.
 

wl

Well-Known Member
Licensed User
Longtime User
Hi Erel,

Is there some kind of heartbeat, so that when the connection drops (tend to occur every once and a while on a mobile device :) this can be detected and a new connection initiated ?

Thanks
 

Douglas Farias

Expert
Licensed User
Longtime User
erel i have this error when i try load the sample server
Program started.
2014-07-16 08:38:52.683:INFO::main: Logging initialized @561ms
2014-07-16 08:38:52.759:INFO:eek:ejs.Server:main: jetty-9.1.z-SNAPSHOT
2014-07-16 08:38:52.778:WARN:eek:ejh.MimeTypes:main: java.util.MissingResourceException: Can't find bundle for base name org/eclipse/jetty/http/encoding, locale pt_BR
2014-07-16 08:38:52.802:INFO:eek:ejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@c42916{/,file:/C:/Users/Douglas/Desktop/B4A-WebSocket/Objects/www,AVAILABLE}
2014-07-16 08:38:52.804:INFO:eek:ejs.AbstractNCSARequestLog:main: Opened C:\Users\Douglas\Desktop\B4A-WebSocket\Objects\logs\b4j-2014_07_16.request.log
2014-07-16 08:38:52.821:INFO:eek:ejs.ServerConnector:main: Started ServerConnector@16995df{HTTP/1.1}{0.0.0.0:51042}
2014-07-16 08:38:52.822:INFO:eek:ejs.Server:main: Started @721ms
Emulated network latency: 100ms
 

LucaMs

Expert
Licensed User
Longtime User
I get a strange behavior.

The server (b4j) runs some routines on the client (device) in the right way.
For example:
B4X:
ws.RunFunction("RaisedByServer_NewNickOK", Array As Object(Nick))
ws.Flush


In the case in which the routine is not present in the client code, I rightly get an error.

Only in one of these calls, the routine of the client does not run and I do not get error messages.

The method of the call is identical and also the modules are the same!

SERVER - wshPlayer
B4X:
Private Sub RunOnClient_NewNickOK(Nick As String)  '  <---- THIS IS OK
    ws.RunFunction("RaisedByServer_NewNickOK", Array As Object(Nick))
    ws.Flush
End Sub

Public Sub RunOnClient_PlayerJoinsRoom(msg As String)
Log("RunOnClient_PlayerJoinsRoom") ' <--- I get this log
    ws.RunFunction("RaisedByServer_PlayerJoinsRoom", Array As Object(msg))
    ws.Flush
End Sub


CLIENT - PlayerHandler
B4X:
Public Sub RaisedByServer_NewNickOK(Params As List)  ' <---- THIS IS OK, RUNS
    Private Nick As String = Params.Get(0)
    CallSubDelayed2(CallBack, EventName & "_NewNickOK", Nick)
End Sub

Public Sub RaisedByServer_PlayerJoinsRoom(params As List)
Log("RaisedByServer_PlayerJoinsRoom") ' <--- I DON'T get this log
End Sub


I realize that this explanation is poor; on the other hand I can not post both projects.


I hope this has happened to you :)
 
Status
Not open for further replies.
Top