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
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 <erik.stel@gmail.com> 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
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
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
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 <erik.stel@gmail.com> 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
participants (2)
-
Erik Stel -
Sven Van Caekenberghe