
简介sgcWebSockets-Enterprise-V2023.5-FS 是一套面向企业的 WebSocket 服务器软件包用于构建低延迟、全双工的实时通信服务。与传统的 HTTP 轮询或服务器发送事件相比它能使服务端主动推送数据并支持大规模并发连接非常适合在线游戏、金融交易、实时仪表盘、协同编辑等场景。该企业版在标准功能外增加了更细粒度的权限控制、负载均衡、集群支持、加密通信、用户认证、访问控制列表以及日志、监控和报告能力并通过健全的错误处理与安全机制保障高负载下的稳定性。压缩包为 7z 格式整体大小约 66.64MB便于获取和部署。借助其 API 与集成接口开发者可以较快地将 WebSocket 能力嵌入现有业务系统缩短开发周期架构师也能借此审视企业级即时通信平台的模块组成与运维要点。该资源目前已有 145 人学习或下载适合正在调研实时通信方案或需要自建高可靠推送服务的团队参考。1. 当 Delphi 项目需要 WebSocket为什么绕不开 sgcWebSocketsWebSocket 早已不是前端页面的专属玩具。你在 Delphi 里写一个交易推送服务、一个设备状态上报网关、或者一个需要实时双向通信的桌面客户端都会遇到同一个尴尬Indy 自带的 TIdHTTPServer 只能做 HTTP 轮询Windows 的 WinHTTP 也不直接暴露 WebSocket 升级流程。自己用 TIdHTTPServer 加 Socket 层手搓协议栈不是不行但帧解析、掩码处理、分片重组、Ping/Pong 心跳这些细节一旦被业务代码追着跑维护成本会迅速超过功能本身。sgcWebSockets 就是为这个场景存在的一套纯 Delphi 实现的 WebSocket 客户端与服务端组件库把 RFC 6455 协议细节全部封装成事件和方法调用。标题里的 Enterprise 版本表示它包含完整的 TLS/SSL 支持、WebSocket over HTTP 代理、以及多线程并发服务端能力FS 后缀则指向它的发布形态——FileSystem 版解压即用不需要依赖安装程序写注册表。如果你正在用 Delphi 10.3 到 12 之间的版本做桌面或服务端程序又不想在协议细节里耗掉一周时间这个库值得看下去。2. 从 7z 压缩包到可用的 sgcWebSockets 组件面板2.1 解开 FS 版压缩包的目录结构sgcWebSockets-Enterprise-V2023.5-FS.7z 解压后你不会看到一个可执行的 setup.exe而是几个层级清晰的文件夹。常见做法是把整个目录解压到一个固定路径比如D:\lib\sgcWebSockets之后这个路径就是 Delphi 的 Library 搜索路径。解压后第一件事是查看根目录下的sgcWebSockets.inc文件这个文件里定义了框架的版本号与平台开关例如SGC_WEBSOCKETS_VERSION这样的字符串常量后面排查组件版本问题时需要对着它确认。打开子目录结构你会看到source、packages、samples等目录其中packages里按 Delphi 版本号拆成多个子目录例如Delphi_11_Alexandria、Delphi_12_Athens这样的命名。确认你机器上装的是哪个 Delphi 版本后进入对应的子目录找到以sgcWebSockets开头的.dpk或.dproj文件。这里要注意一个细节FS 版不会覆盖你现有的 Delphi 安装目录也不会自动修改注册表它把安装动作拆解成两步——手动编译包、手动配置库路径。打开packages\Delphi_11_Alexandria目录通常会看到 Runtime 包和 Design 包两类文件。Runtime 包负责纯粹的运行逻辑编译出的.bpl文件在程序发布时需要分发到目标机器Design 包则包含 IDE 设计期支持让TsgcWebSocketClient和TsgcWebSocketServer等组件出现在 Delphi 的工具面板里。对于只做服务器端开发的场景安装 Runtime 包已经足够但如果你希望在设计期拖拽组件并调整属性Design 包必须一起编译安装。提示不要直接把整个source目录塞进 Library 路径就以为安装完成。那样 Delphi 虽然能识别单元文件但 IDE 组件面板不会出现任何新组件程序里也只能手动用Uses引入单元。对于刚接触这个库的开发者老老实实编译按装一次包最省事。2.2 编译并安装 Design-Time 包的步骤在 Delphi IDE 中操作时文件类型不同操作路径也不同。旧版.dpk文件需要右键选择 Compile新版.dproj文件则直接打开后右键 Build。更友好的方式是用 IDE 的 Package 菜单打开sgcWebSockets.dproj点击 Compile 后再点 Install。这个动作会把组件注册到 IDE 的组件面板里。安装成功后在组件面板中新增的 sgcWebSockets 页签里就能看到几个核心组件下面用表格列出最常用的几个组件类名设计期角色典型用途TsgcWebSocketClient客户端连到远端 WebSocket 服务端处理订阅推送TsgcWebSocketServer服务端监听本地端口接受客户端连接TsgcWSServer服务端 HTTP 升级支持同时处理 HTTP 请求与 WebSocket 升级TsgcWebSocketHTTPServer服务端基于 HTTP 服务的 WebSocket 实现Install 完成后还需要在 IDE 里配置库搜索路径。打开 Tools - Options - Language - Delphi - Library在 Library path 中添加D:\lib\sgcWebSockets\source这样即使不依赖组件面板新项目中直接写Uses sgcWebSocket_Classes, sgcWebSocket_Client, sgcWebSocket_Server;也能编译通过。2.3 配置许可证与运行库环境Enterprise 版本在首次完整编译时可能要求校验许可证信息。具体做法是在项目中引入sgcWebSocket_Classes单元前检查根目录是否包含说明许可证用途的文件。常见做法是把企业版提供的一个.txt许可证文件放在与sgcWebSockets.inc相同的目录下或者把许可证内容写进设计期组件的 License 属性。如果编译报E2004或提示需要注册先确认许可证文件没有被杀毒软件误删。此外Runtime 包编译产出的.bpl文件路径需要加入系统 PATH 或随应用程序一起分发。如果你的部署环境不允许安装额外运行时可以选择在项目里不勾选 Build with runtime packages把 sgcWebSockets 直接静态编入 EXE 中。这样发布时只需要带上几个编译单元即可缺点是 EXE 体积会增加约几百 KB但对后续部署和排错来说更简单。3. 用 sgcWebSockets 搭出第一个可用的 WebSocket 服务端与客户端3.1 最小可运行的服务端代码新建一个 Delphi VCL 或控制台项目在 Form 上放置一个TsgcWebSocketServer组件设置Port属性为 8080。下面是一段能够完整响应客户端收发消息的代码核心事件是OnMessage与OnConnect。uses sgcWebSocket_Classes, sgcWebSocket_Protocols, sgcWebSocket_Server; procedure TForm1.FormCreate(Sender: TObject); begin // 指定监听端口与允许的最大连接数 WSserver.Port : 8080; WSserver.MaxConnections : 1024; // 启动服务端监听 WSserver.Active : True; end; procedure TForm1.WSserverOnConnect(Connection: TsgcWSConnection); begin // 可在连接事件中记录对端地址便于排错 Memo1.Lines.Add(New client: Connection.RemoteHost); end; procedure TForm1.WSserverOnMessage(Connection: TsgcWSConnection; const Text: string); begin // 收到文本消息后原样返回给客户端 Connection.Write(Text); end;Port决定服务端绑定的 TCP 端口MaxConnections控制并发连接数这里设置 1024 已经能覆盖绝大多数桌面应用场域。OnMessage事件中收到的Text参数是服务端解析之后的明文字符串不需要手动处理帧头。Connection.Write是同步写入方法消息会以文本帧方式发送给对端。这个最小的服务端已经具备接受客户端连接、收发文本消息的能力。如果要在OnMessage里判断消息类型是文本还是二进制可以把Connection.Data或消息对象中的DataType属性拿出来判断。例如TsgcWSMessage含DataType枚举区分wdtText和wdtBinary。对于需要传输文件或压缩数据的场景用二进制帧能省掉 Base64 编码开销。3.2 客户端连接与事件回调客户端使用TsgcWebSocketClient设置Host、Port、Path三个属性后执行Connect方法。这里的Path对应 WebSocket URL 中的路径部分比如ws://127.0.0.1:8080/chat中的/chat。uses sgcWebSocket_Classes, sgcWebSocket_Protocols, sgcWebSocket_Client; procedure TForm1.btnConnectClick(Sender: TObject); begin // 配置服务端地址与路径路径要与服务端约定的名称一致 WSclient.Host : 127.0.0.1; WSclient.Port : 8080; WSclient.Path : /chat; // 设置连接超时时间单位是毫秒 WSclient.TimeOut : 3000; // 触发异步连接不会阻塞界面线程 WSclient.Connect; end; procedure TForm1.WSclientOnMessage(Connection: TsgcWSConnection; const Text: string); begin // 收到服务端推送更新界面展示 Memo1.Lines.Add(Text); end;TimeOut属性需要特别注意它设置的是连接建立的超时时间而不是空闲超时。对于需要长时间保持连接不发送数据的场景可以设置HeartBeatInterval和HeartBeatTimeout来维持连接。客户端默认使用异步连接模式调用Connect后立即返回连接结果在OnConnect或OnError事件里得到反馈。对需要在连接成功后立刻做初始化操作的场景建议在OnConnect事件中写入登录握手或订阅逻辑而不是在Connect调用后直接写业务代码因为此时连接尚未真正建立。3.3 处理连接关闭与异常断开网络环境不会永远稳定客户端掉线、服务端重启、防火墙静默断开这些都是常态。sgcWebSockets 在OnDisconnect事件中给出断开回调但你需要区分是主动断开还是异常断开。在断开事件中读取Connection.CloseCode如果值为1006或1001等异常状态码则说明是底层 TCP 连接意外中断需要考虑重连机制。实现重连时建议用一个TTimer控制重连间隔避免无限快速重试把服务端拖垮。常见做法是设置指数退避第一次 3 秒后重试第二次 6 秒最多间隔 30 秒。在调用Connect前检查NotConnected状态防止重复发起连接请求。另外一个容易被忽略的点是服务端关闭时发送 Close 帧客户端收到后会自动断开并触发OnDisconnect此时如果客户端需要区分是服务端主动踢人还是服务端进程崩溃需要在 Close 帧中携带状态码。sgcWebSockets 允许服务端在关闭连接前调用Connection.Close(1001, Server shutting down)这个信息会在客户端的事件中暴露。4. 深入协议边界与参数调优帧细节、TLS 与并发4.1 帧解析、掩码与分片消息的处理时机WebSocket 协议最容易被忽略的是掩码规则客户端发往服务端的帧必须掩码服务端发往客户端的帧不得掩码。sgcWebSockets 已经封装了这一层但你在设计自定义协议时仍然需要理解它影响性能的细节。例如当客户端一次性发送一个 10MB 的二进制数据时底层 Socket 会分片传输服务端事件的触发时机是什么sgcWebSockets 对分片消息的处理是透明的。OnMessage只有在完整消息组装完成后才触发一次这意味着就算底层通过网络收到多个 fragment你的业务代码仍然只需面对一个完整的缓冲区。这在设计大文件传输时有明显优势你不需要自行维护一个 StringBuilder 来拼接分片数据。但代价是内存占用与消息大小成正比如果一个连接恶意声明要传输 2GB 的数据服务端会尝试把整个消息写入内存。针对这种情况企业版提供了MaxMessageSize参数可以在TsgcWebSocketServer上限制单条消息的最大字节数。如果客户端发送超过该值的消息连接会被立即断开。这个参数在公共网络环境中强烈建议设置能够有效规避内存耗尽型攻击。另外在处理二进制消息时事件签名与文本消息不同。文本消息是OnMessage(Connection, Text)而二进制消息走OnBinaryMessage或通过TsgcWSMessage对象的Data属性传递。确认OnMessage中Text参数没有包含帧头字节流否则说明你用了错误的事件。sgc 会把帧解析后的纯数据内容交给你这一点可以在调试时打印前 4 个字节进行验证。4.2 企业版 TLS 证书配置与 wss 连接企业版与免费版最大的区别之一就是 TLS 握手能力的完整性。要启用 wss 访问需要给服务端配置证书与私钥。以下是加载 PEM 格式证书的标准做法var SSLOptions: TsgcWSListenSSLOptions; begin // 从 SSLOptions 子对象中设置证书路径 SSLOptions : WSserver.SSLOptions; SSLOptions.Enabled : True; SSLOptions.Port : 443; SSLOptions.Certificate.FileName : D:\certs\mycert.pem; SSLOptions.PrivateKey.FileName : D:\certs\private.key; SSLOptions.Mode : sslmServer; WSserver.Active : True; end;SSLOptions.Enabled设为True后服务端的监听端口将由Port属性切换到SSLOptions.Port通常为 443。Mode必须设置为sslmServer模式。如果证书是自签名的在客户端连接时需要额外处理证书验证回调。企业版在客户端组件里提供一个OnVerifyCertificate事件你可以在该事件里决定是否接受无效证书procedure TForm1.WSclientVerifyCertificate(Sender: TObject; Cert: TsgcTLSX509Certificate; var Accepted: Boolean); begin // 仅用于开发调试生产环境应验证证书链与指纹 Accepted : True; end;证书链的完整校验在底层默认是开启的如果你在内网用自签名证书测试在回调里打印证书的指纹和 OpenSSL 客户端核对是否一致以此避免中间人攻击。对于 HTTP 请求中携带的Origin头企业版也提供校验支持。跨域 WebSocket 连接在浏览器场景下必须匹配Origin服务端设置AllowedOrigins列表可以阻止非预期域名发起的连接。4.3 多线程并发模型与心跳参数配置TsgcWebSocketServer的并发能力取决于它的线程模型。默认情况下每个客户端连接由一个独立的线程处理这也是OnMessage事件中做耗时操作会阻塞该客户端后续消息的原因。不要把事件处理器里的逻辑设计得过于冗长如果需要对收到的数据进行计算或写库建议把业务逻辑丢到线程池中执行让 WebSocket 连接线程尽快返回。同时需要留意的是OnMessage事件并不是在主线程中执行的它是连接线程的上下文环境。如果你在 VCL 界面里直接操作控件需要通过TThread.Queue或Synchronize切回主线程。代码中可以直接用TThread.Synchronize(nil, procedure begin Memo1.Lines.Add(Text); end);来安全更新界面。很多初用者在服务端事件里直接访问窗体变量时发现偶然崩溃大概率就是忽略了线程切换。关于应用层心跳核心配置是HeartBeatInterval与HeartBeatTimeout。以下表说明两者的分工参数默认值参考作用HeartBeatInterval30 秒服务端主动发送 Ping 帧的间隔HeartBeatTimeout10 秒超过这个时间未收到 Pong 帧则断开连接客户端与浏览器不同没有自动发送 Pong 的限制因此 sgcWebSockets 在收到 Ping 帧后会由底层自动恢复 Pong 帧不需要业务代码干预。对需要精确感知网络状态的业务可以把HeartBeatInterval调短到 10 秒这样断网后最多 20 秒内就能感知。但对千万级连接的服务端来说过密的心跳会产生显著的额外负载建议根据业务允许的故障感知时间来设置不要一味求快。4.4 用 subprotocol 与浏览器端做功能协商当服务端既要支持浏览器客户端又要支持 Delphi 原生客户端时subprotocol 用来自定义应用层协议的协商。所谓 subprotocol不是 WebSocket 协议本身的扩展而是双方约定好的一种命名空间。在连接握手阶段客户端通过Sec-WebSocket-Protocol头列出它支持的协议名服务端选择其中的一个作为最终协议。sgcWebSockets 中客户端设置SubProtocol属性为[chat.v1, chat.v2]服务端可以设置SubProtocols并重写事件的协商逻辑。对于需要区分压缩消息和 JSON 消息的业务可以自行定义 subprotocol 名。例如一个binary-json协议表示所有消息帧都被视为 JSON 序列化后的二进制对象。这样客户端在收到消息后先用TJson反序列化而不是根据帧类型做分支。这也让后续《websocket使用》《websocket subprotocol》这类检索词的搜索意图有了落地答案subprotocol 是用来做版本兼容与协议切换的不可以在连接建立后动态变更。5. 验证 WebSocket 服务端的三种实用手段与避坑技巧验证TsgcWebSocketServer是否正常工作最直接的方法是让一个独立的客户端连接上来收发消息。手段不止一种可以用浏览器控制台运行 JavaScript 原生 WebSocket 客户端也可以用 Python 写几十行脚本模拟连接甚至可以通过 ActiveX 或第三方 TCP 工具手动走一遍握手流程。对于手头没有现成客户端调试工具的场景我最常用的方式是打开 Python 交互环境执行一段轻量脚本完成连接与收发。下面演示如何用 Python 标准库检查 Delphi 服务端的基本收发能力import socket, base64, os, struct # 手动构造 WebSocket 握手请求 s socket.create_connection((127.0.0.1, 8080), timeout3) key base64.b64encode(os.urandom(16)).decode() req ( GET /chat HTTP/1.1\r\n Host: 127.0.0.1:8080\r\n Upgrade: websocket\r\n Connection: Upgrade\r\n fSec-WebSocket-Key: {key}\r\n Sec-WebSocket-Version: 13\r\n\r\n ) s.send(req.encode()) # 读取并检查握手响应 resp s.recv(1024).decode(errorsignore) assert 101 in resp.split(\r\n)[0], resp # 发送一个文本帧客户端帧必须掩码 payload bping mask os.urandom(4) frame bytearray([0x81, 0x80 | len(payload)]) mask frame bytes(b ^ mask[i % 4] for i, b in enumerate(payload)) s.send(frame) # 读取服务端响应解析帧头的 FIN/OPCODE 与长度 data s.recv(1024) opcode data[0] 0x0f length data[1] 0x7f print(fopcode{opcode}, payload{data[2:2length].decode()})连接层面的验证在于握手响应必须返回101 Switching Protocols且服务端回发的帧不能带掩码。上面脚本中对客户端的帧加了掩码符合协议对客户端的要求读取服务端帧时直接取第 3 个字节作为数据起始位置这是因为服务端帧没有 mask 位。在实际调试中如果服务端回包带掩码说明你误开启了服务端掩码选项这是一个容易混淆的配置。sgcWebSockets 中没有直接暴露强制服务端掩码的选项但如果通过反向代理转发 WebSocket 流量有些代理会重新编码帧格式这将导致客户端解析异常。真实网络环境中凡是请求经过了带有协议重写能力的网关都需要留意这一点。另一个常用验证点是使用浏览器的开发者工具。打开一个空白页面的控制台执行const ws new WebSocket(ws://127.0.0.1:8080/chat); ws.onopen () ws.send(hello from browser); ws.onmessage (ev) console.log(ev.data);如果 Delphi 服务端的OnMessage中能够收到hello from browser并且浏览器控制台打印出服务端的回复说明服务端的基础收发链路没有问题。对于服务端主动推送的场景从 Delphi 代码中调用WSserver.Broadcast(notice)观察浏览器端是否收到即可。其实验证服务端的一个重要维度不止是功能还包括连接断开时的表现。在 Delphi 中强制Active : False时服务端会尝试给所有客户端发送关闭帧。如果用 Python 脚本监听这次关闭帧的 opcode 是否为 0x8Close 帧就能确认正常的关闭序列在走。如果没有收到 Close 帧而直接看到 TCP 断开说明服务端进程可能被强杀或线程异常退出。这个验证方法不仅能发现程序崩溃还能暴露资源清理不完整的问题。最后留一个通用技巧排查 sgcWebSockets 问题时先打开sgcWebSocket_Log单元中的日志输出开关把连接建立、帧收发、关闭状态全部记录到本地文件。这个日志不会显著影响性能却能在客户端和服务端各执一词时帮你快速定位责任边界。一旦日志勾出握手失败或 Ping 超时多半可以从记录的 TLS 报错信息中找到准确原因而不是在代码里盲猜。本文还有配套的精品资源点击获取