sgcWebSockets 2026.7:集群、消息历史、async / await 与更安全的客户端 | eSeGeCe 博客

sgcWebSockets 2026.7:集群、消息历史、async / await 与更安全的客户端

· 版本发布

sgcWebSockets 2026.7 关注的是生产环境中的运行。你现在可以把一个 WebSocket 应用铺开到多台服务器上,并让频道、发布/订阅和在线状态在所有服务器之间正常工作。频道可以记住它最近的消息,这样一个断开又回来的客户端就能拿到它错过的内容。客户端会以带抖动的指数退避方式重连,而不是齐刷刷地猛击服务器;发送队列可以设定上限,这样一个慢速消费者就不会耗尽服务器内存;HTTP 客户端还获得了一系列防护,用于应对会泄露凭据的重定向、被降级的 TLS 以及没有大小限制的响应。

除此之外,还有面向 Delphi 的 async / await 层、HTTP 服务器上的 gzip 和 deflate、新的 HTTP QUERY 方法、端到端加密的身份验证、MCP 客户端上的 OAuth2,以及一长串修复。本文是一次导览。每一节都链接到深入介绍该功能的文章。

跨多台服务器的集群

新的 TsgcWSCluster 组件把多台 sgcWebSockets 服务器连成一台逻辑服务器。在节点 A 上发布的消息可以送达连接在节点 B 上的订阅者,而在线状态返回的是整个集群的成员名单,而不只是本地节点的。有两种后端可选:内置的 mesh,节点之间直接通信,你无需额外安装任何东西;以及面向更大规模部署的 Redis Pub/Sub(企业版)。

uses
  sgcWebSocket, sgcWebSocket_Protocols, sgcWebSocket_Cluster;

oCluster := TsgcWSCluster.Create(nil);
oCluster.EngineType  := clusterMesh;          // no extra infrastructure
oCluster.ClusterPort := 5410;                 // this node's mesh listener
oCluster.Peers.Add('192.168.1.101:5410');     // the other nodes
oCluster.Attach(oProtocol);                   // cluster this protocol's pub/sub
oCluster.Start;

了解更多:跨多节点扩展 WebSocket 服务器

消息历史与重连恢复

sgc 协议服务器现在可以保留每个频道最近的 N 条消息,还可以选择带上存活时间。当客户端重连并重新订阅时,它会发送自己实际看到的最后一条消息的偏移量,服务器则会重放它离线期间发布的所有内容。除了你本来就会做的重新订阅之外,客户端代码无需任何改动,而且这整套机制在你启用之前都是关闭的。

oProtocol.History.Enabled    := True;
oProtocol.History.Size       := 1000;   // last 1000 messages per channel
oProtocol.History.TTLSeconds := 3600;   // optional: also expire after 1 hour

了解更多:消息历史与重连恢复

async / await,以及一个真正会等待的 Connect

新的 sgcBase_AsyncAwait 单元为库带来了任务和 future。你可以 await 一个 HTTP 请求、一个 WebSocket 连接或一次 AI 聊天补全,而无需接线任何事件处理器,可以用 ThenProcOnError 串联结果,也可以以真正中止底层请求的方式取消一个任务。

本着同样的精神,Connect 现在会阻塞直到连接完全建立,而不只是已经开始建立,Disconnect 也会等到套接字真正关闭。这消除了以往在快速重连时会咬人的一个竞态。当连接或发送失败时,原因可以从新的 LastError 属性中获得,而且还有一个 Connect 重载会直接把错误字符串返回给你,这样你无需订阅 OnError 就能诊断失败。

var
  vError: string;
begin
  if sgcWebSocketClient1.Connect(10000) then
    sgcWebSocketClient1.WriteData('hello')
  else
    ShowMessage('Connect failed: ' + sgcWebSocketClient1.LastError);

  // or get the reason back directly from the call
  if not sgcWebSocketClient1.Connect(vError, 10000) then
    ShowMessage('Connect failed: ' + vError);
end;

了解更多:Delphi 中的 async / await

加固 HTTP 客户端

