ASP.NET Core SignalR ist eine Open-Source-Bibliothek, die das Hinzufügen von Echtzeit-Webfunktionalität zu Apps vereinfacht. Echtzeit-Webfunktionalität ermöglicht es serverseitigem Code, Inhalte sofort an Clients zu pushen.
Gute Kandidaten für SignalR:
Die SignalRCore-Komponente von sgcWebSockets verwendet WebSocket als Transport, um eine Verbindung zu einem SignalRCore-Server herzustellen. Wenn dieser Transport nicht unterstützt wird, wird ein Fehler ausgelöst.
SignalRCore verwendet Hubs zur Kommunikation zwischen Clients und Servern. SignalRCore bietet 2 Hub-Protokolle: ein Textprotokoll basierend auf JSON und ein Binärprotokoll basierend auf MessagePack. Die sgcWebSockets-Komponente implementiert nur das JSON-Textprotokoll zur Kommunikation mit SignalRCore-Servern.
Um zu konfigurieren, welchen Hub der Client verwendet, setzen Sie einfach in der Eigenschaft SignalRCore/Hub den Namen des Hubs, bevor sich der Client mit dem Server verbindet.
Wenn ein Client eine neue Verbindung zum Server öffnet, sendet er eine Anforderungsnachricht, die Formatprotokoll und Version enthält. sgcWebSockets sendet das Formatprotokoll immer als JSON. Der Server antwortet mit einem Fehler, wenn das Protokoll vom Server nicht unterstützt wird; dieser Fehler kann mit dem Ereignis OnSignalRCoreError behandelt werden, und wenn die Verbindung erfolgreich ist, wird das Ereignis OnSignalRCoreConnect aufgerufen.
Wenn sich ein Client mit einem SignalRCore-Server verbindet, kann er eine ConnectionId senden, die den Client zwischen Sitzungen identifiziert, sodass der Client bei einer Trennung erneut eine Verbindung zum Server herstellen kann, indem er dieselbe vorherige Verbindungs-ID übergibt. Um eine neue Verbindungs-ID zu erhalten, verbinden Sie sich einfach normal mit dem Server, und Sie können die ConnectionId über OnBeforeConnectEvent erfahren. Wenn Sie sich erneut mit dem Server verbinden und eine vorherige Verbindungs-ID übergeben möchten, verwenden Sie die Methode ReConnect und übergeben Sie ConnectionId als Parameter.
Das SignalR-Protokoll ist ein Protokoll für bidirektionalen RPC über jeden nachrichtenbasierten Transport. Jede Partei in der Verbindung kann Prozeduren auf der anderen Partei aufrufen, und Prozeduren können null oder mehr Ergebnisse oder einen Fehler zurückgeben. Beispiel: der Client kann eine Methode vom Server anfordern und der Server kann eine Methode vom Client anfordern. Die folgenden Nachrichten werden zwischen Server und Clients ausgetauscht:
HandshakeRequest: der Client sendet ihn an den Server, um sich auf das Nachrichtenformat zu einigen.
HandshakeResponse: Der Server antwortet dem Client mit einer Bestätigung der vorherigen HandshakeRequest-Nachricht. Enthält einen Fehler, wenn der Handshake fehlgeschlagen ist.
Close: wird vom Client oder Server aufgerufen, wenn eine Verbindung geschlossen wird. Enthält einen Fehler, wenn die Verbindung aufgrund eines Fehlers geschlossen wurde.
Invocation: Client oder Server sendet eine Nachricht an einen anderen Peer, um eine Methode mit oder ohne Argumente aufzurufen.
StreamInvocation: Client oder Server sendet eine Nachricht an einen anderen Peer, um eine Streaming-Methode mit oder ohne Argumente aufzurufen. Die Antwort wird in verschiedene Elemente aufgeteilt.
StreamItem: ist eine Antwort von einer vorherigen StreamInvocation.
Completion: bedeutet, dass eine vorherige Invocation oder StreamInvocation abgeschlossen wurde. Kann ein Ergebnis enthalten, wenn der Prozess erfolgreich war, oder einen Fehler, wenn ein Fehler aufgetreten ist.
CancelInvocation: einen vorherigen StreamInvocation-Request abbrechen.
Ping: ist eine Nachricht, um zu prüfen, ob die Verbindung noch besteht.
SignalRCore ermöglicht die Verwendung der folgenden Codierungen:
Derzeit wird nur JSON unterstützt, obwohl MessagePack verwendet werden kann, indem die gesendeten Nachrichten mit einer externen MessagePack-Bibliothek kodiert werden. Siehe den Abschnitt MessagePack unten für weitere Informationen.
Die Konfiguration des Encoding-Protokolls wird in der Eigenschaft SignalRCore.Protocol definiert. Standardmäßig ist der Wert srcpJSON.
Die Authentifizierung kann aktiviert werden, um jeder Verbindung einen Benutzer zuzuordnen und zu filtern, welche Benutzer auf Ressourcen zugreifen können. Die Authentifizierung wird mit Bearer-Tokens implementiert: Der Client stellt ein Access-Token bereit und der Server validiert dieses Token und verwendet es, um den Benutzer zu identifizieren.
In Standard-Web-APIs werden Bearer-Token in einem HTTP-Header gesendet, aber bei Verwendung von WebSockets wird das Token als Query-String-Parameter übertragen.
Die folgenden Methoden werden unterstützt:
srcaRequestToken
Wenn die Authentifizierung aktiviert ist, lautet der Ablauf:
1. Versucht zunächst, ein gültiges Token vom Server abzurufen. Öffnet eine HTTP-Verbindung zu Authentication.RequestToken.URL und führt einen POST mit den Daten User und Password durch.
2. Wenn der vorherige Schritt erfolgreich ist, wird ein Token zurückgegeben. Wenn nicht, wird ein Fehler zurückgegeben.
3. Wenn ein Token zurückgegeben wird, wird eine neue HTTP-Verbindung geöffnet, um eine neue Verbindung auszuhandeln. Hier wird das Token als HTTP-Header übergeben.
4. Wenn der vorherige Schritt erfolgreich ist, wird eine WebSocket-Verbindung geöffnet und das Token als Query-String-Parameter übergeben.
Authentication.Enabled: wenn aktiv, wird vor dem Aufbau einer WebSocket-Verbindung eine Autorisierung verwendet.
Authentication.Username: der dem Server zur Authentifizierung bereitgestellte Benutzername.
Authentication.Password: das geheime Wort, das dem Server zur Authentifizierung bereitgestellt wird.
Authentication.RequestToken.PostFieldUsername: Name des Feldes zur Übertragung des Benutzernamens (abhängig von der Konfiguration, überprüfen Sie die HTTP-JavaScript-Seite, um zu sehen, welcher Name verwendet wird).
Authentication.RequestToken.PostFieldPassword: Name des Feldes zur Übertragung des Passworts (abhängig von der Konfiguration, prüfen Sie die HTTP-JavaScript-Seite, um zu sehen, welcher Name verwendet wird).
Authentication.RequestToken.URL: URL, unter der das Token angefordert wird.
Authentication.RequestToken.QueryFieldToken: Name des Query-String-Parameters, der in der WebSocket-Verbindung verwendet wird.
srcaSetToken
Hier übergeben Sie das Token direkt an den SignalRCore-Server (da das Token von einem anderen Server bezogen wurde).
Authentication.Enabled: wenn aktiv, wird die Autorisierung verwendet, bevor eine WebSocket-Verbindung hergestellt wird.
Authentication.SetToken.Token: erhaltener Token-Wert.
Das Zugriffstoken kann als Abfrageparameter gesendet werden (dies ist die Standardoption) oder als HTTP-Header als Bearer-Token gesendet werden. Verwenden Sie die Eigenschaft Authentication.TokenParam, um dieses Verhalten zu konfigurieren.
srcaBasic
Diese Option verwendet Basic-Authentifizierung; diese Authentifizierungsmethode erfordert die Konfiguration der SignalRCore-Komponente und des TsgcWebSocketClient.
Beispiel: wenn der Server Basic-Authentifizierung erfordert und der Benutzername "user" und das Passwort "secret" ist, konfigurieren Sie die Komponenten wie unten gezeigt.
// websocket client
WSClient := TsgcWebSocketClient.Create(nil);
WSClient.Authentication.Enabled := True;
WSClient.Authentication.Basic.Enabled := True;
WSClient.Authentication.URL.Enabled := False;
WSClient.Authentication.Session.Enabled := False;
WSClient.Authentication.Token.Enabled := False;
WSClient.Authentication.User := 'user';
WSClient.Authentication.Password := 'secret';
// signalrcore
Signal := TsgcWSAPI_SignalRCore.Create(nil);
Signal.SignalRCore.Authentication.Enabled := True;
Signal.SignalRCore.Authentication.Authentication := srcaBasic;
Signal.SignalRCore.Authentication.Username := 'user';
Signal.SignalRCore.Authentication.Password := 'secret';
Signal.Client := WSClient;
Es gibt drei Arten von Interaktionen zwischen Server und Clients:
Der Caller sendet eine Nachricht an den Callee und erwartet eine Nachricht, die anzeigt, dass der Aufruf abgeschlossen wurde, und optional ein Ergebnis des Aufrufs
Beispiel: Der Client ruft die Methode SendMessage auf und übergibt als Parameter Benutzername und Textnachricht. Sendet eine Invocation Id, um
eine Ergebnisnachricht vom Server zu erhalten.
SignalRCore.Invoke('SendMessage', ['John', 'Hello All.'], 'id-000001');
procedure OnSignalRCoreCompletion(Sender: TObject; Completion: TSignalRCore_Completion);
begin
if Completion.Error <> '' then
ShowMessage('Something goes wrong.')
else
ShowMessage('Invocation Successful!');
end;
Der Caller sendet eine Nachricht an den Callee und erwartet keine weiteren Nachrichten für diesen Aufruf. Aufrufe können ohne einen Invocation-ID-Wert gesendet werden. Dies zeigt an, dass der Aufruf "non-blocking" ist.
Beispiel: Der Client ruft die Methode SendMessage auf und übergibt als Parameter Benutzername und Textnachricht. Der Client erwartet keine Antwort vom Server über das Ergebnis des Aufrufs.
SignalRCore.Invoke('SendMessage', ['John', 'Hello All.']);
Der Caller sendet eine Nachricht an den Callee und erwartet ein oder mehrere vom Callee zurückgegebene Ergebnisse, gefolgt von einer Nachricht, die das Ende des Aufrufs anzeigt.
Beispiel: Der Client ruft die Counter-Methode auf und fordert 10 Zahlen mit einem Intervall von 500 Millisekunden an.
SignalRCore.InvokeStream('Counter', [10, 500], 'id-000002');
procedure OnSignalRCoreStreamItem(Sender: TObject; StreamItem: TSignalRCore_StreamItem; var Cancel: Boolean);
begin
DoLog('#stream item: ' + StreamItem.Item);
end;
procedure OnSignalRCoreCompletion(Sender: TObject; Completion: TSignalRCore_Completion);
begin
if Completion.Error '' then
ShowMessage('Something goes wrong.')
else
ShowMessage('Invocation Successful!');
end;
Um einen einzelnen Aufruf durchzuführen, folgt der Caller dem folgenden grundlegenden Ablauf:
procedure Invoke(const aTarget: String; const aArguments: Array of Const; const aInvocationId: String = '');
procedure InvokeStream(const aTarget: String; const aArguments: Array of Const; const aInvocationId: String);
Weisen Sie einen eindeutigen Wert für die Invocation ID zu (beliebige Zeichenfolge, vom Caller gewählt), um den Aufruf zu repräsentieren. Rufen Sie die Methode Invoke oder InvokeStream auf, die das aufzurufende Target, Arguments und InvocationId enthält (wenn Sie keine InvocationId senden, erhalten Sie kein Abschlussergebnis).
Wenn der Invocation als nicht blockierend markiert ist (siehe "Non-Blocking Invocations" unten), stoppen Sie hier und kehren Sie sofort zur Anwendung zurück. Behandeln Sie StreamItem- oder Completion-Nachrichten mit einer passenden Invocation-ID.
SignalRCore.InvokeStream('Counter', [10, 500], 'id-000002');
procedure OnSignalRCoreStreamItem(Sender: TObject; StreamItem: TSignalRCore_StreamItem; var Cancel: Boolean);
begin
if StreamItem.InvocationId = 'id-000002' then
DoLog('#stream item: ' + StreamItem.Item);
end;
procedure OnSignalRCoreCompletion(Sender: TObject; Completion: TSignalRCore_Completion);
begin
if StreamItem.InvocationId = 'id-000002' then
begin
if Completion.Error '' then
ShowMessage('Something goes wrong.')
else
ShowMessage('Invocation Successful!');
end;
end;
Sie können einen einzelnen Aufruf ausführen und auf den Abschluss warten.
function InvokeAndWait(const aTarget: String; aArguments: Array of Const; aInvocationId: String; out Completion: TSignalRCore_Completion;
const aTimeout: Integer = 10000): Boolean;
function InvokeStreamAndWait(const aTarget: String; const aArguments: Array of Const; const aInvocationId: String;
out Completion: TSignalRCore_Completion; const aTimeout: Integer = 10000): Boolean;
Weisen Sie einen eindeutigen Invocation-ID-Wert (beliebige Zeichenfolge, vom Caller gewählt) zu, um die Invocation darzustellen. Rufen Sie die Methode InvokeAndWait oder InvokeStreamAndWait auf, die das aufzurufende Target, die Arguments und die InvocationId enthält. Das Programm wartet, bis das Completion-Ereignis aufgerufen wird oder das Time-out überschritten wurde.
var
oCompletion: TSignalRCore_Completion;
begin
if SignalRCore.InvokeStreamAndWait('Counter', [10, 500], 'id-000002', oCompletion) then
DoLog('#invoke stream ok: ' + oCompletion.Result)
else
DoLog('#invocke stream error: ' + oCompletion.Error);
procedure OnSignalRCoreStreamItem(Sender: TObject; StreamItem: TSignalRCore_StreamItem; var Cancel: Boolean);
begin
if StreamItem.InvocationId = 'id-000002' then
DoLog('#stream item: ' + StreamItem.Item);
end;
Wenn der Client das Empfangen von StreamItem-Nachrichten beenden möchte, bevor der Server eine Completion-Nachricht sendet, kann der Client eine CancelInvocation-Nachricht mit derselben InvocationId senden, die für die StreamInvocation-Nachricht verwendet wurde, die den Stream gestartet hat.
procedure OnSignalRCoreStreamItem(Sender: TObject; StreamItem: SignalRCore_StreamItem; var Cancel: Boolean);
begin
if StreamItem.InvocationId = 'id-000002' then
Cancel := True;
end;
Eine Invocation gilt erst dann als abgeschlossen, wenn die Completion-Nachricht empfangen wird. Wenn der Client eine Invocation vom Server empfängt, wird das Ereignis OnSignalRCoreInvocation aufgerufen.
procedure OnSignalRCoreInvocation(Sender: TObject; Invocation: TSignalRCore_Invocation);
begin
if Invocation.Target = 'SendMessage' then
... your code here ...
end;
// Once invocation is completed, call Completion method to inform server invocation is finished.
// If result is successful, then call CompletionResult method:
SignalRCore.CompletionResult('id-000002', 'ok');
// If not, then call CompletionError method:
SignalRCore.CompletionError('id-000002', 'Error processing invocation.');
Wird vom Client gesendet, wenn eine Verbindung geschlossen wird. Enthält einen Fehlergrund, wenn die Verbindung aufgrund eines Fehlers geschlossen wurde.
SignalRCore.Close('Unexpected message').
// If the server close connection by any reason, OnSignalRCoreClose event will be called.
procedure OnSignalRCoreClose(Sender: TObject; Close: TSignalRCore_Close);
begin
DoLog('#closed: ' + Close.Error);
end;
Das SignalR-Hub-Protokoll unterstützt "Keep Alive"-Nachrichten, die verwendet werden, um sicherzustellen, dass die zugrunde liegende Transportverbindung aktiv bleibt. Diese Nachrichten helfen sicherzustellen:
Dass Proxys die zugrunde liegende Verbindung während Leerlaufzeiten nicht schließen (wenn nur wenige Nachrichten gesendet werden). Wenn die zugrunde liegende Verbindung abbricht, ohne ordnungsgemäß beendet zu werden, wird die Anwendung so schnell wie möglich informiert.
Das Keep-Alive-Verhalten wird durch Aufrufen der Ping-Methode oder Aktivieren von HeartBeat auf dem WebSocket-Client erreicht. Wenn der Server einen Ping an den Client sendet, sendet der Client automatisch eine Antwort und das Ereignis OnSignalRCoreKeepAlive wird aufgerufen.
procedure OnSignalRCoreKeepAlive(Sender: TObject);
begin
DoLog('#keepalive');
end;
In der MsgPack-Codierung des SignalR-Protokolls wird jede Nachricht als einzelnes MsgPack-Array dargestellt, das Items enthält, die Eigenschaften der jeweiligen Hub-Protokoll-Nachricht entsprechen. Die Array-Items können primitive Werte, Arrays (z. B. Methodenargumente) oder Objekte (z. B. Argumentwerte) sein. Das erste Item im Array ist der Nachrichtentyp.
Lesen Sie die MessagePack-Dokumentation, um zu sehen, wie die gesendeten Nachrichten codiert werden.
Jedes Mal, wenn eine neue Nachricht empfangen wird, wird sie im Ereignis OnSignalRCoreMessagePack zugestellt. Auf die Nachricht kann durch Lesen des Stream-Parameters Data zugegriffen werden. Der Parameter JSON ist standardmäßig leer; wenn Sie die MessagePack-Nachricht in JSON konvertieren, verarbeitet die Komponente die JSON-Nachricht so, als ob die Kodierung JSON verwendet hätte (sodass die Ereignisse OnSignalRCoreCompletion, OnSignalRCoreInvocation... zugestellt werden).