You are not logged in.
Is it possible to implement push notifications (server to client) without polling the server ?
Offline
I'm currently finishing it...
Tonight or tomorrow commit will include [aa230e5299].
It is working here - just a memory leak to fix!
It will use a genuine syntax, using interface parameters:
type
IMyCallback = interface(IInvokable)
['{5CD49AB1-D5A3-4059-B287-25E939DD2A0B}']
procedure EventCalledFromServer(const withAValue: integer);
end;
IMyService = interface(IInvokable)
['{FB31BF33-38C7-45AE-9511-6AC727ADC4F4}']
procedure MethodWithCallback(const anyParameter: variant; const TheCallBack: IMyCallback);
end;
So it will work with direct code (e.g. on server side), and also on client side...
For transmission, this first implementation is using websockets, and either JSON or binary/encrypted/compressed frames.
Performance is very high!
Online
Update: after the memory leak fix, we have committed the feature.
See http://synopse.info/fossil/info/23352907dd
Online
Great ...
Do you have a sample project to demonstrate how it should be used ?
Offline
There is no sample (nor presentation documentation) yet.
I'm waiting for all tests to pass, and the API be stabilized.
But you can took a look at the regression tests in SynSelfTests.pas.
See the TTestBidirectionalRemoteConnection class.
Online
I'm trying to understand how to use it from the regression tests .
I downloaded the latest build but it fails with those errors :
2.1. Bidirectional remote connection:
- Websockets JSON protocol: 60 assertions passed 24.28ms
- Websockets binary protocol: 240 assertions passed 4.67ms
- SOA callback on server side: 2,234 assertions passed 361.09s
- Run http server: 1 assertion passed 20.78s
! Bidirectional remote connection - SOA callback via JSON websockets
! Exception EInterfaceFactoryException raised with messsage:
! TInterfacedObjectFakeClient.FakeCall(IBidirService.TestCallback) failed: 'URI
root/BidirService.TestCallback/2 [-4,2] returned status 404'
! Bidirectional remote connection - SOA callback via binary websockets
! Exception EInterfaceFactoryException raised with messsage:
! TInterfacedObjectFakeClient.FakeCall(IBidirService.TestCallback) failed: 'URI
root/BidirService.TestCallback/4 [-4,2] returned status 404'
Using mORMot 1.18.1117 FTS3
Running on wSeven_64 with code page 1255
Generated with: Delphi XE2 compiler
Time elapsed for all tests: 426.69s
Tests performed at 23/03/2015 11:45:37
Total assertions failed for all test suits: 1 / 6,413,873
! Some tests FAILED: please correct the code.
Done - Press ENTER to Exit
Offline
I spent the whole day on it...
And finally found several design failures in the previous implementation.
But now it sounds fast and stable in all configurations!
Our logging abilities did help a lot for finding the root causes of the problems, which is pretty difficult when working on multi-thread process.
Online
Good job! We'll see how to use it when an example is provided.
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
I'm waiting for all tests to pass
Hi Arnaud, since I always use the latest github repository and I don't run the tests locally, it occurred to me that, do you commit when not all tests pass? If yes, would you add something like 'all tests pass' or 'not all tests pass' for each commit? So that one will know which revision to use. Thanks for your effort!
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
Thanks Arnaud,
I'm also waiting for an example .
Offline
@edminsn
The tests did pass under Delphi 7 and Kylix... But not with XE7!
It was my fault: I only tested the source code was compiling, but not explicitly run the regression tests for every and each compiler.
I try to compile on all platforms and compiler before any commit.
And almost every time, I try to run the tests at least for Delphi 7 and Delphi XE7 (to cover the whole range of compilers).
This commit was a bit optimistic... but it did not break anything AFAIK, since those websockets are a brand new feature.
Online
@ab, noted. Thanks for your effort!
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
I'm also waiting for an example .
Sample has been uploaded at https://github.com/synopse/mORMot/tree/ … WebSockets
You have both Server and Client applications, able to let huge tasks be executed in a thread on the server, then notified to the client when they are finished.
Push notifications via WebSockets sounds pretty stable now.
Any feedback is welcome!
Next step is a publish/subscribe pattern, and simple data synchronization via a version number - probably as shared classes in the DDD toolbox of the framework.
Enjoy!
Online
Thanks ab,
Thats a great help .
I would like to make sure I understand the usage correct architure .
If I have s synopse ORM server who serve 5,000 concurrent users (synopse delphi clients ) whom I want to push 'random' notifications .
Each client logged in will create kind of 'TLongWorkServiceThread' on the server which will be active as long as the client logged .
Is it the right way ( in terms of server resources cpu/memory) to keep all those threads active (although doing nothing most of the time) ?
Offline
For your own service to write on the server side, you may use a pure asynchronous implementation, only using the ILongWorkCallback instance as reference.
It would use minimal CPU and memory resources.
There are in fact something more to consider.
In fact, the current implementation of our WebSockets socket-API server is indeed maintaining one thread per client, not IOCP (as http.sys did).
So 5000 concurrent users would be difficult to manage.
We may change this in the future, either by using http.sys WebSockets support (only for Windows 8 / Windows Server 2012), or by adding some simple IOCP to the socket-API instance.
But in its current implementation, the highest number of concurrent websockets clients may be around 1000.
It is designed mostly to run within a "cloud", between MicroServices nodes.
Or to serve a limited number (up to 100-200) Delphi clients.
If you have 5000 concurrent users, consider using the http.sys server, and simple pooling from the client side.
It would be much easier to let it scale.
Online
Great work Arnaud,
is it possible to use it under Firemonkey (Crossplatform) ?
Rad Studio 12.1 Santorini
Offline
is it possible to use it under Firemonkey (Crossplatform) ?
Not yet.
The crossplatform clients do not support WebSockets yet - we rely on Indy for the communication on FMX, and this library does not handle them...
Online
Following the example LongWorkserver/client , how does the server can detect client disconnection ?
Offline
WebSockets do have an explicit frame from disconnection (from both client and server ends), and if the socket is broken, the server will detect it during the ping, and after a given number of ping sending issue, will kill the socket.
In respect to raw HTTP/1.1, disconnection is much cleaner with WebSockets.
The client will automatically reconnect to the server if the connection is broken, or if the server is shut down.
In all cases, both client and server sides maintain a list of active connections, so will refuse to transmit on an inactive connection.
If you play with the sample 31, e.g. with one server and several clients, you will see this behavior in action.
Online
is there an event exposed by the server which I can use ?
In the terms of the example , I maintain a list of the 'TLongWorkServiceThread' in order to inject them notifications when needed .
when the connection is suddenly broken, how can I know that ?
Offline
itSDS wrote:is it possible to use it under Firemonkey (Crossplatform) ?
Not yet.
The crossplatform clients do not support WebSockets yet - we rely on Indy for the communication on FMX, and this library does not handle them...
Hmm... just the long shot (WebSockets based on Indy):
https://github.com/andremussche/DelphiWebsockets
Offline
Hmm... just the long shot (WebSockets based on Indy):
https://github.com/andremussche/DelphiWebsockets
I know this implementation, pretty basic, and far away from what we need.
It is not crossplatform.
There is no support for protocols.
Online
There's a little typo at proj Project31SimpleEchoServer.dpr Server.Protocols.Add(protocol);
Server.WebSocketProtocols.Add(protocol);
-----
Push notifications is very nice idea, I found several features implemented by esegece.com, it would be nice if we have built-in methods like WriteData to send text/binary to specific client, for instance.
METHODS:
----------
Broadcast: sends a message to all connected clients.
Message / Stream: message or stream to send to all clients.
Channel: if you specify a channel, message will be sent only to subscribers.
Protocol: if defined, message will be sent only to specific protocol.
Exclude: if defined, list of connection guid excluded (separated by comma).
Include: if defined, list (separated by comma).
WriteData: sends a message to a single or a multiple clients. Every time a Client establishes a WebSocket connection, this connection is identified by a Guid, you can use this Guid to send a message to a client.
Ping: sends a ping to all connected clients.
EVENTS:
----------
OnConnect: every time a WebSocket connection is established, this event is fired.
OnDisconnect: every time a WebSocket connection is dropped, this event is fired.
OnError: every time there is a WebSocket error (like mal-formed handkshake), this event is fired.
OnMessage: every time a client sends a text message and it's received by server, this event is fired.
OnBinary: every time a client sends a binary message and it's received by server, this event is fired.
OnHandshake: this event is fired after handshake is evaluated on server side.
OnCommandGet: this event is fired when HTTP Server receives a GET command requesting a HTML page, an image...
OnCommandOther: this event is fired when HTTP Server receives a command different of GET.
OnCreateSession: this event is fired when HTTP Server creates a new session.
OnInvalidSession: this event is fired when an HTTP request is using an invalid/expiring session.
OnSessionStart: this event is fired when HTTP Server starts a new session.
OnCommandOther: this event is fired when HTTP Server closes a session.
OnException: this event is fired when HTTP Server throws an exception.
OnAuthentication: if authentication is enabled, this event if fired. You can check user and password passed by client and enable/disable Authenticated Variable.
PROPERTIES:
--------------
Connections: contains a list with all clients connections.
Bindings: used to manage IP and Ports.
DocumentRoot: here you can define a directory where you can put all html files (javascript, html, css...) if a client sends a request, server automatically will search this file on this directory, if it finds, it will be served.
Extensions: you can enable compression on messages sent (if client don't support compression, messages will be exchanged automatically without compression).
MaxConnections: max connections allowed (if zero there is no limit).
Count: Connections number count.
AutoStartSession: if SessionState is active, when server gets a new http request, creates a new session.
SessionState: if active, enables http sessions.
KeepAlive: if enabled, connection will stay alive after the response has been sent.
SessionList: read-only property used as a container for TIdHTTPSession instances created for the HTTP server.
SessionTimeOut: timeout of sessions.
---
I don't know, would be possible to send a message to a single client?
For instance, esegece websocket component, every time a Client establishes a WebSocket connection, this connection is identified by a Guid, you can use this Guid to send a message to a client. OnHandshack event, you capture current connections, something like:
procedure TForm1.MyWebSocketServer1Handshake(Connection: TsgcWSConnection; var Headers: TStringList);
begin
ListBox1.Items.Add(Connection.Guid);
end;
and you can send message to specific client like:
MyWebSocketServer1.WriteData('C14D600414504ED59BE6DD2V02288314', 'MyMessage to client 1');
MyWebSocketServer1.WriteData('C9E2F90587074020904E2EAE6CB855AC', 'MyMessage to client 2');
MyWebSocketServer1.WriteData('C0DE69F7F10A44A1B6ED0929EDD1254E', 'MyMessage to client 3');
Offline
Fixed the typo in http://synopse.info/fossil/info/baebc40812
Thanks for the feedback!
Our interface-based implementation allows all this, and even more.
We don't mess with a GUID for each client, but simply an interface variable per client.
You can store the interface instance on the server, e.g. in a single variable to send a message to a single client, or in an array to broadcast an event to several clients.
IMHO our interface-based design is much more powerful, and much cleaner.
It works even on "pure server" side, since by definition, an interface variable works without websockets.
Take a look again at the sample 31, and I'm quite sure you will find out all the potential of our design pattern.
Online
Is it possible add an example to show how use more clients and send a message from server to single client?
Offline
Following the example Project31SimpleEchoServer, I have noticed that server side sends a ping within the interval 20s to client side.
That is, the server and client are playing ping pong, After the handshake, the server send a ping to the client, and the client send back a pong.
I don't know if this is expected. Maybe to make sure that the client is still connected, so you don't keep handshaking again with clients who have already completed the handshake. I don't know.
Server.Settings.HeartbeatDelay := 0; will stop this behaviour.
I couldn't help with a example showing server sending a message to specific client.
Is there an event onAfterHandshake exposed by the server which I can use?
f.i. OnHandhake / onAfterHandshake: this event is fired after handshake is evaluated on server side. Every time a WebSocket client connection is established, this event is fired, so I could get/store this into a listbox to send a message to a single client.
Offline
Ping/pong heartbeat is mandatory to the websockets official RFC specification.
See https://tools.ietf.org/html/rfc6455#section-5.5.2
After handshake, you have the TWebSocketProcess.ProcessStart to be overriden.
For a better WebSocket process, consider using interface-based services, and an interface variable as callback.
You will find everything needed with this.
Online
Fixed the typo in http://synopse.info/fossil/info/baebc40812
Thanks for the feedback!Our interface-based implementation allows all this, and even more.
We don't mess with a GUID for each client, but simply an interface variable per client.
You can store the interface instance on the server, e.g. in a single variable to send a message to a single client, or in an array to broadcast an event to several clients.IMHO our interface-based design is much more powerful, and much cleaner.
It works even on "pure server" side, since by definition, an interface variable works without websockets.
Take a look again at the sample 31, and I'm quite sure you will find out all the potential of our design pattern.
On example 31, where is the interface instance that I will store to contact the single client?
Offline
TLongWorkService? How do I decide the client? Sorry but the structure interface structure I just hard to understand.
Offline
I've uploaded a sample implementing a chat service, with real-time broadcast to the connected clients.
See https://github.com/synopse/mORMot/blob/ … Server.dpr and https://github.com/synopse/mORMot/blob/ … Client.dpr
Online
Following the awesome example Project31ChatServer / Project31ChatClient
In my test, I would like to select a JPEG file from filesystem or drag n' drop a file, and broadcast it through a web socket to all others delphi/ajax clients. At this point, image should be encoded as base64 string. Any tip how you would implement SendImg callback to Send bin content?
procedure TChatService.SendImg(s: TSQLRawBlob);
var i: integer;
begin
for i := 0 to high(fConnected) do
fConnected[i].SendImg(s);
end;
procedure TChatCallback.SendImg(s: TSQLRawBlob);
begin
//
end;
procedure ShowImageFromStream(AImage: TImage; AData: TStream);
var
JPEGImage: TJPEGImage;
begin
AData.Position := 0;
JPEGImage := TJPEGImage.Create;
try
JPEGImage.LoadFromStream(AData);
AImage.Picture.Assign(JPEGImage);
finally
JPEGImage.Free;
end;
end;
Offline
to warleyalex:
see sample 26 - RESTful ORM
RESTServerClass.pas
procedure TNoteServer.Blob(Ctxt: TSQLRestServerURIContext);
Offline
What is the right way to store "pseudo" value inside the interface?
"Join" function on Project31ChatServer save the interface inside an array then "BlaBla" send a message to all clients.
The server cannot identify the single client, so on "Join" I want save "pseudo" inside interface so the server will send the message to single client by "pseudo".
I have add a property to IChatCallback:
IChatCallback = interface(IInvokable)
['{EA7EFE51-3EBA-4047-A356-253374518D1D}']
procedure BlaBla(const pseudo, msg: string);
function GetPseudo : string;
procedure SetPseudo(aValue: string);
property Pseudo: string read GetPseudo write SetPseudo;
end;
...
TChatCallback = class(TInterfacedCallback,IChatCallback)
protected
fPseudo: string;
procedure BlaBla(const pseudo, msg: string);
function GetPseudo : string;
procedure SetPseudo(aValue: string);
public
property Username: string read GetPseudo write SetPseudo;
end;
...
function TCommandsCallback.GetPseudo : string;
begin
Result := fPseudo
end;
procedure TCommandsCallback.SetPseudo(aValue: string);
begin
fPseudo:= aValue
end;
then I have edit "Join":
callback.Pseudo:= pseudo;
InterfaceArrayAdd(fConnected,callback);
When I try to connect my client to server I get this error on server:
Exception class EInterfaceFactoryException with message 'TInterfacedObjectFakeServer.FakeCall(ICommandsCallback.SetPseudo) failed: 'TSQLHttpServer.NotifyCallback(3) returned status=404''. Process Project31ChatClient.exe (2248)
Offline
No!
As stated by the doc store your filter value with the callback interface in an array of records or a class list.
I have read 16.7.1, 16.7.1.1, 16.7.1.2 and 16.7.1.3 but I don't have find it.
Edit: I have found it on 16.7.2.3. Thanks
Last edited by array81 (2015-04-12 22:04:22)
Offline
to warleyalex:
see sample 26 - RESTful ORMRESTServerClass.pas
procedure TNoteServer.Blob(Ctxt: TSQLRestServerURIContext);
In my experiments, I can broadcasting JPG image from one client to another. It is done through Base64-encoding (I'm using interface based services), this is nice to dataURI, but it adds 30% to 50% overhead. Now I wish to boost the performance by communicating in binary mode instead. The idea is to try a very basic implementation of video streaming over websockets, to stream a video of, say, 30fps. I don't know if this is possible.
Can you confirm that:
a) From server to client, or broadcasting to clients, I have to use TServiceCustomAnswer (through base64 encoding) to send a JPEG image, for instance.
b) From client to server, it is not implemented for interface-based services (I can not send binary data, only base64).
c) For binary data, I only can use a method-based services through HTTP. (I can not a interface based services to send binary data through websockets.
In my test, I perform a very basic DDOS attack, just to test how websockets efficiently deals with a large amount of data. I open some clients - I would like to broadcast some pretty large strings to all the others connected instances between 200ms. However, for instance, If clientA broadcasts a JPEG image to B,C,D,E,F clients, everybody will receive that image, besides the texts involved.
What makes interesting is: clientA will crash, a lot of base64 crap and text is involved.
Last edited by warleyalex (2015-04-13 01:07:23)
Offline
to warleyalex:
see sample 26 - RESTful ORMRESTServerClass.pas
procedure TNoteServer.Blob(Ctxt: TSQLRestServerURIContext);
In my experiments, I can broadcasting JPG image from one client to another. It is done through Base64-encoding (I'm using interface based services), this is nice to dataURI, but it adds 30% to 50% overhead. Now I wish to boost the performance by communicating in binary mode instead. The idea is to try a very basic implementation of video streaming over websockets, to stream a video of, say, 30fps. I don't know if this is possible.
Can you confirm that:
a) From server to client, or broadcasting to clients, I have to use TServiceCustomAnswer (through base64 encoding) to send a JPEG image, for instance.
b) From client to server, it is not implemented for interface-based services (I can not send binary data, only base64).
c) For binary data, I only can use a method-based services through HTTP. (I can not a interface based services to send binary data through websockets.
In my test, I perform a very basic DDOS attack, just to test how websockets efficiently deals with a large amount of data. I open some clients - I would like to broadcast some pretty large strings to all the others connected instances between 200ms. However, for instance, If clientA broadcasts a JPEG image to B,C,D,E,F clients, everybody will receive that image, besides the texts involved.
What makes interesting is: clientA will crash, a lot of base64 crap and texts are involved.
Offline
On my application I use a WebSockets to send command from server to client.
When the administrator close database on server application, the server application send a message to client about it and then free all variables.
The client receive the message then hide the gui about the database and free all variables about it.
I get and error on client when TSQLHttpClientWebsockets is free: "SndLow 10053 (Connection aborted by the software of the host computer)".
Offline
@warleyalex
a) Indeed
b) This is true
c) Yes
In fact:
b) I'm currently working on it - by a new kind of record to send binary data from the server to the client.
c) I'm currently working on it - by a new kind of record to send binary data form the client to the server.
Online
Following the example Project31ChatServer.dpr
if the parameter aHandleUserAuthentication = True at
Server := TSQLRestServerFullMemory.CreateWithOwnModel([],TRUE);
I can not unsubscribe a service, the callback interface parameter (IChatCallback) will not be released on the client side. I don't know if this is expected.
Offline
@warleyalex
I just checked sample 31 with ...,true) and if the client call Client.SetUser() to initialize the instance as expected: no problem.
Server side:
Server := TSQLRestServerFullMemory.CreateWithOwnModel([],true);
try
Server.CreateMissingTables;
Server.ServiceDefine(TChatService,[IChatService],sicShared).
SetOptions([],[optExecLockedPerInterface]); // thread-safe fConnected[]
HttpServer := TSQLHttpServer.Create('8888',[Server],'+',useBidirSocket);
Client side:
Client := TSQLHttpClientWebsockets.Create('127.0.0.1','8888',TSQLModel.Create([]));
try
Client.Model.Owner := Client;
Client.WebSocketsUpgrade(PROJECT31_TRANSMISSION_KEY);
if not Client.ServerTimeStampSynchronize then
raise EServiceException.Create(
'Error connecting to the server: please run Project31ChatServer.exe');
if not Client.SetUser('User','synopse') then
raise EServiceException.Create('Impossible to create a session');
Client.ServiceDefine([IChatService],sicShared);
if not Client.Services.Resolve(IChatService,Service) then
raise EServiceException.Create('Service IChatService unavailable');
And BTW, the callback is released on the CLIENT side, then propagated to the SERVER side, up to TChatService.CallbackReleased().
Online
Indeed, it's working fine. In my video DO MACARENA with mORMot http://youtu.be/Oe4WVcBgAek
I can not accomplish:
a) how do you disconnect a client from server?
InterfaceArrayDelete(fConnected,ListView1.ItemIndex);
ListView1.Items.Delete(ListView1.ItemIndex);
f.i. If I disconnect clientC from Server, it would be released only on the server side; ClientC still can broadcast messages to all clients.
b) I could find a way, to get the SessionRemoteIP on the subscription process. I'd like to store the user/IP in a listview. procedure TChatService.JOIN(const pseudo, IP: string; const callback: IChatCallback);
user := (Client as TSQLRestClientURI).SessionUser.LogonName;
IP := ?
Offline
Nice demo.
a) What do you call "disconnect"?
If you are talking about InterfaceArrayDelete(), you are not disconnecting the client: you are releasing the callback.
The WebSockets connection is still there over IP.
There is not built-in way to force the IP disconnection from the server.
You would need to use a dedicated method in a callback, on the client, to close the client connection on request.
b) Try to use the ServiceContext.Request.SessionRemoteIP threadvar.
Online
to warleyalex,
рш, сan you put your sample demo here (better if ab add it to ThirdPartyDemos)?
Offline
I using WebSockets to change message (commands) between server and client.
Sometimes I get an error on client (see image).
can you help me to understand the problem?
Offline
It means that the server was not able to send a notification to the client.
Are you using the latest version of the framework?
One potential cause may be that the callback has been released on the client side, so when the server tries to release it, it can't find it.
Online
It means that the server was not able to send a notification to the client.
Are you using the latest version of the framework?
One potential cause may be that the callback has been released on the client side, so when the server tries to release it, it can't find it.
I have update the framework some week ago. I think in april.
Offline
Try with the latest version.
Also check if the callback has not been released on the client side.
See also the CallbackReleased() kind of method (defined e.g. in IServiceWithCallbackReleased). It would allow the server to be notified when a callback has been released on the client server side, to avoid such errors.
Online
Is there a way (just implemented) to send a notify from server to client when there is a change on DB.
I'd like add the clients GUI when there is a change on DB. In this moment my clients update the DB when the user change windows status from minimized to normal or maximized, however thi update the GUI without reason in many case.
I like know if there is a way to optimize this procress so the client can update the GUI when needed and only the piece of GUI needed (for example if there change only on table Product the client will update only the GUI about this table).
Is this just implemented or I need do it?
Offline