重定向是丢掉令牌最容易的方式。如果服务器用一个指向另一台主机的 302 来回应你已认证的请求,客户端会乐呵呵地把 AuthorizationCookie 请求头原样重放过去。2026.7 新增了 StripAuthOnCrossHostRedirect,用于在跨站重定向时丢弃这些请求头,以及 NoInsecureRedirect,用于拒绝把 HTTPS 降级为明文 HTTP 的重定向。

响应现在也可以设限了,MaxChunkSize 限制分块响应中单个块的大小,MaxResponseSize 限制总大小。RejectPublicSuffixCookies 会丢弃那些声称属于过于宽泛的域(例如 "com" 或 "co.uk")的 cookie。上传方面提供了 Expect: 100-continue,它会在发送大体积请求体之前先征得服务器同意,此外还支持分块请求体,用于流式上传你尚不知道大小的内容。

oHTTP.StripAuthOnCrossHostRedirect := True;
oHTTP.NoInsecureRedirect           := True;
oHTTP.MaxResponseSize              := 10 * 1024 * 1024;  // 10 MB, 0 = unlimited
oHTTP.MaxChunkSize                 := 1024 * 1024;       // 1 MB per chunk
oHTTP.RejectPublicSuffixCookies    := True;

所有这些开关默认都是关闭的,而且都不需要你去碰底层的 HTTP 对象:它们同样发布在现成的 API 客户端上(AI/LLM、加密货币交易所、OAuth2、Google Cloud、WhatsApp、AWS SQS、WebPush)。重定向和大小防护同样存在于 HTTP/2 客户端上,而 HTTP/3 获得了响应大小限制。了解更多:加固 HTTP 客户端

重试、退避与抖动

WatchDog 过去以固定间隔重连,这意味着一千个同时失去服务器的客户端会在同一瞬间一起回来。它现在支持指数退避,带有倍数、上限和随机抖动,这样整个客户端集群就会把自己摊开。固定间隔仍然是默认值,所以在你主动要求之前什么都不会变。

HTTP 客户端也获得了自己的重试策略:在可配置的一组状态码上重试,每次等待更久,并在服务器发送 Retry-After 提示时遵守它。OpenAI、Anthropic 和 Gemini 客户端也会使用它。

sgcWebSocketClient1.WatchDog.Backoff           := wdbExponential;
sgcWebSocketClient1.WatchDog.BackoffMultiplier := 2.0;   // double every attempt
sgcWebSocketClient1.WatchDog.MaxInterval       := 60;    // seconds, the ceiling
sgcWebSocketClient1.WatchDog.Jitter            := 0.2;   // up to 20% random spread

了解更多:指数退避、抖动与 Retry-After

发送队列上的背压

当客户端读取的速度慢于你写入的速度时,排队的消息就会在服务器内存中堆积。你现在可以用 QueueOptions.MaxQueueSize 限制每个连接的待发消息数量,并选择达到上限时发生什么:丢弃最旧的消息、丢弃最新的消息,或者断开该连接。新的 OnSendBufferFull 事件会在任何消息被丢弃之前触发,因此你可以记录它、统计它,或者对某个你承受不起截断的连接否决这次丢弃。队列默认是无限制的。

oServer.QueueOptions.MaxQueueSize   := 1000;
oServer.QueueOptions.OverflowPolicy := qopDropOldest;  // or qopDropNewest, qopDisconnect

了解更多:面向慢速 WebSocket 客户端的发送队列限制

HTTP 服务器上的 gzip 和 deflate

只要客户端声明了 Accept-Encoding,HTTP 服务器现在就可以压缩它的回复。它适用于从 DocumentRoot 提供的文件,也适用于你自己在 OnCommandGet 中构造的响应;它会跳过低于最小大小的正文以及压缩后不会变小的内容类型,并且在 HTTP.sys 服务器上同样可用。压缩默认关闭,目前只压缩 HTTP/1.1 的回复。

sgcWebSocketHTTPServer1.HTTPCompression.Enabled := True;
sgcWebSocketHTTPServer1.HTTPCompression.Level   := 6;     // 1..9
sgcWebSocketHTTPServer1.HTTPCompression.MinSize := 1024;  // bytes

