Zinc WebSocket usage

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Zinc WebSocket usage

Erik Stel
Sven or anyone else with experience with ZnWebSockets, a question regarding
ZnWebSocket usage.

I have the following code which sort of resembles my current usage of
ZnWebSockets. ZnWebSockets are used for both sending and receiving messages
in a random fashion (ie, no request-response like pattern). Because the
ZnWebSocket operates synchronous I fork 'handlers' which process the
incoming messages. When executing the code below, the client closes the
connection but will 'recognise' this itself only after a timeout (around 30
seconds by default). Is there a better way to use ZnWebSockets which does
not incur this delay?

My current workaround (see comment at the bottom in code below) is to send
an explicit "close" ZnWebSocketFrame instead of sending #close to the
ZnWebSocket. This will have the client close 'directly', after which I can
close the ZnWebSocket stream explicitly. The implementation of #close for
ZnWebSocket is to perform both: send close frame and close stream. The later
seems to 'stall' the receive message until timing out. The synchronous
handling of incoming messages might be causing this. But could not directly
find why closing the stream resulted in different behaviour than when only
sending the close frame. (Hope this explanation is helpful ;-).






--
Sent from: http://forum.world.st/Pharo-Smalltalk-Users-f1310670.html

Reply | Threaded
Open this post in threaded view
|

Re: Zinc WebSocket usage

Sven Van Caekenberghe-2
Hi Erik,

I am afraid I do not fully understand what your problem is.

Also, I see no code.

Server side, a web socket connection is automatically kept open (it has to be).

Client side, you should study #runWith: and how it is used.

Recently I added sending #ping keep alive message to the runWith: loop (as browsers do the same).

HTH,

Sven

> On 29 May 2020, at 12:39, Erik Stel <[hidden email]> wrote:
>
> Sven or anyone else with experience with ZnWebSockets, a question regarding
> ZnWebSocket usage.
>
> I have the following code which sort of resembles my current usage of
> ZnWebSockets. ZnWebSockets are used for both sending and receiving messages
> in a random fashion (ie, no request-response like pattern). Because the
> ZnWebSocket operates synchronous I fork 'handlers' which process the
> incoming messages. When executing the code below, the client closes the
> connection but will 'recognise' this itself only after a timeout (around 30
> seconds by default). Is there a better way to use ZnWebSockets which does
> not incur this delay?
>
> My current workaround (see comment at the bottom in code below) is to send
> an explicit "close" ZnWebSocketFrame instead of sending #close to the
> ZnWebSocket. This will have the client close 'directly', after which I can
> close the ZnWebSocket stream explicitly. The implementation of #close for
> ZnWebSocket is to perform both: send close frame and close stream. The later
> seems to 'stall' the receive message until timing out. The synchronous
> handling of incoming messages might be causing this. But could not directly
> find why closing the stream resulted in different behaviour than when only
> sending the close frame. (Hope this explanation is helpful ;-).
>
>
>
>
>
>
> --
> Sent from: http://forum.world.st/Pharo-Smalltalk-Users-f1310670.html
>


Reply | Threaded
Open this post in threaded view
|

Re: Zinc WebSocket usage

Erik Stel
Hmmm....weird. My code was visible in preview. I did not look at the final
result after posting. Maybe I should not have used the  tag.

| server client |
server := ZnWebSocket startServerOn: 1701 do: [ :webSocket |
        [ webSocket runWith: [ :message | self crLog: 'Received message: ', message
printString ] ]
        on: ConnectionClosed, PrimitiveFailed do: [ "ignore close" ].
        self crLog: 'The server is closed' ].
client := ZnWebSocket to: (ZnUrl fromString: 'ws://localhost:1701').
[
        [ client runWith: [ :message | "ignore received messages" ] ]
        on: ConnectionClosed, PrimitiveFailed do: [ "ignore close" ].
        self crLog: 'The client is closed'.
        server stop ] fork.
(Delay forSeconds: 1) wait.
client sendMessage: 'Hello world'.
client close.
"Workaround: use the following instead of 'client close'.
client sendFrame: ZnWebSocketFrame close."




--
Sent from: http://forum.world.st/Pharo-Smalltalk-Users-f1310670.html

Reply | Threaded
Open this post in threaded view
|

Re: Zinc WebSocket usage

Erik Stel
Hi Sven,

