Delphi WebSocket服务端实战:开源库选型、消息广播与避坑全记录

发布时间:2026/10/13 13:57:50
Delphi WebSocket服务端实战:开源库选型、消息广播与避坑全记录 简介这套DelphiWebsockets压缩包是一份基于Delphi实现WebSocket服务端的完整源码工程面向需要构建实时双向通信能力的Delphi开发者可用于聊天、股票行情推送、在线协作等场景。包内共32个文件以22个.pas源代码文件为主涵盖服务器握手处理、帧解析、客户端连接与SocketIO支持等模块另含2个.dpr工程文件、.dfm窗体界面及单元测试代码压缩包仅134KB结构紧凑、便于直接参考与二次开发。代码同时涉及Indy组件与RemObjects SDK两条技术路径并附带ROdemoWS.zip及WebSocket演示单元有助于理解连接初始化、HTTP 101升级、数据帧编解码和异常连接管理等关键实现。已有418人学习下载适合熟悉Delphi基础、希望掌握WebSocket服务端实战写法或从事实时通信项目开发的中高级程序员。1. 用Delphi写WebSocket服务为什么直接上开源库而不是自己撸协议WebSocket早已不是网页聊天室的专利。工业看板、期货行情推送、远程运维终端、内部IM凡是需要服务端主动往客户端推数据的场景Delphi桌面程序几乎都能遇到。而Delphi社区最容易被搜到的方案就是DelphiWebsockets这套开源封装——它把RFC 6455的握手、分帧、掩码、心跳、断线重连这些底層事全做完了你只需要注册几个事件回调就能把一个TIdHTTPServer级别的VCL程序变成实时推送服务端。先说结论如果你的项目是Windows下用Delphi 10.3要在一个月内上线一个支持JSON消息广播的WebSocket服务端直接拿DelphiWebsockets当基础层是划算的。它绕开了自己解析握手帧的坑也绕开了TIdHTTPServer对Upgrade请求支持的短板。但前提是你得接受它的事件模型、线程模型和错误处理方式这些恰恰是新手最容易翻车的地方。这套库适合两类人一是桌面程序里要嵌一个轻量级推送通道不想为了一个推送功能去部署Node.js或Nginx二是已有Delphi后端想统一协议栈、少维护一套服务。下面按我从零跑通这个库的顺序把选型理由、最小实现、参数调整和踩过的坑一次讲清楚。2. DelphiWebsockets是什么协议栈构成与选型对比2.1 它到底封装了RFC 6455的哪几层WebSocket规范的核心是四件事HTTP Upgrade握手、帧的拆分与组装、掩码处理、连接生命周期管理。DelphiWebsockets把这四件事封装成了几个类你实际用的时候只接触三个角色TWebSocketServer负责监听和接受连接TWebSocket代表一条已建立的连接TWebSocketMessage和TWebSocketDataMessage是消息载体。握手层是最容易被低估的部分。浏览器和客户端发起连接时先发一个带有Upgrade: websocket头的HTTP请求服务端要计算Sec-WebSocket-Accept并返回101状态码。这个库在内部实现了完整的握手校验包括对Sec-WebSocket-Key的Base64解码和SHA1计算。你自己写的话这一步会消耗大量调试时间而且很容易在大小写、空格、换行符上出错。帧层是第二个关键点。WebSocket数据分文本帧和二进制帧还有控制帧ping/pong/close。DelphiWebsockets对控制帧是自动响应的收到ping自动回pong收到close自动回close并释放连接。这对服务端稳定性非常重要因为很多客户端库会周期性发ping来探测连接是否存活服务端不及时响应pong就会被客户端判定为死连接。第三个是连接管理。库内部维护了一个连接列表提供Broadcast方法向所有连接广播消息也提供GetConnections遍历当前在线连接。你不需要自己维护TClientSocket列表这省掉了大量并发遍历和锁竞争问题。2.2 为什么不用ICS、Synapse或自研方案很多Delphi程序员第一反应是用Indy的TIdHTTPServer加WebSocket支持。但实际上Indy的WebSocket支持长期处于半成品状态尤其是对TIdHTTPServer的Upgrade事件处理需要你自己解析header、自己管理socket生命周期就算跑通了也心虚后续加TLS、加多线程广播又得重新折腾一遍。ICSInternet Component Suite确实提供了更完整的WebSocket实现有客户端也有服务端还支持TLS。但ICS的历史包袱重类命名和事件回调风格跟VCL传统差别很大新手看它的Demo往往要花一两天才能摸清事件触发顺序。还有一条路是用跨平台HTTP库如Horse或mORMot的WebSocket中间件。Horse的WebSocket插件封装得也不错但当你只需要一个推送通道、不想引入完整MVC框架时DelphiWebsockets这种单一职责库更轻。我通常的建议是项目里已经有Horse就顺手用它没有的话单独引入DelphiWebsockets别为了一两个接口去背一个框架的约束。2.3 选型时的三个硬指标版本、编译器和依赖用DelphiWebsockets前先核对三样东西它依赖的WebSocket协议库版本、你的Delphi编译器版本、以及是否引入额外的开源依赖。常见布局是主项目DelphiWebsockets加上对WebSocket4Delphi或wsServer的引用因为你搜到的分支不同内部结构也有差异。我建议直接看它在某Git托管平台上的release分支确认是否标注支持你当前的Delphi版本。Delphi 10.4以前的编译器对一些泛型和匿名方法支持有限如果你的代码里用了较新的语法低版本编译会报E2003之类的符号错误不要试图修改库源码先确认版本匹配。另外一个准备项是安装一个内存泄漏检测工具比如FastMM4开启泄漏报告。WebSocket连接频繁建立和释放库内部如果某个TWebSocket对象没有被正确Free内存增长会非常隐蔽。直接在项目中启用FastMM上线前跑一次压力测试比事后查内存曲线省心得多。3. 本地跑通最小服务端环境准备、握手流程与首条推送3.1 最小单元工程单文件跑出一个能连的服务端我一般先建一个空白VCL项目放一个TMemo和一个TButton用最小代码验证库能工作。以下是Delphi 10.4下可编译的骨架代码uses System.SysUtils, System.Classes, Vcl.Controls, Vcl.StdCtrls, IdCustomTCPServer, WebSocketServer, WebSocketCommon; type TFormMain class(TForm) Memo1: TMemo; Button1: TButton; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button1Click(Sender: TObject); private FServer: TWebSocketServer; end; const PORT 8080; implementation procedure TFormMain.FormCreate(Sender: TObject); begin FServer : TWebSocketServer.Create(PORT, localhost); FServer.OnMessage : DoMessage; FServer.OnConnect : DoConnect; FServer.OnDisconnect : DoDisconnect; FServer.Start; Memo1.Lines.Add(WebSocket server started on port IntToStr(PORT)); end; procedure TFormMain.DoMessage(ASender: TObject; AConnection: TWebSocketConnection; const AData: string); begin Memo1.Lines.Add(Received: AData); AConnection.SendText(echo: AData); end; procedure TFormMain.DoConnect(ASender: TObject; AConnection: TWebSocketConnection); begin Memo1.Lines.Add(Client connected: AConnection.Socket.RemoteHost); end; procedure TFormMain.DoDisconnect(ASender: TObject; AConnection: TWebSocketConnection); begin Memo1.Lines.Add(Client disconnected); end; procedure TFormMain.Button1Click(Sender: TObject); begin if Assigned(FServer) then FServer.Broadcast(button pressed at TimeToStr(Now)); end; procedure TFormMain.FormDestroy(Sender: TObject); begin FServer.Stop; FServer.Free; end;事件回调DoMessage的声明要和库里的TWebSocketMessageEvent完全匹配常见的签名是procedure(ASender: TObject; AConnection: TWebSocketConnection; const AData: string)。不同分支里AConnection的类名可能不同有的叫TWebSocketConnection有的叫TWebSocket先看库源码里的声明再抄事件签名。TWebSocketServer.Create(PORT, localhost)有两个参数第一是监听端口第二是主机名用于校验请求的Host头。如果你要对外提供服务、且客户端可能用IP访问这里应该传空字符串或者0.0.0.0否则握手校验不通过。这是第一个经常踩的坑。Start方法是阻塞启动还是非阻塞启动取决于库内部是否创建了独立的监听线程。绝大多数情况下Start会创建线程并立即返回VCL界面不会卡住。如果你的界面卡住几秒不动说明该分支的Start实现是同步循环要换一个版本或者手动放到后台线程里调用。3.2 三行JS验证服务端是否正常Delphi端写好后用浏览器当客户端最简单。新建一个HTML文件写入以下代码!DOCTYPE html html headmeta charsetutf-8titleWS Test/title/head body button onclickstart()Connect/button button onclicksend()Send/button pre idlog/pre script var ws; function log(msg) { document.getElementById(log).textContent msg \n; } function start() { ws new WebSocket(ws://localhost:8080/); ws.onopen function() { log(open); }; ws.onmessage function(e) { log(recv: e.data); }; ws.onerror function(e) { log(error: e.message); }; ws.onclose function(e) { log(close: e.code e.reason); }; } function send() { ws.send(hello from browser); } /script /body /html用浏览器直接打开这个HTML不需要HTTP服务器点击Connect。此时服务端的DoConnect应该触发Memo里会多一行客户端连接记录。再点击Send服务端DoMessage里会收到hello from browser然后浏览器onmessage会收到echo: hello from browser。这一步跑通说明四条链路是通的TCP监听、HTTP Upgrade握手成功、Delphi收到完整WebSocket消息、Delphi发送消息被浏览器正确还原。如果卡在连接阶段优先检查浏览器控制台里的错误提示它会明确告诉你Unexpected response code: 200还是Error during WebSocket handshake这两者的排查方向完全不同。3.3 在主线程还是工作线程里处理消息VCL线程模型的两个必调参数TWebSocketServer内部默认会在收到消息时回调到哪个线程这是新手最容易忽视的地方。有的分支封装了Synchronize机制事件回调跑到主线程更新Memo直接安全有的分支则直接在工作线程里回调你更新VCL控件就会偶发崩溃。先弄清楚你的库是哪种行为办法是打印TThread.Current.ThreadID到Memo里看每次回调是不是同一个线程ID。如果每次不同说明回调发生在工作线程你在里面操作VCL控件前必须自己TThread.Synchronize。为了避免两种分支的差异我习惯在FormCreate里设置两个全局开关FServer.ThreadContext : tcSync; FServer.FreeOnTerminate : false;ThreadContext决定事件回调是在库的工作线程同步执行还是投递到主线程tcSync表示同步执行在当前工作线程里调回调。FreeOnTerminate改成false确保服务器对象在关闭时不会由后台线程自行释放避免FormDestroy里FServer.Free悬空。实际调优时如果消息量不大每秒几十条tcSync足够如果单条消息处理耗时超过几百毫秒同步回调会把接收线程堵死连接会越积越多这时候改成投递模式或者自己在回调里把处理放到线程池。这个决策点没有绝对对错完全取决于业务。4. 消息格式与并发广播JSON封装、二进制帧处理和线程安全边界4.1 文本消息传JSON格式化、转义与Delphi端的解析坑WebSocket本身不关心消息内容是JSON还是普通字符串但实际业务里90%的消息都是JSON。服务端发送时建议先序列化再发送uses System.JSON; procedure TFormMain.BroadcastJson(const AJson: TJSONObject); var Data: string; begin Data : AJson.ToJSON; FServer.Broadcast(Data); end;Broadcast方法内部遍历所有连接并逐条发送。在低并发下这没问题但在大量连接同时收发时Broadcast内部是否做了加锁保护不同分支差异很大。一部分实现没有统一锁你在主线程广播、工作线程同时断开连接就会碰到Invalid pointer operation。我的做法是不依赖库内部的线程安全性自己在Broadcast外加一个临界区procedure TFormMain.BroadcastSafe(const AData: string); begin FLock.Enter; try FServer.Broadcast(AData); finally FLock.Leave; end; end;FLock是TCriticalSection在FormCreate里初始化。注意Broadcast在遍历连接时如果某个连接已经关闭但还没从列表移除可能触发异常。用临界区包住只能防止自己的两个线程同时调Broadcast不能防止库内部Socket.Close和Broadcast的竞争。所以更好的做法是再配合异常捕获try FServer.Broadcast(AData); except on E: Exception do Memo1.Lines.Add(Broadcast failed: E.Message); end;4.2 二进制帧如何下发图片或协议数据文本帧适合JSON但截图、语音、自定义二进制协议就需要发二进制帧。DelphiWebsockets提供了SendBinary方法入参是TBytesprocedure TFormMain.SendImageData(AConn: TWebSocketConnection; AImageBytes: TBytes); begin AConn.SendBinary(AImageBytes); end;浏览器端接收时onmessage的e.data会是Blob或ArrayBuffer具体取决于你是否设置了ws.binaryType。建议在JS里显式设置ws.binaryType arraybuffer; ws.onmessage function(e) { if (e.data instanceof ArrayBuffer) { var bytes new Uint8Array(e.data); console.log(binary received, length, bytes.length); } };这里有一个常见的坑如果服务端发的TBytes明明有10000字节但客户端收到的ArrayBuffer只有8000字节先检查是不是库里SendBinary在数据长度上用了Length(AImageBytes)还是SizeOf(AImageBytes)。TBytes是动态数组SizeOf返回的是指针大小8字节如果库内部误用了SizeOf就会只发8个字节。正常实现都会用Length但为了安全你可以自查一下库的SendBinary实现。4.3 连接管理与心跳为什么默认心跳不够用WebSocket规范里没有强制心跳但现实中网络设备路由器、防火墙、负载均衡会静默回收空闲TCP连接。DelphiWebsockets部分分支实现了服务器端自动发送ping的功能间隔和超时是可配的。常见配置方式是在FormCreate里设置FServer.PingInterval : 30000; // 单位毫秒 FServer.PingTimeout : 5000;含义是每30秒向客户端发一次ping如果5秒内没收到pong就判定连接失效并清理。把PingInterval设成30秒是因为大多数NAT映射的TCP空闲超时在60秒到120秒之间30秒的探测间隔能保证连接一直处于活跃状态。如果你用的分支没有这个配置就自己开一个定时器发pingprocedure TFormMain.TimerCheck(Sender: TObject); var FailedList: TListTWebSocketConnection; Conn: TWebSocketConnection; begin Server.Pings; // 有些版本提供Pings方法遍历所有连接发ping end;没有Server.Pings的话就得遍历连接逐个SendPing。注意不要在主线程里做网络阻塞操作定义一个2秒的定时器只负责发送ping超时检测交给库的内部逻辑或者你额外记录最后通信时间。4.4 广播风暴连接数过百后必须处理的两个瓶颈第一个瓶颈是单线程发送。Broadcast逐个连接同步发送如果其中一个客户端接收窗口满了网络慢Send方法会阻塞在系统Socket写缓冲区上后续所有连接的消息都被这个慢客户端拖住。解决思路是把Broadcast改为异步发送先进队列再逐个连接发送或者使用socket.SendTimeOut给发送设一个上限。第二个瓶颈是序列化开销。如果每条消息都是完整JSON连接越多CPU越浪费在重复序列化同一个对象上。优化方式是先把整体数据字符串拼接好再调用一次Broadcast不要循环里每条消息都组装JSON。为了实测瓶颈我用同机开500个浏览器Tab连同一个服务端做广播测试很快发现问题内存暴涨、CPU跑到单核100%、断开连接时界面卡2秒。后来把SendText改成内部异步队列触发发送并把广播频率限制在每秒20条问题缓解。如果你的消息频率高过这个量级先考虑合并逻辑不要单纯加机器。5. 从本地到局域网IP绑定、TLS加密与防火墙避坑5.1 绑定地址为什么localhost只能在本地玩TWebSocketServer.Create(PORT, localhost)里的第二参如果填了localhost监听Socket只会接受来自127.0.0.1的连接。局域网的同事用ws://192.168.1.100:8080连进来连接会直接被拒。解决办法是监听所有网卡FServer : TWebSocketServer.Create(PORT, 0.0.0.0);有的分支构造函数的第二参数是主机名字符串有的分支是TIdSocketHandle或IPVersion写法不同。如果你用一个函数库不确定怎么传最直接的办法是把构造函数的源码打开看找到最终传给Bind的参数再对应改。不要猜。5.2 TLS走wss证书格式、加载位置和握手失败排查WebSocket加密有两种实践方案一是在Delphi服务端直接挂证书实现wss://二是前置一个Nginx做TLS终止然后转发到Delphi的明文端口。小规模内网工具直接用Delphi挂证书最省事。在DelphiWebsockets上启用TLS的常见做法是通过TIdServerIOHandlerSSLBaseuses IdSSL, IdSSLOpenSSL; FIOHandler : TIdServerIOHandlerSSLOpenSSL.Create(nil); FIOHandler.SSLOptions.CertFile : D:\certs\server.crt; FIOHandler.SSLOptions.KeyFile : D:\certs\server.key; FIOHandler.SSLOptions.RootCertFile : D:\certs\ca.crt; FIOHandler.SSLOptions.Mode : sslmServer; FServer.IOHandler : FIOHandler;这套代码能跑通的前提是系统里装好了OpenSSL DLLlibssl和libcrypto。Delphi自带的Indy通常依赖特定版本的OpenSSL版本不匹配会报Could not load SSL library或者握手时直接断开。如果客户端浏览器报错ERR_CERT_AUTHORITY_INVALID那就不是库的问题是证书链没配好或者在客户端没安装根证书。内网自签证书场景建议直接在客户端机器上导入CA根证书否则你每次连接都要点继续访问。5.3 防火墙与私有网络的四个检查项Windows防火墙拦截是最常见的服务端明明启动了但客户端连不上的元凶。排查时按顺序检查四件事端口是否在监听命令行运行netstat -ano | findstr 8080看到LISTENING才是真正起来了。防火墙入站规则netsh advfirewall firewall add rule nameWS 8080 dirin actionallow protocolTCP localport8080注意这条命令要管理员权限。局域网IP是否可达客户端机器ping服务端IP不通就是网络问题不关代码的事。杀毒软件或安全组是否拦截了自定义端口的入站连接有些企业版杀软会默认禁止非白名单端口的TCP入站在日志里能看到拦截记录。5.4 端口被占用时的事件提示如果8080端口被其他程序占用Start方法会抛EIdCouldNotBindSocket或ESocketError。建议在FormCreate里捕获try FServer.Start; except on E: Exception do begin Memo1.Lines.Add(Start failed: E.Message); Application.Terminate; end; end;不要裸调Start让程序静默崩溃。这类错误信息对定位问题帮助很大直接说Address already in use就说明端口有程序在监听。6. DelphiWebsockets避坑手册从握手失败到内存泄漏的六个真实场景6.1 握手总是返回200而不是101Host头校验不过现象客户端new WebSocket后浏览器控制台报Error during WebSocket handshake: Unexpected response code: 200Delphi服务端没有任何事件触发。原因服务端构造函数第二参数填了localhost客户端用IP或别的域名访问Host头不匹配被服务端拒绝回退成普通HTTP 200响应。解决监听地址绑定改为0.0.0.0或者确认客户端访问的Host和绑定的主机名完全一致。如果客户端用ws://127.0.0.1:8080连服务端绑localhost也能通过但同一个局域网里的同事用ws://192.168.1.100:8080就连不上。统一改绑0.0.0.0最省心。6.2 消息回调里一操作Memo就报EForeignException现象连接和消息都能收到但每次在DoMessage里执行Memo1.Lines.Add过几分钟程序就弹EForeignException或者VCL控件显示异常。原因回调发生在工作线程直接操作了主线程的VCL控件线程亲和性冲突。有的库内部有Synchronize但某些事件尤其是OnDisconnect没有同步。解决给所有回调包一层TThread.Synchronize或者使用TThread.Queue保证控件访问发生在主线程procedure TFormMain.DoMessageSafe(ASender: TObject; AConnection: TWebSocketConnection; const AData: string); begin TThread.Queue(nil, procedure begin Memo1.Lines.Add(AData); end); end;TThread.Queue不阻塞当前工作线程比Synchronize更适合消息回调这种高频场景。注意Queue里的匿名方法捕获的AData字符串是安全的但如果你要捕获AConnection对象必须考虑它是否已被释放。6.3 内存稳定上涨但查不出是谁在泄漏现象服务端跑一个礼拜内存占用从200MB涨到2GB任务管理器里看进程内存只涨不降。原因连接断开时库内部的连接对象没有从列表移除或者事件回调注册没有解除。部分版本在OnDisconnect里只做了Socket关闭没有调用连接列表的Remove方法。解决在OnDisconnect回调里主动清理procedure TFormMain.DoDisconnect(ASender: TObject; AConnection: TWebSocketConnection); begin FServer.Connections.Remove(AConnection); end;但前提是你的库的Connections属性允许外部直接移除。如果不能直接移除检查TWebSocketServer是否有一个Tick或Cleanup方法需要周期性调用。开了库自带的AutoPing并且PingTimeout设置合理的话大部分版本能自动清理死连接。另一个隐蔽点是OnMessage事件里如果注册了匿名方法要注意匿名方法的捕获变量可能造成循环引用。不要在一个对象的事件里捕获对象自身而不主动释放这会导致整个对象永远不能Free。6.4 系统休眠后客户端全部掉线恢复后连不上现象笔记本合盖再打开服务端还在运行但所有WebSocket客户端都断了点连接也无法重新建立。原因系统休眠期间TCP连接被系统回收服务端的监听Socket也进入不健康状态。有些版本的Start没有监听系统网络状态变化导致监听Socket虽然没有报错但不再接受新连接。解决在FormCreate里监听WM_POWERBROADCAST消息恢复时重启服务器procedure TFormMain.WMPowerBroadcast(var Msg: TMessage); begin if Msg.WParam PBT_APMRESUMEAUTOMATIC then begin FServer.Stop; FServer.Start; end; inherited; end;注意Stop和Start中间加一个短暂的Sleep(100)让系统网络栈完全恢复否则立刻Start可能报Address already in use旧socket还没完全释放。6.5 客户端连接数超过200后CPU单核跑满现象服务端才接几十个连接时CPU正常两百个连接后单核CPU达到100%广播一条消息要几秒。原因Broadcast是同步发送加上每条消息都频繁分配字符串和对象。最耗CPU的不是SendText本身而是内部转换为UTF8编码时的大块内存移动以及Socket写入的系统调用次数。解决先规划好广播频率如果是行情类推送建议在服务端做节流比如100ms内多条消息合并成一条再广播。另外检查Broadcast是否对每个连接都发送了TIdBytes如果是可以改成预编码一次再发送。以下是一个简单的节流模板procedure TFormMain.ThrottledBroadcast(const AData: string); begin FLastMsg : AData; if not FTimerEnabled then begin FTimer.Interval : 500; FTimer.Enabled : true; end; end; procedure TFormMain.TimerExpired(Sender: TObject); begin FTimer.Enabled : false; if FLastMsg then FServer.Broadcast(FLastMsg); end;这只是最基本的时间窗合并更精细的做法是按连接数分片发送把1000条消息分成4个批次每个批次250条批次之间留几毫秒间隔。但优先级最高的永远是减少不必要的广播。6.6 同一份代码在10.3上编译不过现象直接用新版写好的项目在Delphi 10.3上编译报E2010 Incompatible types或某个泛型方法不存在。原因库的新分支用了更新的语法特性比如TArray.ToArray或者泛型匿名方法的推断10.3的编译器不支持。解决三个办法按代价排序。第一切换到该库的旧release分支或对应你编译器版本的tag第二自己动手把报错处的泛型改写成传统循环赋值第三直接把项目里的Delphi编译器升级到10.4以上。不要试图大量修改库源码来适配旧编译器会导致后续无法合并上游更新。7. 更进一步的落地技巧从单服务端到多进程扩展及资源回收当单服务端撑到几千连接时最先暴露的不是网络瓶颈而是内存分配。DelphiWebsockets每个连接会创建一个TWebSocketConnection对象包含接收缓冲区、发送队列连接数过万的话内存就得用GB级规划。这时候常见的扩容方式是同一台机器上起多个监听端口前面用负载均衡做TCP转发但更稳妥的是直接多开几个服务端进程每个绑定不同端口业务层通过共享数据库或消息队列同步状态。升级到多进程前先做一次节流和心跳参数检查。把PingInterval在空闲时段调到30秒把PingTimeout设成5秒能显著减少死连接占用。另外把系统TCP超时参数调短Windows下默认的半开连接回收间隔可能要几分钟在注册表或Idea网络不太稳定时自动断开会更及时。最后说一个更实用的技巧把OnMessage的回调改为TThread.Queue投递到主线程后业务处理顺序就不保证是接收顺序了。如果你需要严格顺序比如同一个客户端发来的指令必须按序处理就不要用Queue改用带编号的消息队列在处理线程里按序号排序。这个坑在实现遥控命令类功能时非常明显连续两条指令第二条先执行会造成状态错乱。我的习惯是先跑通明文再上证书先单连接验证再压200连接先把广播合并再考虑多进程;上线前至少连续跑48小时日志里重点观察Connection closed是否正常回调、内存曲线是否平稳。这套WebSocket服务就能稳定跑很久你自己也不会因为凌晨三点被叫起来查死连接而痛苦。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询