了解更多:HTTP 服务器上的 gzip 与 deflate 压缩

HTTP QUERY 方法

QUERY 是一个新的 IETF HTTP 方法,它像 POST 一样在请求体中发送查询条件,却像 GET 一样保持安全且可缓存,因此你的查询不再被硬塞进一个有长度限制的 URL 里。2026.7 在 HTTP/1.x、HTTP/2、HTTP/3 和 REST 客户端上实现了它,服务器和代理也能处理它。

vResult := oClient.Query('https://api.example.org/contacts', oQuery);

了解更多:Delphi 中全新的 HTTP QUERY 方法

E2EE 身份验证

端到端加密可以让载荷保持私密,但它本身并不能告诉你另一端是谁。中间的服务器或中继可以给每一方各发一份自己的密钥,然后读取全部内容。在 2026.7 中,每一方都可以用一个长期身份密钥为自己的加密密钥签名,另一方则验证该签名,这样悄无声息的密钥掉包就会被检测出来,而不是被接受。

它适用于一对一和群聊,还新增了用于批准或固定对端身份、以及在对端身份密钥发生变化时向你发出警告的事件;它默认关闭,并且与 2026.6 的对端完全向后兼容。

E2EE.E2EE_Options.Identity.Enabled    := True;
E2EE.E2EE_Options.Identity.PrivateKey := LoadIdentityPrivateKey;  // PEM
E2EE.E2EE_Options.Identity.PublicKey  := LoadIdentityPublicKey;   // PEM
E2EE.OnE2EEVerifyPeerIdentity := OnE2EEVerifyPeerIdentityEvent;
E2EE.OnE2EEKeyChange          := OnE2EEKeyChangeEvent;

了解更多:E2EE 身份验证

MCP 客户端的 OAuth2,以及 Identity Assertion 授权类型

MCP 客户端现在可以使用 OAuth2 认证,而不是静态的 API 密钥。它会自己获取令牌,在过期之前一直缓存并刷新它,而当 OAuth2 启用时,它的优先级高于 API 密钥。与此同时,OAuth2 客户端新增了一种授权类型,即 Identity Assertion Authorization Grant,它可以把一个域中的身份传递到另一个域:客户端会替你完成整个多步骤交换,并触发事件让你能跟踪每一步。

MCPClient.MCPOptions.AuthenticationOptions.OAuth2.Enabled := True;
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.GrantType := auth2ClientCredentials;
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.TokenURL := 'https://auth.example.com/oauth2/token';
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.ClientId := 'YOUR_CLIENT_ID';
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.ClientSecret := 'YOUR_CLIENT_SECRET';

了解更多:MCP 客户端的 OAuth2

加密货币行情:重新订阅与限流

直到现在,当一个行情套接字断开而 WatchDog 把它拉回来之后,套接字是通了,但在你自己重放每一个订阅之前,你其实什么都没订阅。交易所客户端现在可以替你做这件事:设置 Resubscribe := True,你的数据流就会在重连后被恢复。它覆盖 Binance、Kraken、Coinbase 以及另外大约十四家交易所,还有 XTB 基于会话的行情。它默认为 False。

REST API 客户端获得了一个可选的客户端限流器,这样一波突发请求就不会让你被交易所临时封禁。限制你在一个时间窗口内发送的请求数量,并选择超额的请求是等待还是被拒绝。默认关闭。

oBinance.RateLimit.Enabled     := True;
oBinance.RateLimit.IntervalMs  := 60000;   // one minute window
oBinance.RateLimit.MaxRequests := 1000;
oBinance.RateLimit.Behavior    := rlbWait;

了解更多:重新订阅与客户端限流器

较小的新增内容

修复与加固

2026.7 带来了一长串修复。与其把它们全部重复一遍,不如看看它们合起来意味着什么。

