You are not logged in.
Based on example 31, I wrote a simple service for uploading logs using websockets.
However, I would like the client side (which implements the callback class) to receive an event in the event of a lost connection so that it can re-connect to the server and pass the callback interface.
I made this code for client:
procedure TLogClientWebsockets.OnClientDisconnected(sender: TObject);
begin
// disconnect event handling is never called!!
end;
procedure TLogClientWebsockets.OnWebSocketsClosed(sender: TObject);
begin
// disconnect event handling is never called!!
end;
procedure TLogClientWebsockets.Start();
begin
fModel := TSQLModel.Create([], cRemoteLogRootName);
fRestClient := TSQLHttpClientWebsockets.Create('127.0.0.1', cRemoteLogHTTPPort.ToString, fModel);
fRestClient.Model.Owner := fRestClient;
fRestClient.OnWebSocketsClosed := OnWebSocketsClosed;
fRestClient.WebSockets.OnWebSocketsClosed := OnWebSocketsClosed;
fRestClient.WebSockets.Settings.OnClientDisconnected := OnClientDisconnected;
fRestClient.WebSockets.Settings.HeartbeatDelay := 3000;
fRestClient.WebSockets.Settings.DisconnectAfterInvalidHeartbeatCount := 3;
fRestClient.WebSocketLoopDelay := 100;
fRestClient.WebSocketsUpgrade(cWebSocketEncryptionKey);
fRestClient.ServiceDefine([ILogService], sicShared);
if not fRestClient.Services.Resolve(ILogService, fService) then
raise EServiceException.Create('Service ILogService unavailable');
fLogManager := TLogManager.Create(TPanel(fLogPanel), 0, nil);
fCallback := TRemoteLogCallback.Create(fLogManager, fRestClient, IRemoteLogCallback, fLogPanel.Handle);
fService.StartLogService(fCallback);
end;
Then I run the application and during transmission using TCPView (from Sysinternals) I close the socket that supports the websockets connection. On the server side, the onClientDisconnect event fires. But nothing is happening on the client's side!
How to detect connection loss from the client side?
Offline
A raw TCP Connection closed is not noticeable until a send/receive operation fails, by definition of how TCP works.
A WebSockets connection gracefully closed with its "Connection Close" message from the other end will be notified.
Offline
So there is no way to know from the client side (callback implementation side) that the connection has died?
My problem is protection against network errors and the possibility of resuming the connection. How to do it?
Offline
So there is no way to know from the client side (callback implementation side) that the connection has died?
But you will know, the connection will timeout and it will throw an exception (or trigger some onFail event handle) that you are supposed to handle.
Offline
In the code that I showed in the example, there are two events:
procedure TLogClientWebsockets.OnClientDisconnected(sender: TObject);
begin
// disconnect event handling is never called!!
end;
procedure TLogClientWebsockets.OnWebSocketsClosed(sender: TObject);
begin
// disconnect event handling is never called!!
end;
None of these events is called, so I ask what I am doing wrong or how to do it correctly?
Offline
The client websocket connection does not send ping frames to the server by default so you will never get a timeout exception if you don't actually make a request to the server.
Regardless, there seems to be a bug anyway, since setting HeartbeatDelay does absolutely nothing. I've made a pull request with a fix that I tested against sample 31 chat application, https://github.com/synopse/mORMot/pull/204.
With this fix, WebSockets.Settings.OnClientDisconnected will be called once DisconnectAfterInvalidHeartbeatCount has been reached.
Last edited by pvn0 (2019-05-27 12:01:03)
Offline
Thanks pvn0 and Arnaud! I will test it today...
Offline
Has anyone tested and confirmed that this works now ?
Offline
It partially works, but there are further problems.
On the server side, I assign the OnClientDisconnected event:
procedure TWSLogServer.StartRemoteLogService();
begin
[...]
fHTTPServer := TSQLHttpServer.Create(IntToStr(fHTTPPort), [fRestServer], '+', useBidirSocket);
fWSServer := fHTTPServer.WebSocketsEnable(fRestServer, cWebSocketEncryptionKey);
fWSServer.Settings.OnClientDisconnected := OnClientDisconnected; <========== event assigned
end;
When I release the server, I want to release the event, so I execute the code:
procedure TWSLogServer.StopRemoteLogService();
begin
if Assigned(fHTTPServer) then
begin
fWSServer.Settings.OnClientDisconnected := nil; <======== event released, but only on TWebSocketServer class side!
FreeAndNil(fHTTPServer);
FreeAndNil(fRestServer);
end;
end;
However, the event is still called!
Code review and debugging led me to the conclusion that there is an error in the settings, because they are saved in the record:
/// parameters to be used for WebSockets process
TWebSocketProcessSettings = record
[...]
And in working classes there are separate instances of those records that have copies of settings data, so that changing Settings in the TWebSocketServer class instance does not affect the TWebSocketProcess class instance.
TWebSocketServer = class(THttpServer)
protected
fSettings: TWebSocketProcessSettings; <==== first settings
TWebSocketProcess = class(TSynPersistent)
protected
fSettings: TWebSocketProcessSettings; <===== next settings
How do I release a OnClientDisconnected event from a TWebSocketProcess?
Maybe worth change TWebSocketProcessSettings from record to class?
Last edited by jaclas (2019-06-10 11:54:24)
Offline
This is unrelated to the original question and it would be better to open it's own forum thread, regardless, yes, if you change TWebSocketServer.Settings it won't propagate to existing connections, this is by design :
// the settings to be used for WebSockets process
// - note that those parameters won't be propagated to existing connections
function Settings: PWebSocketProcessSettings; {$ifdef HASINLINE}inline;{$endif}
How do I release a OnClientDisconnected event from a TWebSocketProcess?
Currently, you can't, at least not for existing connections. You have control over code that is executed in the event handler so you can chose to ignore the notification. (that code better be thread safe either way).
I agree that the current implementation is not ideal, I will fix this when I have some free time.
Offline
I can't ignore events, because the problem is that the event method is part of the freed server object and at the moment of calling the object is already released which ends with a series of AV...
For now, I did a workaround, I additionally support the OnClientConnected() event and in it I remember the "self" object, which is TWebSocketProcess, thanks to which it later has access to its settings. But it is ugly :-|
But I will wait for a better solution and thanks for the comments :-)
Offline
Frankly, problems with such basic functionality as disconnect are discouraging.
I introduced Mormot into my company for it's WebSocket implementation but my colleagues now doubt it it was a good choice.
Offline
I got the next problem.
If after unexpected closing the websocket connection I will resume this connection again (start websocket from client side) then next connection break (simulated by TCPView -> Close connection) no longer triggers the OnWebSocketsClosed event.
Offline
I got the next problem.
If after unexpected closing the websocket connection I will resume this connection again (start websocket from client side) then next connection break (simulated by TCPView -> Close connection) no longer triggers the OnWebSocketsClosed event.
I was unable to reproduce this , can you give a more detailed explanation?
Offline
If after unexpected closing the websocket connection I will resume this connection again (start websocket from client side) then next connection break (simulated by TCPView -> Close connection) no longer triggers the OnWebSocketsClosed event.
I have a similar problem:
https://synopse.info/forum/viewtopic.ph … 392#p29392
Offline
My event OnWebSocketsClosed looks like this:
procedure TRemoteLogClientWebsockets.OnWebSocketsClosed(sender: TObject);
begin
TThread.Queue(nil, procedure
begin
fService.StartLogSession(fCallback);
end);
end;
After brute-force close connection (by using this tool: https://docs.microsoft.com/en-us/sysint … s/tcpview) first time this event is called and websocket is connected again, but after second disconnection event OnWebSocketsClosed event is never called again.
Offline
I create simple example apps based od Example 31 from mORMot demos.
See screencast how the second close the connection does not result in an OnWebSocketsClosed event on client side:
client source: https://pastebin.com/4teDqTJG
server source: https://pastebin.com/QKLFUKZF
Last edited by jaclas (2019-06-17 09:22:40)
Offline
I create simple example apps based od Example 31 from mORMot demos.
See screencast how the second close the connection does not result in an OnWebSocketsClosed event on client side:client source: https://pastebin.com/4teDqTJG
server source: https://pastebin.com/QKLFUKZF
Thanks, very nice example.
Fixed in https://github.com/synopse/mORMot/pull/213
the problem was the very unfortunate placement of the TWebSocketProcessSettings record in the THttpClientWebSockets class, when the socket closes, class is destroyed and the settings lost with it. That is why you don't get the second event triggered, the settings are reset to default. I've now introduced DefaultWebSocketProcessSettings in the TSQLHttpClientWebsockets class, every new client websocket class instance will inherit this default settings (it won't break any existing implementation) so you better do this:
Client.DefaultWebSocketProcessSettings.HeartbeatDelay := 3000;
Client.DefaultWebSocketProcessSettings.DisconnectAfterInvalidHeartbeatCount := 2;
Client.OnWebSocketsClosed := OnWebSocketsClosed;
instead of
Client.WebSockets.Settings.HeartbeatDelay := 3000;
Client.WebSockets.Settings.DisconnectAfterInvalidHeartbeatCount := 2;
Client.WebSockets.OnWebSocketsClosed := OnWebSocketsClosed;
notice the change in assigning the OnWebSocketsClosed event, it's important.
Solving this problem revealed a much bigger issue, there's an internal connection state that is tracked at the TSQLRestClientURI level, it was not being properly updated when the websocket connection re-establishes, this was also fixed.
Last edited by pvn0 (2019-06-18 08:04:11)
Offline
Very, very big thanks for your effort and time pvn0!
Now all is right! :-D
Last edited by jaclas (2019-06-18 11:59:25)
Offline
This is not yet merged into master ?
Offline
Yes, I'm also waiting for the official repo.
Arnaud, please review and merge these changes.
Offline
Has been merged - with some minor blank fixes - as https://github.com/synopse/mORMot/pull/213
Thanks for the feedback!
Offline