简介本资源是基于C Builder开发的TCP客户端/服务器通信完整示例面向C初学者及网络编程入门者解决Windows平台下Socket通信基础实践难题。项目涵盖服务端监听、客户端连接、字符串收发等核心流程直观演示三次握手、连接管理与双向通信机制适用于构建聊天工具、远程控制或轻量级数据交换系统等教学与原型开发场景。压缩包共47个文件含可执行程序.exe、窗体定义.dfm、源码.cpp/.h、IDE工程文件.bdsproj、运行时依赖库.bpl/.dll及调试资源.tds/.res总大小1.91MB结构完整开箱即用。已有154人学习下载提供从编译到联调的全链路代码参考包含VCL组件集成细节、事件驱动逻辑与常见异常处理片段便于理解C Builder网络控件封装原理并快速迁移至实际项目。1. BCBTCPClientServerDEMO一个被低估的 C Builder 网络编程“活化石”它真能跑通现代 Windows 的 TCP 通信你手头有个.rar文件名字叫BCBTCPClientServerDEMO.rar解压后是两套 Delphi/C Builder 混编的.cpp/.dfm/.bpr工程——不是 VS 项目不是 CMakeLists.txt更不是 modern C20 的协程服务端。它用的是TClientSocket和TServerSocket这对 Borland 时代的 VCL 组件编译目标是 Win32 GUI 应用。很多人第一反应是“这玩意儿早该进博物馆了”但现实是在工业现场数据采集、老旧 PLC 上位机对接、教育机构嵌入式通信实验课、甚至某些军工配套软件的维护场景里这套代码至今还在跑而且跑得比你用 Qt 写的 demo 更稳。它不依赖 Boost.Asio 或 libuv不碰 OpenSSL不搞异步回调链就靠 VCL 封装的 Winsock API 主线程消息泵把 TCP 连接、收发、断连、重连这些事干得极简、极透明、极可控。本文不讲历史情怀只讲一件事如何在 Windows 10/11 下用最新版 RAD StudioC Builder 11 Alexandria真正跑通这个 DEMO并让它和 Python/Node.js/Java 客户端双向通信成功——不是“理论上可以”而是你敲完命令、点下 Run、看到Connected!和Received: Hello from client真实出现在窗体上。适合正在维护老系统、需要快速验证协议交互、或想从零理解 Win32 Socket 在 GUI 框架中如何落地的 C 实战者。2. 从 RAR 到可运行工程C Builder 环境搭建与 DEMO 工程迁移实操2.1 环境准备为什么必须用 C Builder 而不是 VS 或 MinGWBCBTCPClientServerDEMO的核心不是裸 socket而是TClientSocket和TServerSocket这两个 VCL 可视化组件。它们不是纯 C 类而是封装了WSAStartup、socket()、bind()、listen()、accept()、connect()、send()、recv()的完整生命周期并深度绑定到 VCL 的消息循环Application-ProcessMessages()和事件模型OnConnect、OnRead、OnError。VS 的 MFC 或 Win32 SDK 项目可以自己写 socket但无法直接加载.dfm窗体资源、无法响应TServerSocket::OnAccept事件——因为事件钩子是 VCL 特有的__closure机制。MinGW 更不行它根本没 VCL。所以第一步必须确认你用的是Embarcadero RAD StudioC Builder版本建议10.4 Sydney 或 11 Alexandria兼容性最好Win11 支持完善。安装时务必勾选C Development和VCL Framework不要只装 Delphi。验证方式打开 IDE → File → New → Other → Installed → CBuilder Projects → VCL Forms Application - C → 能成功新建空窗体工程说明环境就绪。提示如果你只有旧版 BCB6Borland C Builder 6它也能编译但默认不支持 Windows 10 的高 DPI 缩放和 TLS 1.2连接现代 HTTPS 代理或某些云服务会失败。强烈建议升级到 10.4。2.2 解压与工程结构识别.rar里的关键文件是什么解压BCBTCPClientServerDEMO.rar后典型目录结构如下BCBTCPClientServerDEMO/ ├── Client/ ← 客户端工程 │ ├── Client.cpp ← 主窗体实现 │ ├── ClientUnit.cpp ← 核心逻辑含 TClientSocket │ ├── ClientUnit.h │ ├── ClientUnit.dfm ← 窗体设计器文件含 TClientSocket 组件 │ └── Client.bpr ← Borland Project 文件类似 .vcxproj ├── Server/ ← 服务端工程 │ ├── Server.cpp │ ├── ServerUnit.cpp ← 含 TServerSocket │ ├── ServerUnit.h │ ├── ServerUnit.dfm │ └── Server.bpr └── README.txt ← 通常只有两行“运行 Server.exe再运行 Client.exe”重点看*.dfm文件——它是二进制或文本格式的窗体资源描述里面明确写着object ClientSocket1: TClientSocket Left 8 Top 8 Height 25 Width 121 Address 127.0.0.1 Port 8080 ClientType ctBlocking Parent Self end注意ClientType ctBlocking这是关键它表示阻塞模式非异步所有SendText()/ReceiveText()都会挂起主线程直到完成。这和现代非阻塞/IOCP 思路相反但正是它简单可靠的原因——没有回调地狱没有状态机OnConnect触发即代表连接建立OnRead触发即代表有数据可读。2.3 工程迁移从旧 BCB 版本到 RAD Studio 11 的三步适配旧版 BCB如 5/6生成的.bpr是文本格式新版 RAD Studio 默认用.cbproj。直接双击.bpr会提示转换。但转换常失败推荐手动重建新建空工程File → New → Other → CBuilder Projects → VCL Forms Application - C → 保存为Client路径选原 Client/ 目录下。复制窗体资源打开原ClientUnit.dfm用记事本找到object ClientSocket1: TClientSocket块全选复制在新工程中双击窗体进入设计器 → View → Tool Palette → Win32 → 找到TClientSocket组件拖到窗体上 → 右键 → Edit Text → 粘贴刚才复制的内容覆盖默认属性。同理处理ServerUnit.dfm中的TServerSocket。移植核心代码原ClientUnit.cpp中的void __fastcall TClientUnit::ClientSocket1Connect(TObject *Sender)事件函数直接复制到新工程ClientUnit.cpp的对应位置。注意检查头文件包含#include vcl.h #pragma hdrstop #include ClientUnit.h #include ScktComp.hpp // 必须加否则 TClientSocket 不识别参数说明#include ScktComp.hpp是 VCL Socket 组件的头文件没有它编译器报错E2268: Cannot instantiate abstract class TClientSocket。这是新手最常卡住的一步。3. 让 Server 和 Client 真正对话TCP 连接建立、数据收发与事件驱动逻辑拆解3.1 Server 端TServerSocket 的监听与 Accept 流程TServerSocket的工作流是典型的 Win32listen()→accept()循环但被 VCL 封装成事件驱动// ServerUnit.cpp void __fastcall TServerUnit::ServerSocket1Accept(TObject *Sender, TCustomWinSocket *ClientSocket) { // 此事件在每次有新连接接入时触发 // ClientSocket 是新分配的 socket 句柄代表该客户端连接 Memo1-Lines-Add(Client connected: ClientSocket-RemoteAddress); // 关键必须设置 ClientSocket 的 OnRead 事件否则收不到数据 ClientSocket-OnRead ClientSocketRead; } //--------------------------------------------------------------------------- void __fastcall TServerUnit::ClientSocketRead(TObject *Sender, TCustomWinSocket *Socket) { // Sender 是 TServerSocketSocket 是具体客户端 socket String data Socket-ReceiveText(); // 阻塞读取直到 \r\n 或超时 if (!data.IsEmpty()) { Memo1-Lines-Add(Received: data); // 回复客户端 Socket-SendText(Echo: data); } }逻辑说明OnAccept不是“连接已建立”而是“连接请求到达已 accept 完毕返回新 socket”。TCustomWinSocket* Socket参数就是这个新 socket它有自己的OnRead/OnError事件。ReceiveText()默认以\r\n为分隔符读取一行不是 recv() 的 raw buffer所以客户端必须发带换行的数据如Hello\r\n。参数说明ReceiveText()可传入超时毫秒数如ReceiveText(5000)表示最多等 5 秒。若超时返回空字符串需检查Socket-LastError。3.2 Client 端TClientSocket 的连接与 SendText/ReceiveText 协作客户端流程更线性// ClientUnit.cpp void __fastcall TClientUnit::Button1Click(TObject *Sender) { // Button1 是 “Connect” 按钮 if (ClientSocket1-Active) { ClientSocket1-Close(); Button1-Caption Connect; return; } try { ClientSocket1-Address 127.0.0.1; // 可改为服务器 IP ClientSocket1-Port 8080; ClientSocket1-Open(); // 阻塞调用成功才返回 Button1-Caption Disconnect; Memo1-Lines-Add(Connected to server!); } catch (Exception e) { Memo1-Lines-Add(Connect failed: e.Message); } } //--------------------------------------------------------------------------- void __fastcall TClientUnit::ClientSocket1Connect(TObject *Sender) { // 连接成功后自动触发 Memo1-Lines-Add(Connection established.); } void __fastcall TClientUnit::Button2Click(TObject *Sender) { // Button2 是 “Send” 按钮 if (ClientSocket1-Active) { String msg Edit1-Text; // Edit1 是输入框 ClientSocket1-SendText(msg \r\n); // 必须加 \r\n Memo1-Lines-Add(Sent: msg); Edit1-Clear(); } }逻辑说明ClientSocket1-Open()是阻塞的内部调用connect()失败抛异常。SendText()发送后立即返回但实际发送由底层 Winsock 异步完成VCL 封装了 WSASend。OnRead事件在服务端SendText()后触发客户端需自行实现ClientSocket1Read处理回复。参数说明SendText()默认使用AnsiString若发中文需确保Edit1-Text是 UTF-8 编码VCL 默认 ANSI否则乱码。解决方案ClientSocket1-SendText(UTF8Encode(Edit1-Text) \r\n);3.3 跨平台/跨语言互通验证用 Python netcat 模拟客户端别只信两个 BCB 窗口互相发消息。用 Python 验证真实性和兼容性# test_client.py import socket import time s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((127.0.0.1, 8080)) s.send(bHello from Python\r\n) time.sleep(0.1) # 等待服务端处理 response s.recv(1024) print(Server reply:, response.decode(utf-8, errorsignore)) s.close()运行此脚本前先启动Server.exe确保端口 8080 未被占用。输出应为Server reply: Echo: Hello from Python这证明TServerSocket监听的是标准 TCP socket无私有协议ReceiveText()的\r\n分隔符是通用约定非 BCB 特有服务端能正确处理非 VCL 客户端的连接。4. 避坑指南BCB TCP Demo 在现代 Windows 下的 5 个血泪经验4.1 现象Server.exe 启动后Memo1显示 “Listening on port 8080”但 Python 客户端connect()报错[WinError 10061] No connection could be made because the target machine actively refused it原因Windows 防火墙默认阻止新程序的入站连接。TServerSocket启动时未弹出防火墙授权窗口旧版 BCB 会新版 RAD Studio 不触发。解决手动添加防火墙规则控制面板 → Windows Defender 防火墙 → 高级设置 → 入站规则 → 新建规则 → 程序 → 选择Server.exe路径 → 允许连接 → 命名 “BCB Server Port 8080”。4.2 现象Client.exe 点击 Connect 按钮后界面卡死超过 30 秒最后弹出异常 “Connection timed out”原因TClientSocket默认连接超时是 30 秒且Open()是阻塞调用卡住整个 GUI 线程。解决改用非阻塞模式 定时器轮询不推荐破坏原设计或更优解在OnConnecting事件中设短超时void __fastcall TClientUnit::ClientSocket1Connecting(TObject *Sender, TCustomWinSocket *Socket) { // 设置连接超时为 5 秒需在 Open() 前设置 Socket-SetSockOpt(SOL_SOCKET, SO_SNDTIMEO, (const char*)timeout, sizeof(timeout)); }但SetSockOpt在TClientSocket中不可直接调用。务实方案用TTimer启动连接超时后Close()并提示用户。4.3 现象发送中文字符串服务端ReceiveText()返回乱码如 “浣犲ソ”原因VCL 默认使用系统 ANSI 编码GB2312/GBK而 Python 默认 UTF-8。SendText()发送的是 ANSI 字节服务端ReceiveText()按 ANSI 解码但 Python 用 UTF-8 解必然错。解决统一编码为 UTF-8客户端BCBClientSocket1-SendText(UTF8Encode(Edit1-Text) \r\n);服务端BCBString data UTF8Decode(Socket-ReceiveText());Python 客户端s.send(你好\r\n.encode(utf-8))4.4 现象Server.exe 接收多个客户端后某个客户端断开OnRead仍被频繁触发ReceiveText()返回空字符串原因TServerSocket的OnRead事件在 socket 关闭时也会触发因为底层recv()返回 0。原代码未判断 EOF。解决在ClientSocketRead中加 EOF 检查void __fastcall TServerUnit::ClientSocketRead(TObject *Sender, TCustomWinSocket *Socket) { String data Socket-ReceiveText(); if (data.IsEmpty()) { // 可能是客户端关闭连接 if (Socket-Connected false) { Memo1-Lines-Add(Client disconnected: Socket-RemoteAddress); return; } } // ... 正常处理 data }4.5 现象编译通过但运行时报错 “Cannot load package ‘vcl‘” 或 “Module not found: rtl110.bpl”原因RAD Studio 运行时包BPL未正确部署。TServerSocket依赖vcl.bpl和rtl.bpl它们必须与 EXE 同目录或在系统 PATH 中。解决项目 → Options → Packages → Runtime Packages → 勾选 “Build with runtime packages” → 确保vcl、rtl在列表中编译后将$(BDS)\bin\vcl110.bpl、$(BDS)\bin\rtl110.bpl复制到Server.exe同目录或更彻底项目 → Options → C Linker → Linking → “Link with runtime packages” 设为 False静态链接EXE 体积增大但免部署。5. 进阶技巧把 BCB TCP Demo 改造成生产可用的服务端非 GUI5.1 为什么 GUI 服务端不适合长期运行三个硬伤GUI 消息泵依赖TServerSocket必须运行在有Application-Run()的 GUI 线程中。一旦窗体最小化或失去焦点消息泵可能延迟导致OnRead响应变慢无守护进程能力Windows 服务要求无界面、自动启动、崩溃自恢复VCL 窗体应用无法注册为服务资源泄漏风险每个OnAccept创建的TCustomWinSocket*若未显式Close()在大量连接下内存持续增长VCL 不自动回收。所以真正的生产改造不是优化窗体而是剥离 VCL保留核心 socket 逻辑。5.2 剥离 VCL 的最小可行方案用 Win32 API 重写 ServerSocket 核心我们不重写整个网络栈只替换TServerSocket保留业务逻辑OnRead处理。以下是关键步骤新建控制台工程File → New → Console Application - C初始化 Winsock#include winsock2.h #pragma comment(lib, ws2_32.lib) int main() { WSADATA wsa; WSAStartup(MAKEWORD(2,2), wsa); // ... 后续 socket 逻辑 WSACleanup(); return 0; }用select()替代OnRead事件fd_set readfds; FD_ZERO(readfds); FD_SET(server_socket, readfds); int activity select(0, readfds, NULL, NULL, timeout); if (activity 0 FD_ISSET(server_socket, readfds)) { // 有新连接 SOCKET client accept(server_socket, NULL, NULL); FD_SET(client, readfds); // 加入监控 } // 遍历所有 client socket 检查可读 for (int i 0; i max_clients; i) { if (client_sockets[i] ! INVALID_SOCKET FD_ISSET(client_sockets[i], readfds)) { char buffer[1024] {0}; int bytes recv(client_sockets[i], buffer, sizeof(buffer)-1, 0); if (bytes 0) { // 处理 buffer调用你的业务函数原 OnRead 逻辑 process_message(buffer, client_sockets[i]); } else if (bytes 0) { // 客户端关闭 closesocket(client_sockets[i]); client_sockets[i] INVALID_SOCKET; } } }优势完全脱离 VCL可编译为 Windows 服务用CreateService()CPU 占用更低连接数上限更高不受 VCL 消息队列限制。5.3 保留 BCB 工程价值的混合架构GUI 配置 控制台服务最务实的落地方式用原Server.dprDelphi或Server.cppC做配置管理前端IP、端口、日志路径、启停按钮点击“Start Service”时用ShellExecute()启动一个独立的tcp_service.exe上面写的控制台服务配置变更写入config.initcp_service.exe定时 reload日志统一写入server.logGUI 端用TTimer每秒读取并显示最新行。这样既保留了 BCB 的快速开发优势窗体拖拽、事件绑定又规避了其运行时缺陷。我在某电力 SCADA 上位机项目中就是这么做的——客户要图形化操作运维要 7x24 稳定两者兼顾。我坚持在每个新项目里留一个 BCB TCP Demo 的备份工程不是怀旧是因为当 Qt 的 QWebSocket 在工控机上偶发卡死、当 Boost.Beast 的 SSL 握手耗时突增 200ms 时那个朴素的TServerSocket依然准时触发OnRead像钟表一样可靠。它提醒我网络编程的本质不是堆砌新特性而是让字节流在物理线缆上按约定的时间和顺序抵达该去的地方。希望帮到你。本文还有配套的精品资源点击获取