内存安全与解析器加固。修复了越界读取问题,涉及 MQTT 5 客户端(broker 声称的数据量多于它实际发送的量)、AMQP 1.0 客户端(UUID 解码)、STUN/TURN 解析器(包括一个死循环和 IPv6 地址)、WebRTC SCTP 数据通道以及原始 TCP 帧扫描器。UDP 客户端和服务器(包括 DTLS)在断开时不会再重复释放套接字或让它悬空。在行为异常的对端本可无限制增长内存的地方,加入了内部上限:MQTT 待发消息队列、sgc 协议的每连接订阅数、WAMP 服务器的待处理调用、分块响应的 trailer 行,以及 AMQP 字段表的嵌套深度。

HTTP/2。一个解压后大小上限(默认 64 MB)堵住了 "zip 炸弹" 响应导致的内存耗尽漏洞,响应大小限制不再能被响应的最后一部分绕过,而失败的请求现在会立即失败,而不是把整个超时耗完。

加密与随机数。WebAuthn 服务器、OAuth2 授权服务器以及内置的 HTTP 会话 id 现在使用密码学安全的随机数生成器,挑战值一次性使用,机密以常量时间进行比较(客户端密钥、JWT 签名、HTTP API Basic 密码)。签名有效但声明有误的 JWT 现在会被拒绝,而不是被接受。启用 OpenSSL 的 StrictVerify 后,证书还会针对主机名进行校验,因此一张为其他主机签发的有效证书会被拒绝。连接到 IP 地址的情形以及现有设置不受影响。

Server-Sent Events。一个流式响应如果在一次读取中送来多个事件,或者一个事件被拆分在两次读取中,除第一个之外的内容都会丢失。此问题已修复,这也恢复了 AI/LLM 客户端逐 token 的流式输出。OpenAI 流式聊天此前还漏设了 stream 标志,现在它会像其他提供方一样流式输出。

交易所客户端。Kraken 的请求 id 现在严格递增,BitMEX 以秒而不是毫秒发送其过期时间,MEXC 对它实际发送的编码参数进行签名,Cryptorobotics 在按哈希调用中不再交换两个值,Deribit 会在登录令牌过期前刷新它,而 Forex 客户端会报告它以往在读取账户 id 时吞掉的错误。

Lazarus 与 Free Pascal。三处编译修复:JSON 单元的接口现在按平台使用正确的调用约定(此前在 Linux 上无法编译)、Forex 客户端不再调用仅限 Delphi 的函数来读取数字格式,以及后量子密钥交换单元不再依赖 size_t

history.txt 中还有更多内容,包括 gRPC 客户端(并发调用可能收到彼此的回复,且发送失败被报告为成功)、OpenAPI 服务器(规范加载现在是线程安全的,而且你的处理器不会再在内部锁被持有时运行)、每次登录失败都会造成的服务器连接与套接字泄漏、对没有 "message" 字段的 OpenAI 回复(例如来自 Ollama 和 LM Studio 的回复)导致的崩溃、WinHTTP 客户端上修复的 WriteAndWaitData,以及 HTTP 客户端日志中的机密脱敏,这样启用日志不会再把令牌以明文写到磁盘上。

.NET 版本

sgcWebSockets .NET 2026.7 的重点是 HTTP 客户端。它提供了与 Delphi 版本相同的一整套防护:跨站重定向时剥离凭据、阻止 HTTPS 到 HTTP 的降级、单块与总响应大小上限、公共后缀 cookie 拒绝、Expect: 100-continue 以及分块请求体。这些功能在 HTTP 客户端组件和现成的 API 客户端上均可用(AI/LLM 客户端、OAuth2、Google Cloud、WhatsApp、AWS SQS、WebPush),同样的重定向与大小防护也存在于 HTTP/2 客户端上。重定向处理和 cookie 过期的修复也一并包含在内。所有功能默认关闭。

升级

对现有的 2026.x 项目而言,2026.7 是一次即插即用的升级。本次发布中的每一项新功能默认都是关闭的:集群、消息历史、退避、发送队列上限、HTTP 压缩、客户端防护、E2EE 身份验证以及交易所重新订阅,全都必须显式打开,因此安装新版本在你主动启用之前不会改变任何行为。

拥有有效订阅的客户可以从客户专区下载新版本,或从 esegece.com/products/websockets/download 下载。

有疑问、建议,或者需要迁移方面的帮助?联系我们。回复你的将是编写这些代码的人。