Maybe you missed the additional post with the source code (I attached it
again below with small change in way it is logging). Is there any advice on
how to do this (other than the workaround I'm using at the moment)? The
issue being that the client recognises its own closing only after a timeout
(around 30 seconds by default). A message "Client closed" will appear on the
Transcript after this 30 seconds. With the workaround (see comment in code),
it does recognise the closing immediately.

As you can see, I'm already using #runWith: as you suggested. Your recent
addition of sending 'ping' does not change the synchronous behaviour.

I'm using this on a Pharo 7 image, but also tested it on Pharo 9.

Kind regards,
Erik

Just in case, I added the code again (without using #crLog: this time):

| server client |
server := ZnWebSocket startServerOn: 1701 do: [ :webSocket |
        [ webSocket runWith: [ :message | Transcript show: 'Received
message: ', message
printString ; cr ] ]
        on: ConnectionClosed, PrimitiveFailed do: [ "ignore close" ].
        Transcript show: 'The server is closed' ; cr ].
client := ZnWebSocket to: (ZnUrl fromString: 'ws://localhost:1701').
[
        [ client runWith: [ :message | "ignore received messages" ] ]
        on: ConnectionClosed, PrimitiveFailed do: [ "ignore close" ].
        Transcript show: 'The client is closed' ; cr.
        server stop ] fork.
(Delay forSeconds: 1) wait.
client sendMessage: 'Hello world'.
client close.
"Workaround: use the following instead of 'client close'.
client sendFrame: ZnWebSocketFrame close."





--
Sent from: http://forum.world.st/Pharo-Smalltalk-Users-f1310670.html

Reply | Threaded
Open this post in threaded view
|

Re: Zinc WebSocket usage

Sven Van Caekenberghe-2
Erik,

Yes, I saw your previous email, answering it was on my todo list ;-)

I took Pharo 9 on macOS 10.15 and did:

Metacello new
  repository: 'github://svenvc/zinc/repository';
  baseline: 'ZincHTTPComponents';
  load: #(WebSocket).

(Accepting loads over existing code)

Although your example is clear, and I could run it, it is still a bit unclear to me what you actually want to do architecturally.

Indeed, it takes 30s (the timeout) for the reading on the client side (inside the #runWith:) to react to the closing in the case of ZnWebSocket>>#close (which sends #close to the stream/socket).

Your alternative works, because in that case, the other end (server side) closes, which apparently triggers the reading on the client side out of its timeout.

Why this does not work is not clear to me (note the 2 process interact with the socket).

There are several ways to change the timeout, the simplest is globally doing:

ZnNetworkingUtils defaultSocketStreamTimeout: 5.

But it would be better to do it dynamically (that is possible but another story).

Anyway changing the timeout to 5s and logging the time using:

| server client |
server := ZnWebSocket startServerOn: 1701 do: [ :webSocket |
       [ webSocket runWith: [ :message | Transcript show: 'Received message: ', message
printString ; cr ] ]
       on: ConnectionClosed, PrimitiveFailed do: [ "ignore close" ].
       Transcript show: 'The server is closed @' , DateAndTime now asString; cr ].
client := ZnWebSocket to: (ZnUrl fromString: 'ws://localhost:1701').
[
       [ client runWith: [ :message | "ignore received messages" ] ]
       on: ConnectionClosed, PrimitiveFailed do: [ "ignore close" ].
       Transcript show: 'The client is closed@' , DateAndTime now asString; cr.
       server stop ] fork.
(Delay forSeconds: 1) wait.
client sendMessage: 'Hello world'.
client close.
"Workaround: use the following instead of 'client close'."
"client sendFrame: ZnWebSocketFrame close."
{ server. client }.

Shows indeed the difference between 30s and 5s.

Now, if you only want to send a message, why not do:

| server client |
ZnNetworkingUtils defaultSocketStreamTimeout: 5.
server := ZnWebSocket startServerOn: 1701 do: [ :webSocket |
        [
                webSocket runWith: [ :message |
                        Transcript show: 'Received message: ', message printString ; cr ] ]
                on: ConnectionClosed, PrimitiveFailed do: [ "ignore close" ].
        Transcript show: 'Server websocket closed @' , DateAndTime now asString; cr ].
client := ZnWebSocket to: 'ws://localhost:1701'.
client sendMessage: 'Hello world'.
client close.
{ server. client }.

This works fine/immediately (since there is no pending read to interact with).

That is why I was asking what your architecture is.

Because if you want to do asynchronous reading and writing, from different processes, things will inevitably get more complicated.

BTW: the server does not close, just the server side web socket.

Sven

> On 2 Jun 2020, at 19:06, Erik Stel <[hidden email]> wrote:
>
> Hi Sven,
>
> Maybe you missed the additional post with the source code (I attached it
> again below with small change in way it is logging). Is there any advice on
> how to do this (other than the workaround I'm using at the moment)? The
> issue being that the client recognises its own closing only after a timeout
> (around 30 seconds by default). A message "Client closed" will appear on the
> Transcript after this 30 seconds. With the workaround (see comment in code),
> it does recognise the closing immediately.
>
> As you can see, I'm already using #runWith: as you suggested. Your recent
> addition of sending 'ping' does not change the synchronous behaviour.
>
> I'm using this on a Pharo 7 image, but also tested it on Pharo 9.
>
> Kind regards,
> Erik
>
> Just in case, I added the code again (without using #crLog: this time):
>
> | server client |
> server := ZnWebSocket startServerOn: 1701 do: [ :webSocket |
>        [ webSocket runWith: [ :message | Transcript show: 'Received
> message: ', message
> printString ; cr ] ]
>        on: ConnectionClosed, PrimitiveFailed do: [ "ignore close" ].
>        Transcript show: 'The server is closed' ; cr ].
> client := ZnWebSocket to: (ZnUrl fromString: 'ws://localhost:1701').
> [
>        [ client runWith: [ :message | "ignore received messages" ] ]
>        on: ConnectionClosed, PrimitiveFailed do: [ "ignore close" ].
>        Transcript show: 'The client is closed' ; cr.
>        server stop ] fork.
> (Delay forSeconds: 1) wait.
> client sendMessage: 'Hello world'.
> client close.
> "Workaround: use the following instead of 'client close'.
> client sendFrame: ZnWebSocketFrame close."
>
>
>
>
>
> --
> Sent from: http://forum.world.st/Pharo-Smalltalk-Users-f1310670.html
>


Reply | Threaded
Open this post in threaded view
|

Re: Zinc WebSocket usage

Erik Stel
In reply to this post by Erik Stel
Hi Sven,

(I hope this message does arrive. Sorry for the late reply. I have not been able to use the browser-interface for the ML for a number of days now. An admin could not figure this out either. So resorting to actually using a mail ;-)

My architecture is one in which client and server are working fairly independent. A client or server can send the other a message whenever it wants (and there is no response expected). The client is responsible for setting up the connection and keeping it ‘open’ (for the server to reach the client). When the client wants it can however disconnect and reconnect at a later time. The server will still be there (this is not visible in the example of course). Both client and server keep track of messages which can’t be sent when the connection is (temporarily) down.

So I have to have a separate process to read messages from the process that writes messages to the WebSocket. In a test I discovered that closing the connection from the client did not get noticed in the process doing the reading. I’d like that process to stop running fairly directly since it is performing some additional logic. Now it takes a timeout. Closing the connection using the specified workaround does get noticed and works for me. But I’m unsure if this is a good approach and there’s the risk it will not work on a next update. A shorter timeout could be a solution but does mean I’m going into a ‘polling’ mode. So I think I prefer my workaround over setting a very short timeout.

If you have a better solution though, please let me know.

Kind regards,
Erik


Reply | Threaded
Open this post in threaded view
|

Re: Zinc WebSocket usage

Sven Van Caekenberghe-2
Erik,

I can't explain why this condition is holding (when a read is pending, closing the socket stream has no effect until the timeout expires, all client side).

I would stick with your solution:

| server client |
server := ZnWebSocket startServerOn: 1701 do: [ :webSocket |
       [ webSocket runWith: [ :message | Transcript show: 'Received message: ', message
printString; cr ] ]
       on: ConnectionClosed, PrimitiveFailed do: [ "ignore close" ].
       Transcript show: 'The server websocket is closed @ ' , DateAndTime now asString; cr ].
client := ZnWebSocket to: 'ws://localhost:1701'.
[
       [ client runWith: [ :message | "ignore received messages" ] ]
       on: ConnectionClosed, PrimitiveFailed do: [ "ignore close" ].
       Transcript show: 'The client websocket is closed @ ' , DateAndTime now asString; cr.
       server stop ] fork.
10 milliSeconds wait.
client sendMessage: 'Hello world! #' , 999 atRandom asString.
"client close."
"Workaround: use the following instead of 'client close'."
client sendFrame: ZnWebSocketFrame close.
{ server. client }.

I checked and no resources are lost at the end (the socket/stream gets cleaned up).

I would lower the timeout in any case, 30s is way too long, I would go for 5s or 10s.

Sven

> On 5 Jun 2020, at 09:55, Erik Stel <[hidden email]> wrote:
>
> Hi Sven,
>
> (I hope this message does arrive. Sorry for the late reply. I have not been able to use the browser-interface for the ML for a number of days now. An admin could not figure this out either. So resorting to actually using a mail ;-)
>
> My architecture is one in which client and server are working fairly independent. A client or server can send the other a message whenever it wants (and there is no response expected). The client is responsible for setting up the connection and keeping it ‘open’ (for the server to reach the client). When the client wants it can however disconnect and reconnect at a later time. The server will still be there (this is not visible in the example of course). Both client and server keep track of messages which can’t be sent when the connection is (temporarily) down.
>
> So I have to have a separate process to read messages from the process that writes messages to the WebSocket. In a test I discovered that closing the connection from the client did not get noticed in the process doing the reading. I’d like that process to stop running fairly directly since it is performing some additional logic. Now it takes a timeout. Closing the connection using the specified workaround does get noticed and works for me. But I’m unsure if this is a good approach and there’s the risk it will not work on a next update. A shorter timeout could be a solution but does mean I’m going into a ‘polling’ mode. So I think I prefer my workaround over setting a very short timeout.
>
> If you have a better solution though, please let me know.
>
> Kind regards,
> Erik
>
>


Reply | Threaded
Open this post in threaded view
|

Re: Zinc WebSocket usage

Erik Stel
Sven,

Okay. Thx for spending your time to investigate this. I'll stick with the
current workaround then.

Cheers,
Erik




--
Sent from: http://forum.world.st/Pharo-Smalltalk-Users-f1310670.html