TCP类 提供基于 TCP 的网络收发:可作服务端监听,也可作客户端连接。支持文本与字节集、可选分包、TLS,以及异步发送文件(带进度)。
类型名必须写 TCP类。声明变量时一般会自动创建会话,多数情况不必手写 创建TCP()。
相关:WebSocket类(消息帧 / ws·wss)、HTTP类(请求响应)。
1. 创建与变量作用域
1.1 推荐写法:声明即创建
TCP类 srv
TCP类 cli
声明后即可调用方法、注册事件。需要显式赋值时再用:
TCP类 t = 创建TCP()
1.2 局部变量(函数内)
只在当前函数(或事件程序)内有效,离开函数后不可再使用该变量名。适合一次性短连接、临时探测。
函数 按钮探测.被单击() {
TCP类 cli
cli.置超时(3000)
如果 (cli.连接_同步("127.0.0.1", 9000) == 假) {
信息框(cli.错误信息, 0)
返回()
}
cli.发送("PING")
文本 resp = cli.接收(256)
信息框(resp, 0)
cli.断开()
}
注意:局部 TCP类 在函数结束后会话会随变量结束而不可再操作;长连接、要在多个事件里共用的实例,请用文件顶部变量或全局变量。
1.3 文件顶部变量(本源文件模块级)
写在 .yc 文件顶部、所有函数之外。本文件内任意函数、窗口事件都可访问,适合「一个窗口配套一套 TCP」。
' 文件:源代码/主窗口.yc(示意)
TCP类 g_srv
TCP类 g_cli
函数 服务进入(整数 id, 文本 IP, 整数 端口, 文本 IP端口) {
输出("进入 " + 到文本(id) + " " + IP端口)
}
函数 主窗口.创建完毕() {
g_srv.客户进入(&服务进入)
g_srv.数据到达(&服务数据)
g_srv.客户离开(&服务离开)
如果 (g_srv.启动(9000, "127.0.0.1") == 假) {
信息框(g_srv.错误信息, 0)
}
}
函数 主窗口.销毁() {
g_cli.断开()
g_srv.关闭全部()
}
文件顶部声明 TCP类 g_srv / g_cli 即可跨本文件事件与按钮复用同一会话。
1.4 全局变量(跨源文件)
需要多个 .yc 共用同一套 TCP 时,写在工程的 源代码/global.yc(或约定的全局源文件)顶部:
' 文件:源代码/global.yc
TCP类 全局服务端
TCP类 全局客户端
其它源文件可直接使用同名变量(工程内全局可见),无需再声明一遍:
' 文件:源代码/网络逻辑.yc
函数 启动服务() {
全局服务端.置分包模式(1)
全局服务端.客户进入(&进入)
全局服务端.数据到达(&收到)
全局服务端.客户离开(&离开)
全局服务端.启动(9000)
}
1.5 三种作用域对照
| 写法位置 | 可见范围 | 典型用途 |
|---|---|---|
函数内 TCP类 t |
仅该函数 | 短连接、探测、用完即断 |
本文件顶部 TCP类 g_xxx |
本文件全部函数/事件 | 单窗体长连接、回显服务 |
global.yc 顶部 |
整个工程源文件 | 多模块共用服务端/客户端 |
1.6 使用顺序(重要)
- 声明
TCP类变量 - 按需配置:超时、NoDelay、KeepAlive、TLS、分包
- 先注册事件,再
启动(或监听)/连接 - 在回调或其它逻辑里
发送/广播/发送文件 - 结束时
断开/关闭全部/重置
事件不是控件那种 函数 按钮1.被单击(),而是普通函数 + 对象.事件名(&函数名) 注册。
2. 连接 ID 约定
| 角色 | 连接 ID |
|---|---|
| 服务端 | 每个客户端一个正整数 ID,在「客户进入 / 数据到达 / 客户离开」等回调的第一个参数给出;也可用 取连接ID列表() |
| 客户端 | 本端唯一连接,ID 固定为 1 |
服务端发数据必须带 ID:srv.发送(id, "hi")。
客户端发数据不要带 ID:cli.发送("hi")。
3. 事件
事件均在 界面线程 回调。回调内不要长时间阻塞(大循环、同步死等);重活请放到线程类或尽快返回。
传 0 可清除该事件回调。
3.1 客户进入(推荐;旧名「用户进入」)
注册: 对象.客户进入(子程序指针 回调)(或 用户进入)
回调形参: 函数 名(整数 连接ID, 文本 IP, 整数 端口, 文本 IP端口)
- 服务端:有客户端接入时触发;
IP/端口/IP端口为对端地址(接入时缓存,后续包复用)。 - 客户端:仅 异步
连接()成功时触发;ID 为 1。 - 同步
连接_同步成功 不会 触发本事件。 IP/IP端口指针仅在本回调内有效。
函数 进入(整数 id, 文本 IP, 整数 端口, 文本 IP端口) {
输出("进入 id=" + 到文本(id) + " " + IP端口)
}
srv.客户进入(&进入)
cli.客户进入(&进入)
3.2 数据到达
注册: 对象.数据到达(子程序指针 回调)
回调形参: 函数 名(整数 连接ID, 文本 IP, 整数 端口, 文本 IP端口, 文本 数据)
- 分包模式
0:每次读到的 UTF-8 文本,可能粘包/半包。 - 分包模式
1/2:已拆好的完整一帧文本。 - 二进制请用「数据到达字节集」。可与文本回调同时注册,同一帧会各触发一次。
- 对端信息与「客户进入」一致,按连接缓存,不必每包再查。
函数 收到(整数 id, 文本 IP, 整数 端口, 文本 IP端口, 文本 data) {
srv.发送(id, data) ' 回显(服务端)
}
srv.数据到达(&收到)
注意:回调参数 data / IP 只在本函数内有效,不要赋给全局文本后留到定时器再读。
3.3 数据到达字节集
注册: 对象.数据到达字节集(子程序指针 回调)
回调形参: 函数 名(整数 连接ID, 文本 IP, 整数 端口, 文本 IP端口, 字节集 数据)
适合文件块、协议体等非 UTF-8 数据。
函数 收到字节(整数 id, 文本 IP, 整数 端口, 文本 IP端口, 字节集 data) {
srv.发送字节集(id, data)
}
srv.数据到达字节集(&收到字节)
3.4 客户离开(推荐;旧名「用户离开」)
注册: 对象.客户离开(子程序指针 回调)(或 用户离开)
回调形参: 函数 名(整数 连接ID, 文本 IP, 整数 端口, 文本 IP端口)
对端关闭、本端 断开、关闭全部、网络错误断开时都会触发。
函数 离开(整数 id, 文本 IP, 整数 端口, 文本 IP端口) {
输出("离开 " + 到文本(id) + " " + IP端口)
}
srv.客户离开(&离开)
3.5 连接失败
注册: 对象.连接失败(子程序指针 回调)
回调形参: 函数 名(文本 原因)
仅客户端 异步 连接() 失败时触发。连接_同步 失败请看返回值与 取错误() / 错误信息。
函数 失败(文本 原因) {
信息框(原因, 0)
}
cli.连接失败(&失败)
3.6 发送进度
注册: 对象.发送进度(子程序指针 回调)
回调形参: 函数 名(整数 连接ID, 长整数 已发送, 长整数 总计)
由 发送文件 触发。开始时会先报一次 (0, 总计),之后每发出一块再报;空文件也会报 (0, 0)。
函数 进度(整数 id, 长整数 sent, 长整数 total) {
输出(到文本(sent) + "/" + 到文本(total))
}
cli.发送进度(&进度)
3.7 发送完毕
注册: 对象.发送完毕(子程序指针 回调)
回调形参: 函数 名(整数 连接ID, 逻辑 成功, 文本 错误)
发送文件 成功或失败都会触发。断开、关闭全部会取消进行中的发送并走失败。
函数 发完(整数 id, 逻辑 ok, 文本 err) {
如果 (ok) {
输出("发送完成")
} 否则 {
输出(err)
}
}
cli.发送完毕(&发完)
4. 属性(只读,无括号)
属性与同名「取××」方法等价,可写成 t.超时 或 t.取超时()。
| 属性 | 类型 | 说明 |
|---|---|---|
超时 |
整数 | 当前超时毫秒数;写入请用 置超时 |
错误信息 |
文本 | 最近一次失败原因 |
模式 |
文本 | "客户端" / "服务端" / "空闲" |
是否已连接 |
逻辑 | 客户端是否仍保持连接 |
是否在监听 |
逻辑 | 服务端是否正在监听 |
是否连接中 |
逻辑 | 异步连接是否仍在进行 |
客户端数 |
整数 | 服务端当前在线连接数 |
端口 |
整数 | 服务端=监听端口;客户端=连接端口 |
绑定地址 |
文本 | 服务端监听绑定 IP |
远程地址 |
文本 | 已连接远端 ip:port |
连接地址 |
文本 | 客户端目标主机(不含端口) |
TLS |
逻辑 | 是否已启用 TLS 模式 |
TLS协议 |
文本 | 最近握手协商协议,如 TLS 1.2 |
要求客户端证书 |
逻辑 | 服务端是否要求客户端证书 |
分包模式 |
整数 | 0 / 1 / 2 |
整数 ms = t.超时
文本 e = t.错误信息
如果 (cli.是否已连接) {
输出(cli.远程地址)
}
5. 方法说明
下列签名中,方括号表示可选参数。服务端/客户端参数差异已在说明中标出。
5.1 基础配置
置超时 / 取超时
对象.置超时([整数 毫秒=30000])
整数 = 对象.取超时()
≤0 时回退为 30000。影响连接与收发等待。
置NoDelay / 置KeepAlive
对象.置NoDelay([逻辑 启用=真])
对象.置KeepAlive([逻辑 启用=真])
默认均为开:NoDelay 降低小包延迟;KeepAlive 利于长连接保活。
5.2 TLS(须在监听/连接之前设置)
| 方法 | 签名 | 说明 |
|---|---|---|
| 置TLS | 置TLS([逻辑 启用=真]) |
后续连接走 TLS |
| 置TLS验证 | 置TLS验证([逻辑 验证=真]) |
客户端是否验证服务端证书;假仅建议测试 |
| 置TLS主机名 | 置TLS主机名(文本 主机名) |
客户端 SNI/校验证书用;省略时常用连接地址 |
| 置TLS证书 | 置TLS证书(文本 证书文件, [文本 密码=""]) → 逻辑 |
服务端 PFX/P12 |
| 置TLS根证书 | 置TLS根证书(文本 根证书文件) → 逻辑 |
追加自签/自定义 CA(CER/DER) |
| 置TLS客户端证书 | 置TLS客户端证书(文本 证书文件, [文本 密码=""]) → 逻辑 |
客户端双向认证 |
| 置TLS要求客户端证书 | 置TLS要求客户端证书([逻辑 要求=真], [逻辑 验证=真]) |
服务端要求客户端证书 |
| 是否TLS | 是否TLS() → 逻辑 |
或属性 TLS |
| 是否要求客户端证书 | 是否要求客户端证书() → 逻辑 |
或属性 要求客户端证书 |
| 取TLS协议 | 取TLS协议() → 文本 |
或属性 TLS协议 |
srv.置TLS(真)
srv.置TLS证书("d:\\certs\\server.pfx", "password")
srv.启动(9443)
cli.置TLS(真)
cli.置TLS主机名("127.0.0.1")
cli.置TLS根证书("d:\\certs\\ca.cer") ' 自签时
cli.连接("127.0.0.1", 9443)
5.3 分包(粘包拆包)
须在收发前设置;双方模式必须一致。
| 模式 | 含义 |
|---|---|
0 |
原始字节流;数据到达 可能粘包/半包,需自拆 |
1 |
每帧前加 4 字节小端无符号长度(推荐二进制/JSON 消息) |
2 |
按分隔符切帧(适合文本行协议) |
对象.置分包模式([整数 模式=0])
整数 = 对象.取分包模式()
对象.置分包分隔符([文本 分隔符="\n"]) ' 仅模式 2
对象.置分包最大长度([整数 字节=1048576]) ' 单帧上限,默认 1MB
整数 = 对象.取分包最大长度()
模式 1/2 下发完整帧请用 发送分包;收完整帧走 数据到达 / 数据到达字节集。
传大文件不要指望一帧塞完:用 发送文件,或自行循环 发送字节集;1MB 是单帧上限,不是整文件上限。
5.4 服务端:启动与连接管理
启动(推荐;同「监听」)
逻辑 = 对象.启动(整数 端口, [文本 绑定地址="0.0.0.0"], [整数 积压队列=0])
逻辑 = 对象.监听(整数 端口, [文本 绑定地址="0.0.0.0"], [整数 积压队列=0]) ' 旧名
- 端口:
1~65535。 - 绑定地址:
0.0.0.0全部网卡;本机联调常用127.0.0.1。 - 积压队列:等待接入的连接个数(不是毫秒);
0或负数为系统默认。
返回真=已开始监听;假看 错误信息。须先注册相关事件。
停止 / 关闭全部 / 断开
对象.停止() ' 推荐;同「停止监听」——不再接受新连接;已有连接保留
对象.停止监听() ' 旧名
对象.关闭全部() ' 停止监听并断开全部连接
对象.断开(整数 连接ID) ' 踢掉指定连接(服务端)
查询
整数 = 对象.取客户端数()
整数数组 = 对象.取连接ID列表()
文本 = 对象.取本地地址([整数 连接ID=1])
文本 = 对象.取客户IP([整数 连接ID=1]) ' 推荐;对端 IP
整数 = 对象.取客户端口([整数 连接ID=1]) ' 对端端口
文本 = 对象.取客户地址([整数 连接ID=1]) ' 「IP:端口」;同旧「取客户端地址」
文本 = 对象.取客户端地址([整数 连接ID=1]) ' 旧名
文本 = 对象.取绑定地址()
整数 = 对象.取端口()
逻辑 = 对象.是否在监听()
5.5 客户端:连接与同步收发
连接(异步)
逻辑 = 对象.连接(文本 地址, [整数 端口=0])
- 地址可为
127.0.0.1、域名,或IP:端口/[IPv6]:端口。 - 端口为
0或省略时从地址字符串解析。 - 返回真只表示「已开始连接」,不代表已连上;成功走
客户进入,失败走连接失败。 - 也可轮询
是否已连接/是否连接中。
连接_同步
逻辑 = 对象.连接_同步(文本 地址, [整数 端口=0])
阻塞至成功或超时。返回真=已连上,可立刻 发送/接收;假看 错误信息。
不会触发 客户进入 / 连接失败。适合短请求;长连接+事件请用 连接()。
接收(同步)
文本 = 对象.接收([整数 最大长度=4096])
字节集 = 对象.接收字节集([整数 最大长度=4096])
单次读取上限默认 4096,硬上限 1MB。不等于完整消息;事件驱动场景请用 数据到达,勿在界面事件里死循环 接收。
其它客户端查询
逻辑 = 对象.是否已连接()
逻辑 = 对象.是否连接中()
文本 = 对象.取远程IP()
整数 = 对象.取远程端口()
文本 = 对象.取远程地址() ' 「IP:端口」;同 取客户地址()
文本 = 对象.取连接地址()
整数 = 对象.取连接端口()
对象.断开() ' 客户端省略连接ID
5.6 发送
发送 / 发送字节集
' 服务端
整数 = 对象.发送(整数 连接ID, 文本 数据)
整数 = 对象.发送字节集(整数 连接ID, 字节集 数据)
' 客户端
整数 = 对象.发送(文本 数据)
整数 = 对象.发送字节集(字节集 数据)
返回成功写出的字节数;≤0 失败,看 错误信息。文本按 UTF-8 写出。
发送分包
整数 = 对象.发送分包(整数 连接ID, 文本 数据) ' 服务端
整数 = 对象.发送分包(文本 数据) ' 客户端
按当前分包模式自动加长度前缀或分隔符。模式 0 时接近普通 发送。
广播
整数 = 对象.广播(文本 数据) ' 返回成功发送的连接数
整数 = 对象.广播字节集(字节集 数据)
仅服务端有意义:向全部在线客户端发送。
发送文件
异步按块发送本地文件(原始字节流,不套分包封装)。
逻辑 = 对象.发送文件(文本 路径) ' 客户端,默认分片 65536
逻辑 = 对象.发送文件(整数 连接ID, 文本 路径) ' 服务端,默认分片
逻辑 = 对象.发送文件(整数 连接ID, 文本 路径, 整数 分片大小) ' 自定义分片
逻辑 = 对象.发送文件(1, 文本 路径, 整数 分片大小) ' 客户端自定义分片:连接ID 写 1
- 返回真=已启动;假=立即失败(路径空、未连接等),看
错误信息。 - 启动后的失败只走「发送完毕」。
- 进度走「发送进度」。
- 建议双方
置分包模式(0),对端用「数据到达字节集」拼文件。 - 同一会话再次
发送文件,或断开/关闭全部,会取消进行中的发送。 - 分片
≤0用 65536;上限 1MB。
5.7 状态与清理
文本 = 对象.取模式() ' 或 对象.模式
文本 = 对象.取错误() ' 或 对象.错误信息
对象.重置() ' 关闭连接并恢复超时/NoDelay 等默认;事件回调也会清除,需重新注册
关闭全部 后可再次 监听 复用同一实例。
6. 综合实战示例
6.1 服务端回显(事件驱动)
文件顶部变量 + 先注册事件再监听。
TCP类 g_srv
函数 进入(整数 id, 文本 IP, 整数 端口, 文本 IP端口) {
输出("[进] " + 到文本(id) + " " + IP端口)
}
函数 收到(整数 id, 文本 IP, 整数 端口, 文本 IP端口, 文本 data) {
输出("[收] " + data)
g_srv.发送(id, data)
}
函数 离开(整数 id, 文本 IP, 整数 端口, 文本 IP端口) {
输出("[离] " + 到文本(id))
}
函数 主窗口.创建完毕() {
g_srv.置超时(30000)
g_srv.客户进入(&进入)
g_srv.数据到达(&收到)
g_srv.客户离开(&离开)
如果 (g_srv.启动(9000, "127.0.0.1") == 假) {
信息框(g_srv.错误信息, 0)
}
}
函数 主窗口.销毁() {
g_srv.关闭全部()
}
6.2 客户端异步连接 + 收发
TCP类 g_cli
函数 已连接(整数 id, 文本 IP, 整数 端口, 文本 IP端口) {
输出("已连接 " + IP端口)
g_cli.发送("HELLO")
}
函数 客户收到(整数 id, 文本 IP, 整数 端口, 文本 IP端口, 文本 data) {
输出("回包: " + data)
}
函数 连失败(文本 原因) {
信息框(原因, 0)
}
函数 按钮连接.被单击() {
g_cli.客户进入(&已连接)
g_cli.数据到达(&客户收到)
g_cli.连接失败(&连失败)
g_cli.客户离开(&客户离开)
如果 (g_cli.连接("127.0.0.1:9000") == 假) {
信息框(g_cli.错误信息, 0)
}
}
函数 客户离开(整数 id) {
输出("已断开")
}
函数 按钮断开.被单击() {
g_cli.断开()
}
6.3 同步短请求(局部变量)
适合「连上 → 发 → 收 → 断」的一次性逻辑,不必注册连接成功事件。
函数 按钮查询.被单击() {
TCP类 cli
cli.置超时(5000)
如果 (cli.连接_同步("127.0.0.1", 9000) == 假) {
信息框(cli.错误信息, 0)
返回()
}
cli.发送("TIME?\r\n")
文本 resp = cli.接收(1024)
如果 (resp == "") {
信息框(cli.错误信息, 0)
} 否则 {
信息框(resp, 0)
}
cli.断开()
}
6.4 长度前缀分包(双方一致)
TCP类 g_srv
TCP类 g_cli
函数 服务帧(整数 id, 文本 IP, 整数 端口, 文本 IP端口, 文本 data) {
g_srv.发送分包(id, data)
}
函数 客户帧(整数 id, 文本 IP, 整数 端口, 文本 IP端口, 文本 data) {
输出("完整帧: " + data)
}
函数 开始长度分包演示() {
g_srv.置分包模式(1)
g_cli.置分包模式(1)
g_srv.客户进入(&进入)
g_srv.数据到达(&服务帧)
g_srv.客户离开(&离开)
g_srv.启动(9001, "127.0.0.1")
g_cli.客户进入(&已连接)
g_cli.数据到达(&客户帧)
g_cli.连接失败(&连失败)
g_cli.连接("127.0.0.1", 9001)
}
' 连接成功后:
' g_cli.发送分包("AAA")
' g_cli.发送分包("BBB")
分隔符模式将双方改为 置分包模式(2),可选 置分包分隔符("\r\n"),发送仍用 发送分包。
6.5 广播通知
函数 按钮广播.被单击() {
整数 n = g_srv.广播("NOTICE: 服务器将维护\r\n")
输出("已发给 " + 到文本(n) + " 个客户端")
}
函数 按钮踢人.被单击() {
整数数组 ids = g_srv.取连接ID列表()
整数 i = 0
循环 (数组长度(ids), i) {
g_srv.断开(ids[i])
}
}
6.6 发送文件 + 进度
发送端与接收端建议 置分包模式(0);接收端用字节集回调拼文件(此处仅演示接收计数)。
TCP类 g_cli
TCP类 g_srv
长整数 g_已收
函数 进度(整数 id, 长整数 sent, 长整数 total) {
如果 (total > 0) {
输出("进度 " + 到文本(sent * 100 / total) + "%")
}
}
函数 发完(整数 id, 逻辑 ok, 文本 err) {
如果 (ok) {
输出("文件发送完成")
} 否则 {
信息框(err, 0)
}
}
函数 服务收字节(整数 id, 字节集 data) {
g_已收 = g_已收 + 字节集长度(data)
}
函数 按钮传文件.被单击() {
g_已收 = 0
g_srv.置分包模式(0)
g_srv.数据到达字节集(&服务收字节)
g_srv.客户进入(&进入)
g_srv.客户离开(&离开)
g_srv.启动(9002, "127.0.0.1")
g_cli.置分包模式(0)
g_cli.发送进度(&进度)
g_cli.发送完毕(&发完)
g_cli.客户进入(&已连接)
g_cli.连接失败(&连失败)
如果 (g_cli.连接_同步("127.0.0.1", 9002) == 假) {
信息框(g_cli.错误信息, 0)
返回()
}
' 默认分片
如果 (g_cli.发送文件("d:\\data\\demo.bin") == 假) {
信息框(g_cli.错误信息, 0)
}
' 自定义分片(客户端连接ID 固定为 1):
' g_cli.发送文件(1, "d:\\data\\demo.bin", 262144)
}
服务端向某个客户端推文件:
g_srv.发送进度(&进度)
g_srv.发送完毕(&发完)
g_srv.发送文件(id, "d:\\data\\push.bin", 65536)
6.7 全局变量跨文件
源代码/global.yc:
TCP类 聊天服务
TCP类 聊天客户
源代码/服务.yc:
函数 启动聊天服务() {
聊天服务.置分包模式(2)
聊天服务.置分包分隔符("\n")
聊天服务.客户进入(&聊天进入)
聊天服务.数据到达(&聊天收到)
聊天服务.客户离开(&聊天离开)
聊天服务.启动(9010)
}
源代码/客户.yc:
函数 连接聊天室() {
聊天客户.置分包模式(2)
聊天客户.置分包分隔符("\n")
聊天客户.客户进入(&客户进房)
聊天客户.数据到达(&客户收消息)
聊天客户.连接失败(&客户失败)
聊天客户.连接("127.0.0.1", 9010)
}
函数 发送聊天(文本 行) {
如果 (聊天客户.是否已连接) {
聊天客户.发送分包(行)
}
}
7. 常见注意点
- 先事件,后监听/连接;否则会漏掉进入、数据等回调。
- 服务端发送必带连接 ID;客户端发送不要带 ID(自定义分片的
发送文件(1, 路径, 分片)除外)。 - 回调在界面线程:不要
延时死等,也不要长时间占满 CPU。 连接()与连接_同步()事件行为不同;按场景二选一。- 分包模式双方一致;传文件优先
发送文件+数据到达字节集,不要用 1MB 单帧硬塞整文件。 - 长连接实例放文件顶部或
global.yc;局部变量只适合短请求。 - 失败统一查
对象.错误信息或对象.取错误()。 - 客户端
断开()会停止并回收接收线程;关窗前也会自动清理全部 TCP 会话。 - 支持库面板路径:扩展类库 → TCP类 → 事件 / 方法 / 属性。