Delphi Indy10 TIdTCPServer Linux环境下客户端断开失败问题咨询
根因分析
- Indy10在Windows和Linux平台的底层IO模型实现不一致:Windows采用IOCP完成端口模型,异常断开的连接会被系统自动通知Indy回收对应TIdContext;Linux采用per-connection线程+select模型,无RST包的异常断开场景下,默认的
Disconnect调用不会触发Context队列的移除逻辑,导致条目残留 - TIdTCPConnection内置的
FConnectionHandle临界区在Linux下基于pthread_mutex实现,无获取超时机制:如果在非Indy工作线程(如自定义超时检测线程)中直接调用Binding.CloseSocket,会和Indy内部OnExecute/OnDisconnect事件的执行线程抢占临界区,触发死锁导致调用无响应,多次重复调用会触发临界区未定义行为,直接导致进程崩溃无日志
解决方案
1. 调整断开逻辑的执行上下文,避免跨线程操作
给自定义TDTBClient类新增待断开标记属性,外部超时检测线程仅负责修改该标记,所有断开操作统一放到Indy的OnExecute事件中执行,从根源上避免临界区抢占问题:
// 自定义TDTBClient类新增属性 type TDTBClient = class public NeedDisconnect: Boolean; // 其他原有属性 end;
2. 实现线程安全的断开逻辑
在OnExecute事件的循环头部优先检测断开标记,在Indy自身的工作线程中执行断开,Indy会自动完成Context回收:
procedure TYourServerForm.IdTCPServerExecute(AContext: TIdContext); var LClient: TDTBClient; begin LClient := TDTBClient(AContext.Data); // 优先检测断开标记 if LClient.NeedDisconnect then begin AContext.Connection.Disconnect(False); // 跳过优雅断开,直接终止连接 Exit; // 退出事件后Indy会自动从Context列表移除当前条目 end; // 原有业务逻辑 end;
3. 修复服务端关闭卡死问题
关闭服务端前不要直接设置Active := False,先主动清理所有存活连接,Linux下增加回收等待时间:
procedure TYourServerForm.StopServer; var I: Integer; LContextList: TList; begin if not IdTCPServer.Active then Exit; LContextList := IdTCPServer.Contexts.LockList; try for I := 0 to LContextList.Count - 1 do begin TIdContext(LContextList[I]).Connection.Disconnect(False); end; finally IdTCPServer.Contexts.UnlockList; end; Sleep(150); // Linux下预留150ms等待Context回收完成,避免残留条目卡主关闭流程 IdTCPServer.Active := False; end;
4. 开启TCP层保活自动清理死连接
在OnConnect事件中为每个新连接开启TCP KeepAlive,从内核层面自动识别无响应的死连接,减少手动断开的需求:
procedure TYourServerForm.IdTCPServerConnect(AContext: TIdContext); begin // 开启保活:30秒无流量开始发保活包,每3秒发一次,累计3次失败判定为死连接 AContext.Connection.Socket.Binding.SetKeepAliveValues(True, 30000, 3000); // 绑定自定义TDTBClient实例 AContext.Data := TDTBClient.Create; end;
5. 可选优化:升级Indy组件版本
Delphi 10.4自带的Indy10存在多个Linux平台专属的临界区、Context回收bug,可手动升级到Indy官方最新主干版本,已修复多数已知Linux兼容性问题。
内容的提问来源于stack exchange,提问作者Bart Kindt
相关产品推荐
相关产品推荐

