ASP.NET Core SignalR 是一个开源库,可简化向应用程序添加实时 Web 功能的过程。实时 Web 功能使服务器端代码能够即时向客户端推送内容。
SignalR 的良好候选场景:
SignalRCore sgcWebSockets 组件使用 WebSocket 作为传输方式连接到 SignalRCore 服务器,如果此传输方式不受支持,将引发错误。
SignalRCore 使用集线器(Hub)在客户端与服务器之间进行通信。SignalRCore 提供两种集线器协议:基于 JSON 的文本协议和基于 MessagePack 的二进制协议。sgcWebSockets 组件仅实现了 JSON 文本协议,用于与 SignalRCore 服务器通信。
要配置 Hub 客户端将使用哪个 Hub,只需在客户端连接到服务器之前,在 SignalRCore/Hub 属性中设置 Hub 的名称。
当客户端向服务器打开新连接时,会发送包含格式协议和版本的请求消息。sgcWebSockets 始终以 JSON 格式发送协议。若服务器不支持该协议,将回复错误,可通过 OnSignalRCoreError 事件处理;若连接成功,则调用 OnSignalRCoreConnect 事件。
当客户端连接到 SignalRCore 服务器时,可以发送 ConnectionId 在会话间标识客户端。要获取新的连接 ID,只需正常连接到服务器并通过 OnBeforeConnectEvent 获知 ConnectionId。若要重连并传递之前的连接 ID,请使用 ReConnect 方法并将 ConnectionId 作为参数传入。
SignalR 协议是一种基于消息传输的双向 RPC 协议。连接的任意一方均可调用另一方的过程,过程可返回零个或多个结果或一个错误。示例:客户端可以请求服务器的方法,服务器也可以请求客户端的方法。服务器与客户端之间交换以下消息:
HandshakeRequest:客户端发送给服务器,用于协商消息格式。
HandshakeResponse:服务器向客户端回复对之前 HandshakeRequest 消息的确认。如果握手失败,则包含错误。
Close:当连接关闭时由客户端或服务器调用。如果连接因错误关闭,则包含错误信息。
调用:客户端或服务器向另一个对等方发送消息,以调用带参数或不带参数的方法。
StreamInvocation:客户端或服务器向另一对端发送消息,以调用带参数或不带参数的流式方法。响应将被拆分为不同的项目。
StreamItem:是来自先前 StreamInvocation 的响应。
Completion:表示上一次调用或 StreamInvocation 已完成。如果处理成功则包含结果,如果出错则包含错误信息。
CancelInvocation:取消之前的 StreamInvocation 请求。
Ping:用于检查连接是否仍然存活的消息。
SignalRCore 允许使用以下编码方式:
目前仅支持 JSON,但可以使用外部 MessagePack 库对发送的消息进行编码,从而使用 MessagePack。有关更多信息,请参阅下方的 MessagePack 章节。
编码协议的配置在 SignalRCore.Protocol 属性中定义。默认值为 srcpJSON。
可以启用身份验证,将用户与每个连接关联,并过滤哪些用户可以访问资源。身份验证使用 Bearer 令牌实现:客户端提供访问令牌,服务器验证此令牌并使用它来识别用户。
在标准 Web API 中,承载令牌通过 HTTP 标头发送,但使用 WebSocket 时,令牌作为查询字符串参数传输。
支持以下方法:
srcaRequestToken
若启用了身份验证,流程如下:
1. 首先尝试从服务器获取有效令牌。打开到 Authentication.RequestToken.URL 的 HTTP 连接,并使用用户名和密码数据进行 POST。
2. 如果前一步成功,则返回令牌;否则返回错误。
3. 若返回令牌,则打开新的 HTTP 连接进行协商。此时令牌作为 HTTP 头传递。
4. 如果上一步成功,打开 WebSocket 连接并将令牌作为查询字符串参数传递。
Authentication.Enabled:若激活,则在建立 WebSocket 连接之前需进行授权。
Authentication.Username:提供给服务器用于身份验证的用户名。
Authentication.Password:提供给服务器以进行身份验证的密码。
Authentication.RequestToken.PostFieldUsername:用于传输用户名的字段名称(取决于配置,请查看 HTTP JavaScript 页面以了解使用的名称)。
Authentication.RequestToken.PostFieldPassword: 传输密码的字段名称(取决于配置,请查看 http javascript 页面以了解所用名称)。
Authentication.RequestToken.URL:请求令牌的 URL。
Authentication.RequestToken.QueryFieldToken:WebSocket 连接中使用的查询字符串参数名称。
srcaSetToken
在此处,您直接将令牌传递给 SignalRCore 服务器(因为令牌是从另一台服务器获取的)。
Authentication.Enabled:如果激活,在建立 WebSocket 连接之前将使用授权。
Authentication.SetToken.Token:获取到的令牌值。
访问令牌可以作为查询参数发送(这是默认选项),也可以作为 Bearer Token 在 HTTP 头中发送。使用属性 Authentication.TokenParam 配置此行为。
srcaBasic
此选项使用基本身份验证,此身份验证方法需要配置 SignalRCore 组件和 TsgcWebSocketClient。
示例:如果服务器需要基本身份验证,用户名为"user",密码为"secret",请按如下所示配置组件。
// 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;
服务器与客户端之间存在三种交互类型:
调用方向被调用方发送消息,并期待一条表明调用已完成的消息,以及调用结果(可选)
示例:客户端调用 SendMessage 方法,并将用户名和文本消息作为参数传递。发送调用 ID 以
从服务器获取结果消息。
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;
调用方向被调用方发送一条消息,且不期望收到此次调用的任何后续消息。调用时可不带调用 ID 值,这表明该调用为"非阻塞"。
示例:客户端调用 SendMessage 方法,并将用户名和文本消息作为参数传入。客户端不期望服务器返回任何关于调用结果的响应。
SignalRCore.Invoke('SendMessage', ['John', 'Hello All.']);
调用方向被调用方发送消息,并期望被调用方返回一个或多个结果,最后跟随一条表示调用结束的消息。
示例:客户端调用 Counter 方法,请求以 500 毫秒的间隔返回 10 个数字。
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;
为了执行单次调用,调用方遵循以下基本流程:
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);
为调用分配唯一的 Invocation ID 值(由调用方选择的任意字符串)。调用包含目标对象、参数和 InvocationId 的 Invoke 或 InvokeStream 方法(如果不发送 InvocationId,则不会收到完成结果)。
如果调用被标记为非阻塞(参见下方"非阻塞调用"),则在此处停止并立即将控制权交还给应用程序。使用匹配的调用 ID 处理 StreamItem 或 Completion 消息。
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;
您可以进行单次调用并等待完成。
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;
为调用分配唯一的调用 ID 值(任意字符串,由调用方选定)。调用 InvokeAndWait 或 InvokeStreamAndWait 方法,传入被调用的目标、参数和调用 ID。程序将等待直至完成事件被触发或超时。
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;
如果客户端希望在服务器发送 Completion 消息之前停止接收 StreamItem 消息,客户端可以发送与启动流的 StreamInvocation 消息使用相同 InvocationId 的 CancelInvocation 消息。
procedure OnSignalRCoreStreamItem(Sender: TObject; StreamItem: SignalRCore_StreamItem; var Cancel: Boolean);
begin
if StreamItem.InvocationId = 'id-000002' then
Cancel := True;
end;
只有收到完成消息时,调用才被视为完成。如果客户端从服务器接收到调用,将触发 OnSignalRCoreInvocation 事件。
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.');
由客户端在连接关闭时发送。如果连接因错误而关闭,则包含错误原因。
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;
SignalR Hub 协议支持"保活"消息,用于确保底层传输连接保持活跃。这些消息有助于确保:
在空闲期间(消息发送较少时),代理不会关闭底层连接。如果底层连接在未正常终止的情况下断开,应用程序将尽快得到通知。
保活行为通过调用 Ping 方法或在 WebSocket 客户端上启用 HeartBeat 来实现。如果服务器向客户端发送 Ping,客户端将自动发送响应,并调用 OnSignalRCoreKeepAlive 事件。
procedure OnSignalRCoreKeepAlive(Sender: TObject);
begin
DoLog('#keepalive');
end;
在 SignalR 协议的 MsgPack 编码中,每条消息表示为一个单独的 MsgPack 数组,其中包含与给定 hub 协议消息属性对应的项。数组项可以是基本值、数组(例如方法参数)或对象(例如参数值)。数组中的第一项是消息类型。
请参阅 MessagePack 文档,了解如何对发送的消息进行编码。
每次收到新消息时,都会在 OnSignalRCoreMessagePack 事件中分发。消息内容可通过读取 Data Stream 参数获取。JSON 参数默认为空;如果您将 MessagePack 消息转换为 JSON,组件将像处理 JSON 编码消息一样处理该 JSON(因此 OnSignalRCoreCompletion、OnSignalRCoreInvocation 等事件将被触发)。