sgcWebSockets 2026.8 是今年最大的一次发布。新增了四个传输组件,在客户端和服务端同时带来 QUIC 与 HTTP/3;OpenSSL 现在可以链接进您的可执行文件,让 TLS 应用不再需要附带任何一个 DLL;sgcHTML 新增三十多个组件,加入了 WebBroker 与 DataSnap 调度器,并且能够适配手机屏幕。
在底层,SChannel TLS 层被彻底拆开重建。如果您使用 Windows 的 TLS 栈,这就是必须安装的版本:TLSOptions.Version 会被悄悄忽略,已吊销的证书会被接受,TLS 1.3 连接永远无法完成,重新协商会在完全不做检查的情况下替换证书。这些问题现已全部修复,同时提供了吊销检查、版本下限以及服务端的客户端证书验证。本文余下部分是一次导览,每一节都会链接到深入介绍该功能的文章。
QUIC 与 HTTP/3
四个新组件为 Delphi 和 C++ Builder 带来了 QUIC(RFC 9000)与 HTTP/3(RFC 9114):TsgcQUICClient、TsgcQUICServer、TsgcHTTP3Client 和 TsgcHTTP3Server。它们运行在原生的 OpenSSL 3.5 QUIC 引擎之上,因此无需部署任何第三方协议栈,同时您可以获得融入握手过程的 TLS 1.3、没有队头阻塞的多路复用流、0-RTT 会话恢复,以及客户端切换网络时的连接迁移。
uses
sgcQUIC_Client;
var
oClient: TsgcQUICClient;
begin
oClient := TsgcQUICClient.Create(nil);
oClient.Host := 'www.example.com';
oClient.Port := 443;
oClient.OnQUICConnect := OnQUICConnect;
oClient.OnQUICStreamData := OnQUICStreamData;
oClient.Active := True; // TLS 1.3 handshake in a single flight
oClient.WriteData('ping'); // send bytes on a QUIC stream
end;
HTTP/3 客户端支持所有 HTTP 方法,提供 QPACK 头部压缩,可以阻塞或异步工作,能够通过 Alt-Svc 发现 HTTP/3 端点,并处理服务器推送。
uses
sgcHTTP3_Client;
var
oClient: TsgcHTTP3Client;
vBody: string;
begin
oClient := TsgcHTTP3Client.Create(nil);
oClient.OnResponse := OnResponse;
oClient.OnAltSvc := OnAltSvc;
oClient.Connect('www.example.com', 443);
vBody := oClient.Get('https://www.example.com/');
end;
两者都需要 sgcWebSockets Enterprise 中的 sgcQUIC 包。延伸阅读:QUIC 客户端与服务器组件 以及 HTTP/3 客户端与服务器组件。
可执行文件内置 OpenSSL
把 libcrypto 和 libssl 与应用程序一起部署,一直是交付 TLS 客户端时最不愉快的一环。从 2026.8 开始,本库可以静态链接 OpenSSL 3.5.7:在项目的 uses 子句中加入一个单元,DLL 就消失了。移除该单元,DLL 又会像以前一样被加载,因此无论哪个方向,都只是一行代码的改动。
uses
sgcWebSocket, sgcWebSocket_Classes,
sgcIdSSLOpenSSL_Static;
它涵盖客户端与服务器、32 位与 64 位,自 Delphi XE2 起可用。延伸阅读:静态链接 OpenSSL,告别 DLL。
sgcHTML:WebBroker、DataSnap 与三十个新组件
sgcHTML 页面不再绑定在 sgcWebSockets 服务器上。一个新的调度器组件可以从任意 WebBroker 应用(独立运行、ISAPI、Apache 或 CGI)提供这些页面,也可以从 DataSnap 服务器提供,其桥接会在与您的 REST 端点相同的端口上加入实时 WebSocket 更新。
uses
Web.HTTPApp, sgcHTMX_Engine_Server_WebBroker, sgcHTMX_Router;
// Any WebBroker host: the engine's Owner is the web module,
// so the standard WebBroker dispatcher calls it automatically
FEngine := TsgcHTMX_Engine_Server_WebBroker.Create(WebModule1);
FEngine.Router := FRouter; // your htmx routes and the page
本次发布中组件集大幅扩充,并且所有内容仍然完全在服务器端绘制,无需加载任何客户端库:
- 数据。 TreeGrid、PivotTable、ActivityFeed 和可调整大小的 Splitter,另外还有 Excel 导出、每一行下方的明细面板,以及 Grid 上可记忆的列与排序选择。延伸阅读:四个用于结构化数据的全新 sgcHTML 组件。
- 可视化。 Heatmap、Sparkline、CandlestickChart 和 TreeMap,另有 QRCode 与 Barcode,全部以内联 SVG 渲染。延伸阅读:六个全新的 sgcHTML 可视化组件。
- 表单。 MultiSelect、ColorPicker、Slider、TimePicker、SignaturePad 和 Transfer,并带有日期时间选择器与日期范围选择器。延伸阅读:六个全新的 sgcHTML 表单输入组件。
- 反馈。 Badge、PDFViewer、ContextMenu、ProgressBar、JobProgress 和 LogViewer。延伸阅读:六个全新的 sgcHTML 反馈组件。
- 管理。 Presence、RolesPermissions 和 UserManagement。它们只负责绘制界面,每个用户能做什么仍由您自己的服务器决定。延伸阅读:三个全新的 sgcHTML 管理组件。
现在页面也会适配屏幕尺寸。在手机上,侧边菜单会收起到一个按钮后面,内容占满整个宽度;在桌面端则毫无变化。把新的 Responsive 属性设为 False 即可恢复此前的固定布局。延伸阅读:sgcHTML 用于 WebBroker 与 DataSnap。
SChannel TLS 全面改造
SChannel 是 Windows 的 TLS 栈,完全不部署 OpenSSL 就能使用。它存在一批从外部看不出来的问题,而这是最糟糕的一类。在 Windows 11 和 Windows Server 2022 上,TLSOptions.Version 根本没有被读取;在 Windows 10 上请求 TLS 1.3 会悄悄退回到 TLS 1.2;而不设置版本则会把 SSL 3.0、TLS 1.0 和 TLS 1.1 重新打开。即使 VerifyCertificate 设为 True,已吊销的服务器证书仍会被接受,因为构建证书链时根本没有请求任何吊销状态。重新协商之后,新证书会被接受,而不检查证书链或主机名。Indy 在旁路打开的连接,比如 HTTP 重定向到另一台主机,或者 FTP 数据通道,返回的是空配置,于是它们在关闭验证的状态下运行,而且没有任何提示。
这些问题现已全部修复,握手完成时还会检查协商出的版本,并随之带来三组新选项:吊销检查、版本下限,以及 Windows 强加密。
// client
oClient.TLSOptions.Version := tls1_3; // highest version to use
oClient.TLSOptions.SChannel_Options.VersionMin := tls1_2; // lowest accepted
oClient.TLSOptions.SChannel_Options.UseStrongCrypto := True;
oClient.TLSOptions.SChannel_Options.Revocation.Check := scrcChainExcludeRoot;
oClient.TLSOptions.SChannel_Options.Revocation.Timeout := 5000; // ms
// or all of it in one line
oClient.TLSOptions.Preset := tlspSecureDefaults;
吊销检查的默认值刻意设得比较宽松:IgnoreRevocationOffline 和 IgnoreNoRevocationCheck 为 True,因此打开检查不会破坏原本可用的连接;而 Timeout 会限制 CRL 与 OCSP 的获取时间,使得无法访问的响应服务器不会拖住握手。真正被吊销的证书则始终会被拒绝。
在服务端,SChannel 现在可以向客户端索要证书,这是它此前从未做过的。客户端证书会经过与客户端验证服务器证书相同的检查,包括证书链、有效期和 OnSChannelVerifyPeer 事件,只是不做主机名检查。
oServer.SSLOptions.VerifyCertificate := True;
oServer.SSLOptions.VerifyCertificate_Options.FailIfNoCertificate := True;
还有两项修复也属于这里。当对端未发送关闭通知就断开连接时(例如代理重启),使用 SChannel 的客户端根本察觉不到连接已经消失,于是 OnDisconnect 从不触发,WatchDog 也从不运行。另外,TLS 1.3 连接根本无法完成,因为服务器在握手后立即发送的会话票据,会让客户端一直等待服务器其实已经发出的数据。现在握手会遵守 ConnectTimeout,而不只是覆盖 TCP 连接过程;并且在关闭前会发送关闭通知,SChannel、Apple 和 Android 连接均是如此。
服务器限制与 IOCP / EPOLL 引擎
服务器新增了一组此前没有上限的限制。WebSocket 服务器默认接受 10,000 个并发连接,而不是无限个,并限制每个客户端每秒可发送的控制帧数量(100),使其不会被 ping 淹没。HTTP/2 服务器新增了 MaxRequestSize,DTLS 组件新增了 MaxConnections、HandshakeTimeout 和 IdleTimeout,这样来自伪造地址的大量单包洪流就不会再耗尽服务器内存。所有这些都可以调高,或设为 0 以恢复此前的行为。
高性能引擎得到了一长串修复:多条清理路径上的内存与句柄泄漏和崩溃、客户端在被接受后立即断线、服务器在清理过程中停止时连接被释放两次、启用工作线程时的释放后使用,以及每处理一条消息就丢失一个缓冲区。HTTP keep-alive 完全没有生效,于是每个请求之后连接都会被关闭,服务器上堆满处于 TIME_WAIT 状态的套接字。在 Linux 上,每个 HTTP 请求都会泄漏已解析请求的部分内容,而且所有工作线程共用一个连接队列,导致一两个线程承担了几乎全部工作。
另有两个新选项作为收尾,两者默认都关闭:keep-alive 探测与空闲超时,使半开套接字不再堆积;以及按轮询方式把新连接分配到各个工作线程。
Server.IOHandlerOptions.IOHandlerType := iohEPOLL;
Server.IOHandlerOptions.EPOLL.TCPKeepAlive.Enabled := True;
Server.IOHandlerOptions.EPOLL.TCPKeepAlive.Time := 60; // seconds idle before probing
Server.IOHandlerOptions.EPOLL.TCPKeepAlive.Interval := 10; // seconds between probes
Server.IOHandlerOptions.KeepAliveTimeout := 120; // seconds
延伸阅读:面向 IOCP 与 EPOLL 服务器的 TCPKeepAlive 与 KeepAliveTimeout。感谢 Andrea 提供的补丁,多项引擎修复正是以此为基础。
协议:STOMP、MQTT、AMQP 与 HTTP/2
STOMP 得到的改动最多。优雅的 Disconnect 现在会等待代理以回执确认,因此关闭时不会丢失任何内容。消息会完全按代理发送的样子交付,使用 content-length 读取正文,因此正文中可以包含换行符和二进制零。多个帧打包在一条 WebSocket 消息中时会全部得到处理,跨两条消息拆分的帧会被重新组装,二进制帧也不再被忽略。ACK 与 NACK 会发送所协商版本要求的头部,心跳只在服务器确认连接之后才开始。
oSTOMP.DisconnectTimeout := 10000; // ms to wait for the receipt, 0 = return at once
oSTOMP.ACKEx(vMessageId, vSubscriptionId);
ShowMessage('STOMP ' + oSTOMP.NegotiatedVersion);
MQTT 不再构造出会被代理拒绝的数据包,比如带用户名却没有密码的 CONNECT,或者携带较大属性块的 MQTT 5 数据包;MQTT 5 客户端也不再读越数据包末尾:此前一个被截断的数据包会把内存中紧随其后的任何内容当作属性值交给应用程序。它现在会遵循代理告知的信息,从 CONNACK 中返回的 Keep Alive,到只带 Topic Alias 抵达的消息。QoS 2 发布路径会发送正确的后续确认,对被代理拒绝的消息予以丢弃而不是无休止重试,并按合理的节奏重试。
AMQP 1.0 客户端现在可以抵御行为异常的代理:分成小块到达的帧、无效的头部长度、嵌套过深的消息,以及精心构造的、能膨胀成巨量内存的小消息。AMQP 0.9.1 与 1.0 的默认最大帧长度改为 1 MB,而不再是几乎无限。HTTP/2 头部压缩得到保护,可抵御构造的头部;连接会拒绝针对从未打开过的流的 DATA 帧、PRIORITY 帧洪流、流编号复用以及空延续帧洪流;服务器会拒绝包含换行符或空字节的头部名称与值,从而阻止请求走私。重置流引起的内存增长,以及大约 100 个请求之后出现的 “CONCURRENT STREAM limit has been exceeded”,两者均已修复。
MCP 服务器
工具现在可以为其输入公开完整的 JSON Schema,声明带 items 的数组、枚举、整数与可空类型、嵌套对象以及 additionalProperties,某些 MCP 客户端要求这些内容,否则就会拒绝。服务器还会公开 2025-11-25 协议的元数据:readOnlyHint 注解与注解标题、工具图标、服务器标题、网站与图标,以及 tools/list 的任务支持声明。
如果您已经在运行 MCP 服务器,有两项修复很重要。服务器会把内部连接 id 作为一条消息写入客户端用 GET 打开的事件流,VS Code GitHub Copilot 等客户端因此报出 “Failed to parse message”。另外,来源检查只在请求带有某个浏览器从不发送的头部时才会执行,因此它从未针对本应阻止的场景生效:用户访问的网页可以连上其本机上的 MCP 服务器,列出其工具、运行它们并读取结果。现在每个请求都会检查来源,包括浏览器的预检请求。
MCPServer.MCPOptions.HTTPStreamable.AllowedOrigins.Add('https://*.example.com');
加密货币交易所
这部分的大多数改动源自两份客户反馈。以 HTTP 错误状态失败的请求,现在会报告服务器应答时携带的头部,因此 429 上的 Retry-After,或者交易所返回的速率限制计数器,终于可以读取了。现成的 API 客户端会抛出 EsgcHTTPAPIProtocolException,它派生自此前抛出的异常,因此现有的处理代码仍然有效。
try
vResponse := oBinance.GetAggregateTrades('BTCUSDT');
except
on E: EsgcHTTPAPIProtocolException do
begin
if E.ErrorCode = 429 then
Sleep(E.RetryAfterMs);
vWeight := E.GetHeader('X-MBX-USED-WEIGHT-1M');
// E.ResponseHeaders holds the complete list
end;
end;
此前每个交易所客户端在重连后都会一次性把整份订阅列表重新发送出去,而连接才建立了几毫秒,于是限制每秒消息数的交易所会立刻关闭它,如此循环往复。现在重放会按节奏进行,在 Binance 上还会作为合并帧发送。此外还新增了针对您所发送消息的客户端节流。
oBinance.Throttle.Enabled := True;
oBinance.Throttle.MaxMessages := 4; // per window
oBinance.Throttle.IntervalMs := 1000;
// paced reconnect replay is on by default:
// oBinance.Throttle.PaceResubscribe := False; restores the old burst
// subscribe a whole watchlist with ONE frame
oBinance.SubscribeStreams(['btcusdt@trade', 'ethusdt@trade', 'bnbusdt@trade']);
Binance 停用了 Spot 用户数据流所依赖的 listenKey 端点,因此账户、订单和余额更新现在来自 Binance WebSocket API,通过组件自行打开并在重连后续订的第二条连接传输。事件的结构保持不变,您的处理程序仍然有效,但新的订阅需要签名:现在除了 Binance.ApiKey,还必须设置 Binance.ApiSecret。Binance.us 与 Futures 仍然使用 listenKey。USD-M Futures 的行情数据现在由两个地址提供,通过新的 Binance.FuturesStreamEndpoint 属性选择(默认为 bfsePublic,聚合成交、标记价格、K 线和强平流使用 bfseMarket)。
另外还修复了:OKX 的价格和数量在发送前会被四舍五入到小数点后五位,因此在最小变动价位更精细的合约上,下的单并不是您想要的单,低于 0.00001 的数值会被当作零发送。OKX 和 KuCoin 的保活现在会发送各交易所要求的文本 ping,而不是 WebSocket ping,并在收不到 pong 时重新连接。延伸阅读:Binance 速率限制:批量、节流与可读的 429。
zlib 1.3.1 与物料清单
内置的 zlib 从 1.2.12 升级到 1.3.1,修正了 CVE-2022-37434,这是 inflate 中的一个堆越界读取问题。链接的目标文件针对 Delphi 7 到 Delphi 13、32 位与 64 位重新构建,并在每个编译器上验证通过。附带修复一项:在 Delphi 10 Seattle 64 位上 zlib 目标文件根本没有被链接,编译器选用了 32 位目标文件而导致编译失败。
现在每个安装包还会安装 sbom.cdx.json,这是一份 CycloneDX 软件物料清单,列出了构建本库所用的组件及其版本与许可证。Enterprise 与 All-Access 会给出定制版 Indy 及其 zlib 的版本,Core、Standard 与 Professional 则记录所用 Indy 为 Delphi 或 C++ Builder 自带的版本。它在安装包构建时按版本生成,因此始终与您安装的内容一致。延伸阅读:sgcWebSockets 与 sgcOpenAPI 中的 zlib 1.3.1 以及 sgcWebSockets 现在会安装自己的 SBOM。
安全修复
除 TLS 之外,本次发布还封堵了若干漏洞。请阅读下面的清单,如果您的应用中用到了其中某个组件,请升级。
- OAuth2 服务器。 授权码会被发送到请求中指定的任意地址,因为重定向地址从未与注册的地址比对过,因此一个精心构造的链接可以把某个用户的授权码送到别人的站点。现在它必须完全匹配。登录页面在显示应用名称和请求的作用域之前也会先做清理。
- WebAuthn 服务器。 FIDO 元数据文件未经检查就被信任,而且在未设置根证书时检查会被完全跳过,因此伪造的文件可以让服务器接受一个假的验证器。注册时检查的证书扩展解码器同样什么都没有检查,因此被截断或嵌套过深的扩展可能让服务器读取无关的内存。
- Files 协议。 传入的文件名几乎原样使用,因此对端可以发送形如
../../../file的名称,从而在磁盘的任意位置读取、写入或删除文件。删除一侧则完全没有任何防护。 - WinHTTP 客户端。 现在默认会验证服务器证书。此前它会忽略未知的证书颁发机构、错误的主机名和过期的日期,因此
wss连接可能被中间人拦截而不被察觉。 - WebSocket 协议。 打开握手会按标准要求进行校验,无效帧会被拒绝而不是被接受,握手密钥使用安全随机数生成器,而且无论密码是否接近正确,用户名和密码的检查耗时都相同。
- HTTP 客户端。 响应头部的数量限制从未生效,因此服务器可以持续不断地发送头部,直到客户端耗尽内存。把
MaxHeaderLines设为 0(表示不限制)的效果恰恰相反,会丢弃所有响应头部,包括Content-Length和Location。 - OpenAPI 服务器。 在启用
EnforceSecurity但未指定验证事件时,只要请求携带了 api key 或Authorization头部,就能到达具体操作。现在这类请求会返回 401。 - 解析器。 七个 STUN 属性和若干 SOCKS5 应答会读越数据包末尾,而当对端在一个数据报中发送多条记录时,DTLS 连接会写越其读取缓冲区的末尾。
还有一点很容易被忽略:把 TLS 选项从一个组件复制到另一个组件时,大多数选项会丢失。只有 IO 处理器、ALPN 协议和 OpenSSL 选项被复制,因此证书文件、密码、根证书、TLS 版本和 VerifyCertificate 都保持为空,这样配置出来的组件最终什么都不验证。
其他小改进
- WebTransport。 会话上新增
DatagramsSupported属性,告诉您数据报是否真的可以发送。 - Sockets。 连接的套接字绑定上新增
SetReceiveTimeout方法,与SetSendTimeout相对应,这样陷入静默的连接会干净地失败,而不是冻结正在读取的线程。 - 桥接服务器。 由托管在桥接上的 DataSnap 或 WebBroker 应用设置的 Cookie,其日期被序列化为 1899 年,因此浏览器会当作已过期而丢弃;HTTP.sys 桥接还会把多个 Cookie 缩减成一个。现在它们会作为会话 Cookie 连同全部属性一起发送。
- 错误信息。 失败的 TLS 连接此前会报出类似 “error:00000006:lib(0):func(0):EVP lib” 的信息,因为来自 OpenSSL 的真实原因被丢弃了。ML-KEM-768 现在会说明它需要 OpenSSL 3.5 或更高版本并显示找到的版本;使用 RC2 40 位等旧算法的 PKCS#12 文件则会说明如何启用 legacy provider。
- 异常。 修复了 TCP 与 HTTP/2 组件的
OnException事件中的随机崩溃。异常在事件运行之前就被抛出它的线程销毁了,导致处理程序读取已释放的内存并报出错误的类名。
本次发布期间的其他新内容
随本库一起发布的两部分内容本月也有各自的文章:REST 服务器组件,这是一个专为 REST API 打造的 HTTP 服务器,具备 CORS、指标、健康检查端点、多租户以及一个 OpenAPI 插件,可以把一份规范文件变成路由、校验、安全与 Swagger UI;还有 sgcProtoBuf,这个代码生成器可以把 .proto 文件转换成 Delphi 单元。延伸阅读:TsgcHTTPRESTServer:全新的 REST 服务器组件、REST 服务器 + OpenAPI、用户、多租户与指标 以及 sgcProtoBuf:从 .proto 文件到 Delphi 单元。
.NET 版本
sgcWebSockets .NET 2026.8 包含本次发布中共通的那一半内容。SChannel 相关工作全部在内,包括被忽略的版本、克隆连接上丢失的设置、未经检查的重新协商、永远无法完成的 TLS 1.3 连接、未被察觉的断开连接、没有上限的握手和启动竞态,以及关闭前的关闭通知和服务端的客户端证书验证。IOCP 与 EPOLL 的修复、WebSocket 握手与帧校验、MQTT、STOMP、AMQP 与 HTTP/2 的加固、OAuth2、WebAuthn、MCP 与 Files 协议的安全修复,以及交易所方面的改动(Binance Spot 用户数据流、按节奏进行的重连重放、OKX 与 KuCoin 的保活)同样包含在内。带回执的优雅 STOMP Disconnect,以及 WebSocket 服务器上的控制帧限制,在那里也是新增内容。
升级说明
对现有的 2026.x 项目而言,2026.8 是可以直接替换的升级,但在构建之前有两点值得了解。Binance Spot 用户数据流现在会对其订阅签名,因此必须在 Binance.ApiKey 之外同时设置 Binance.ApiSecret。另外,WebSocket 服务器现在默认接受 10,000 个并发连接,而不是无限个,因此如果您的连接数超过这个值,请调高 MaxConnections 或将其设为 0。
其余一切在您主动启用之前都是关闭的:吊销检查、版本下限、强加密、客户端证书验证、IOCP 与 EPOLL 引擎上的 keep-alive、消息节流以及 MCP 允许来源列表,全部默认保持此前的行为。
拥有有效订阅的客户可以从客户专区下载新版本,或访问 esegece.com/products/websockets/download。
有疑问、反馈或需要迁移帮助?联系我们。回复您的将是编写这些代码的人。
