简介Indy 10.2.3 是 Delphi 开发环境中常用的网络通信组件库主要面向 Delphi 2007 至 2010 版本的桌面与网络应用开发者帮助快速实现 TCP/IP、HTTP、FTP、SMTP、POP3 等协议通信降低底层 Socket 开发门槛。压缩包共包含 2000 个文件约 6.39MB以 pas 源代码、txt 说明与变更日志、bmp/gif 图标、dpk 工程文件、rc 资源脚本为主并包含 NUnit 单元测试、构建批处理、DLL、可执行程序及多种配置文件既适合直接安装集成也适合通过源码学习组件内部机制。包内变更日志清晰记录了相对早期版本在性能优化、错误修复、SSL/TLS 新版本支持及异步处理等方面的改进Test 与 NUnit 目录提供测试用例Lib、Builder、icons、KB 等目录分别对应库文件、构建工具、界面图标与常见问题知识库可辅助完成编译部署和问题排查。目前已有 170 人学习浏览对于需要兼容旧版 Delphi 并快速搭建网络功能的开发者来说这份资源兼具参考价值与实用价值。1. indy10.2.3老 Delphi 项目里最容易被甩锅、又最不敢动的网络层某个维护了三年的 Delphi 服务端项目最近一次组件升级后客户端连接频繁超时、内存稳定上涨查了两天没有头绪。最后把网络组件回退到 indy10.2.3重启后一切恢复平静。这不是巧合而是 Indy 版本行为差异导致的典型问题。indy10.2.3 是 Indy 10 系列里一个偏早期但非常稳定的版本很多旧工程的后端服务、客户端通信、HTTP 上报都锁在这个版本上。它的定位很简单不追新特性只求 TCP 收发行为可预期、依赖干净、升级成本可控。这篇文章写给两类人一类是被老项目绑在 indy10.2.3 上、想弄清楚它到底能干什么的维护者另一类是想从高版本降下来、又担心踩坑的开发者。后面所有内容都围绕一件事怎么把 indy10.2.3 跑稳、跑明白。2. 先搞清楚 indy10.2.3 是什么版本定位与依赖锁定2.1 10.2.3 在 Indy 10 系列里的位置Indy 10 从早期版本一路迭代到 10.6.x中间经历过多次内部接口调整。10.2.3 属于 10.2 这条分支的后期修订版它的特点是TIdTCPServer、TIdTCPClient、TIdHTTP 这些核心组件的基本模型已经定型和后续版本差别不大但它在 SSL 处理、默认编码、异常类型上保留了更老的行为习惯。比如 10.2.3 里 OpenSSL 组件默认找的是 libeay32.dll 和 ssleay32.dll 这两个旧命名文件而后面的版本已经切换到 libcrypto 和 libssl 命名。这个差异对只看代码不看底层 DLL 的人来说就是典型的黑匣子问题代码没变换了版本就崩。另一个容易被忽略的点是包结构。10.2.3 把核心运行包和设计时包分开核心包不依赖 IDE 运行设计时包才负责组件面板显示。如果你只关心服务端部署可以只编译核心包不装设计时包如果你需要在 IDE 里拖动组件才需要编译并安装 dclIndy 系列包。这一点在升级时特别重要因为很多安装教程把两步混在一起导致运行时缺包或者 IDE 直接起不来。从选型的角度看10.2.3 并不是最先进的版本但它有一个不可替代的优势行为可预期。很多老项目针对它的超时机制、编码默认值、异常类型做过大量适配代码里可能到处都是针对 Indy 10.2.3 的 workaround。升级换版本意味着所有 workaround 都要重新验证这个成本往往比换掉整个网络层还高。2.2 为什么旧的 10.2.3 反而更稳编码、超时与 DLL 行为差异升级到新版本后最常见的三个差异点恰好对应 Indy 最容易出问题的三个维度。第一是默认编码。10.2.3 里 TIdIOHandler 读字符串时默认使用 IndyTextEncoding_Default也就是跟随系统区域设置的 ANSI 编码。而新版本在多数情况下更倾向于 UTF-8。如果你的服务端和客户端约好的是 GBK 或 ANSI升级后就可能出现乱码。这种现象在日志类服务里非常常见表面上看是新版本有 bug实际上只是默认编码变了。第二是超时语义。10.2.3 的 ConnectTimeout 和 ReadTimeout 单位是毫秒并且 ReadTimeout 绑定在 IOHandler 上。后续版本对超时的判定逻辑做过调整比如对空闲连接的处理更激进。一个在 10.2.3 下能稳定保持一周的长连接在新版本下可能被超时机制主动断开。对于心跳间隔比较久的连接这种差异是致命的。第三是 OpenSSL DLL 的加载行为。10.2.3 只认旧的 DLL 文件名顺序也有讲究先找程序目录再找系统目录。新版本则要求较新的 OpenSSL 版本并且文件名不同。老项目里如果直接拷贝 DLL 到 exe 目录就能用升级后同样的操作会失效。这三个差异汇总成一张对比表维度indy10.2.3 行为后续版本常见行为影响默认字符串编码跟随系统 ANSI 区域设置更倾向 UTF-8中文乱码、日志乱码超时单位与触发逻辑毫秒按 IOHandler 判定多次调整判定逻辑长连接被无故断开OpenSSL DLL 文件名libeay32.dll / ssleay32.dlllibcrypto / libsslSSL 握手失败设计时包依赖核心包与设计时包分离延续分离但包名变化安装路径易混乱所以如果你接手的是一个基于 10.2.3 的老项目别急着升级。先评估这三个维度在代码里是否被依赖再决定要不要动。很多时候不动就是最稳的方案。3. 把 10.2.3 装进现有 IDE卸载、编译与路径设置3.1 先确认现在装的是哪个版本动手之前先确认当前 IDE 里实际加载的 Indy 版本。这个步骤很多人跳过结果编译了半天用的是旧包问题依旧。最可靠的办法是写一行代码在运行时输出版本号uses IdGlobal; procedure TForm1.Button1Click(Sender: TObject); var V: string; begin V : IdVersion; ShowMessage(V); // 会输出类似 10.2.3 的版本字符串 end;逻辑说明IdGlobal 单元里的 IdVersion 常量在编译期由 Indy 的头文件定义直接引用即可拿到当前生效的版本号。注意如果 IDE 里同时存在多个版本的 Indy 源码头文件编译顺序决定了这个值取的是哪一份。参数说明如果输出结果是 10.1.x 或 10.2.0说明当前工程实际链接的不是 10.2.3后续操作要以这个输出为准而不是看 IDE 组件面板里显示的包名。组件面板显示的是设计时包运行时代码链接的是核心包两者可能不一致。3.2 卸载旧包、编译 10.2.3 源码的完整顺序确认旧版本后按顺序做以下操作。顺序不能乱否则会出现设计时包指向新版本、运行时链接旧版本的错位问题。第一步打开 IDE 的 Component Install Packages 列表找到所有名称里带 Indy 的包例如 dclIndyCore.bpl、dclIndyProtocols.bpl点击 Remove 卸载。这一步只移除设计时注册不影响已编译工程里的运行时代码。第二步关闭 IDE删除临时目录里残留的.bpl和.dcp文件。常见位置是 IDE 的默认库输出目录以及系统临时目录。残留文件是很多疑难问题的根源版本混用往往就是这里产生的。第三步重新打开 IDE在 Tools Options Delphi Options Library Library path 中把 10.2.3 的源码目录加到最前面。注意要加的是包含IdTCPClient.pas、IdTCPServer.pas的根目录而不是某个子目录。路径顺序决定编译器优先使用哪份源码。第四步打开源码目录里的包工程文件例如IndyCore.dpk、IndyProtocols.dpk先编译 IndyCore再编译 IndyProtocols最后编译对应的设计时包。编译过程中如果提示找不到某个单元优先检查库路径是否把源码根目录放进去了。第五步重新打开组件面板确认 TIdTCPClient、TIdTCPServer 等组件出现。之后回到工程里重新编译再用上一节的 IdVersion 验证一次。这个顺序的目的是保证源码路径唯一、编译顺序正确、设计时包与核心包一致。任何一步错位都会在高版本和低版本之间来回横跳。3.3 编译参数、库路径与条件编译在实际工程里除了 IDE 库路径还要检查工程的搜索路径。有些工程在 project 文件里直接写死了旧版本的绝对路径这种情况下就算 IDE 库里加了新路径编译器仍然优先使用工程里写死的路径。// 在工程源文件里加条件编译避免头文件版本不一致 {$IFDEF INDY10} uses IdGlobal, IdTCPClient, IdTCPServer, IdHTTP; {$ENDIF}逻辑说明使用条件编译把 Indy 相关单元包裹起来可以防止在非 Indy 环境下编译时报错。INDY10 这个符号需要在 Project Options Delphi Compiler Conditional defines 里手动添加。参数说明库路径优先级从高到低依次是工程搜索路径、IDE 库路径、系统默认路径。如果你在 IDE 里改了路径但没有生效优先检查工程文件里是否有额外的 Search path。另外10.2.3 里 OpenSSL 相关单元需要确定是否启用了USE_OPENSSL条件编译如果启用了运行时就必须提供对应的 DLL否则 SSL 组件在实例化时会报错。编译参数上建议关闭运行时主题相关影响但这不是必须真正必须的是把Assertions保持开启这样 Indy 内部不少断言能在开发阶段暴露出问题发布时再关闭。这个习惯能省掉很多排查时间属于典型的血泪经验。4. 用 indy10.2.3 把 TCP 收发跑起来三个高频组件的参数4.1 服务端最小可运行示例TIdTCPServerTIdTCPServer 是 Indy 10.2.3 里最常用的服务端组件。它的工作模型是监听指定端口每个连接进入后在独立线程里执行 OnExecute 事件。在 10.2.3 里这个模型的线程模型已经非常成熟不需要额外创建线程。procedure TForm1.FormCreate(Sender: TObject); begin IdTCPServer1.DefaultPort : 9000; IdTCPServer1.Active : True; end; procedure TForm1.IdTCPServer1Execute(AContext: TIdContext); var s: string; begin // ReadLn 默认按行读取行结束符是 CRLF s : AContext.Connection.IOHandler.ReadLn; // 原样回写并在末尾补上行结束符 AContext.Connection.IOHandler.Write(echo: s #13#10); end;逻辑说明AContext 是当前连接的上下文每个连接都有独立的 IOHandler。ReadLn 是阻塞读取会一直等到收到行结束符或者连接断开。Write 写出的内容不会自动追加行结束符所以要在字符串里手动加#13#10。这个细节经常导致客户端等不到回包是典型的“服务端没返回”假象。参数说明DefaultPort 是监听端口Active 置 True 表示启动监听。正式项目里建议把 Active 放到 FormShow 或单独的开始按钮里不要在 FormCreate 里直接启动因为窗口还没准备好时异常弹窗很难看。MaxConnections 默认是 0表示不限制如果需要限流设置成具体数字超过后新连接会被拒绝。ListenQueue 是内核监听队列长度高并发场景建议适当加大比如 100 到 200。在 10.2.3 里OnExecute 里如果发生未捕获异常会导致当前连接被断开但服务端监听不受影响。如果希望异常时不断开连接需要在 OnExecute 内部自己包一层 try/except。这个是 10.2.3 和后续版本行为差异比较大的地方后续版本对异常的处理更温和。4.2 客户端连接参数TIdTCPClient 的握手与超时客户端的核心参数是 Host、Port、ConnectTimeout、ReadTimeout。在 10.2.3 里这些参数单位都是毫秒ConnectTimeout 在 Connect 调用时生效ReadTimeout 绑定在 IOHandler 上。IdTCPClient1.Host : 127.0.0.1; IdTCPClient1.Port : 9000; // 建立 TCP 连接的超时时间 IdTCPClient1.ConnectTimeout : 3000; // 读取响应的超时时间 IdTCPClient1.ReadTimeout : 5000; IdTCPClient1.Connect; try // 发送一行数据 IdTCPClient1.IOHandler.Write(ping #13#10); // 读取服务端回包 Result : IdTCPClient1.IOHandler.ReadLn; finally IdTCPClient1.Disconnect; end;逻辑说明ConnectTimeout 控制的是 TCP 三次握手阶段ReadTimeout 控制的是读操作阶段。ReadTimeout 需要在 Connect 之后设置或者在连接前设置但确保 IOHandler 已经创建。在 10.2.3 里IOHandler 默认是 TIdIOHandlerStack它在第一次 Connect 时自动创建。参数说明ReadTimeout 赋值为 0 表示无限等待可能导致线程一直挂住不建议在生产环境使用。如果服务端是长连接且不定期推送数据ReadTimeout 应该设成一个较大的值比如 30000而不是无限。另外Write 调用后要立即 ReadLn中间不要做耗时操作否则服务端可能已经返回数据而客户端还在等 IOHandler 里的缓冲区凑满造成假死。10.2.3 里还有一个容易被忽略的参数BoundPort。如果需要客户端固定使用某个端口连接可以设置它但默认值为 0表示由系统随机分配。这个参数在防火墙严格的环境里很有用但设置后可能因端口被占用而连接失败需要配合异常处理。4.3 TIdHTTP 里的编码、代理与超时TIdHTTP 在 10.2.3 里是一个很完整的 HTTP 客户端但它对服务端返回的编码判断有一些历史包袱。默认情况下它根据 HTTP 响应头里的 Content-Type 的 charset 参数判断编码如果没有 charset就按 ISO-8859-1 处理。对于返回中文但没有声明 charset 的接口就会得到乱码。var Response: string; begin IdHTTP1.ReadTimeout : 5000; IdHTTP1.ConnectTimeout : 5000; // 手动指定响应编码绕开默认 ISO-8859-1 的坑 IdHTTP1.Response.ContentType : text/html; charsetutf-8; Response : IdHTTP1.Get(http://example.com/api); end;逻辑说明在发出 Get 请求前手动设置 Response.ContentType 的 charset可以强制 TIdHTTP 按 UTF-8 解码响应体。这只对本次请求生效下次请求前需要重新设置否则可能沿用上一次的编码设置。参数说明ConnectTimeout 在 10.2.3 的 TIdHTTP 里控制的是 TCP 连接阶段ReadTimeout 控制的是响应读取阶段。如果请求整个过程都很慢通常瓶颈在网络读取而不是连接。另外TIdHTTP 的 ProxyParams 属性组里可以配置代理在需要走代理的环境里需要设置 ProxyParams.ProxyServer 和 ProxyParams.ProxyPort。如果不需要代理确保 ProxyParams.ProxyPort 设置为 0否则可能出现“一连就挂”的玄学问题。5. indy10.2.3 避坑指南五个典型的翻车现场与排查顺序5.1 服务端收到一串乱码UTF-8 被当成 ANSI 读现象客户端发过来的中文服务端 ReadLn 之后变成乱码。原因客户端用 UTF-8 发送服务端 10.2.3 默认用系统 ANSI 编码读取两边字符集不一致。解决在连接建立后强制设置 IOHandler 的编码。AContext.Connection.IOHandler.DefStringEncoding : IndyTextEncoding_UTF8;逻辑说明DefStringEncoding 决定 ReadLn、Write 等字符串操作使用的编码。两端都设置成 IndyTextEncoding_UTF8 后乱码问题消失。注意 10.2.3 里这个属性在 IOHandler 上是直接可见的不需要额外引用单元。5.2 SSL 握手失败OpenSSL DLL 版本与文件名不匹配现象使用 TIdSSLIOHandlerSocketOpenSSL 的客户端连不上 10.2.3 的服务端报错提示 SSL 库加载失败。原因10.2.3 的 SSL 组件固定加载 libeay32.dll 和 ssleay32.dll而系统里只有 libcrypto/libssl。解决找到配套的旧版本 DLL 放到 exe 目录确保文件名与 10.2.3 预期一致。如果代码里有{$IFDEF USE_OPENSSL}确认编译时已经启用。压测时注意DLL 放在系统目录也能加载但优先级低于 exe 目录混放可能导致加载到错误版本。5.3 超时设置无效RTTI 看不见的默认值现象给 ReadTimeout 赋值 5000但服务端不返回时客户端仍然卡住十几秒才报错。原因在 10.2.3 里ReadTimeout 对 TIdTCPClient 直接生效但如果连接已经建立IOHandler 内部有自己维护的超时状态重新赋值后不会立即重置当前读操作的超时判定。解决不要在连接中途动态修改 ReadTimeout而是在 Connect 前一次性设置。如果必须中途修改先调用 IOHandler.ReadTimeout 赋值然后确保没有其他线程在同一个连接上同时做读操作。实践中也看到过多个线程共用一个客户端导致超时失灵的情况不建议那样用。5.4 服务端启动就报端口占用TIME_WAIT 造成的假象现象服务端 Close 后立刻重启报 10048 地址被占用。原因TCP 连接关闭后端口进入 TIME_WAIT 状态默认持续约 2 到 4 分钟。解决设置 ReuseSocket 属性为 True。10.2.3 的 TIdTCPServer 上设置ReuseSocket : rsTrue可以让服务端在端口处于 TIME_WAIT 时也能重新绑定。注意这个属性要在 Active 置 True 之前设置否则不生效。5.5 客户端连不上 IPv6 主机默认 IP 版本是 IPv4现象给 Host 传了一个 IPv6 地址Connect 报错。原因10.2.3 里 TIdTCPClient 的 IPVersion 默认是 ID_IPV4不会自动切到 IPv6 解析。解决设置IPVersion : ID_IPV6。如果主机名本身兼容 IPv6也可以使用 Host 方式让系统解析但 10.2.3 在纯 IPv6 环境下手动指定更可靠。这个坑在局域网 IPv6 项目里特别明显属于搜索词经常被问到的问题。6. 用自测脚本验证 indy10.2.3 的连接行为压测、抓包与固化参数验证 10.2.3 的服务端稳不稳不能只看能不能连通。我的习惯是写两个最小脚本一个循环发数据一个循环收数据把超时时间调到比实际业务低观察异常出现频率。procedure TForm1.Timer1Timer(Sender: TObject); begin try IdTCPClient1.Connect; IdTCPClient1.IOHandler.Write(heartbeat #13#10); s : IdTCPClient1.IOHandler.ReadLn; // 如果回包内容不是预期计数器加一 except Inc(FailCount); end; end;逻辑说明用 Timer 每 5 秒触发一次连接和读写模拟客户端心跳。FailCount 记录失败次数连续跑 12 小时数字稳定在 0 说明基础连接逻辑没大问题。压测时看三个数值连接数、内存、句柄数。在 Windows 任务管理器里看内存占用处理器数影响不大句柄数要关注因为连接未释放时句柄数会持续上涨。10.2.3 服务端没有内置连接池每个连接一个线程连接数过千时线程切换开销明显这时候应该检查代码里是否有连接未关闭的分支。如果句柄数持续上涨多半是某条异常路径没有调用 Disconnect。抓包观察也要成为习惯。连接建立后按正常业务发一包数据看 TCP 层面是否有重传客户端 Close 后观察服务端是否收到 FIN。如果 TCP 层没有异常问题大概率在应用层编码或超时设置上。最后说一个从项目里带出来的教训老项目里别因为某个新特性就动 Indy 版本。把 10.2.3 的核心参数、DLL 依赖、编码行为写成一份环境说明放在工程目录里比任何口头沟通都有效。每次接手的人先读那份说明再动代码能少踩一大半坑。希望帮到你。本文还有配套的精品资源点击获取