网络通信是易语言应用开发中的重要组成部分,但在实际开发中经常会遇到连接超时、数据粘包、内存泄漏等问题。本文将从实战角度出发,提供完整的TCP通信解决方案和常见问题处理方法。
![图片[1]-易语言网络通信实战:TCP客户端服务端开发与常见问题解决方案](https://blogimg.vcvcc.cc/2025/11/20251117123807652-1024x768.png?imageView2/0/format/webp/q/75)
一、高性能TCP服务端开发
(1) 多线程服务端架构
.版本 2
.支持库 sock
.支持库 EThread
.支持库 spec
.程序集 主程序集
.程序集变量 服务端套接字, 整数型
.程序集变量 是否运行, 逻辑型
.程序集变量 客户端列表, 客户端信息, , "0"
.程序集变量 线程池, 整数型, , "0"
.数据类型 客户端信息
.成员 套接字, 整数型
.成员 IP地址, 文本型
.成员 端口, 整数型
.成员 最后活动时间, 日期时间型
.成员 接收线程, 整数型
.子程序 __启动窗口_创建完毕
标题 = "易语言TCP服务端 - 端口: 8888"
.子程序 _按钮_启动服务_被单击
.局部变量 绑定结果, 逻辑型
服务端套接字 = 创建套接字 (AF_INET, SOCK_STREAM, 0)
.如果真 (服务端套接字 = -1)
信息框 ("创建套接字失败!", 0, , )
返回 ()
.如果真结束
绑定结果 = 绑定 (服务端套接字, "0.0.0.0", 8888)
.如果真 (绑定结果 = 假)
信息框 ("绑定端口失败!", 0, , )
关闭套接字 (服务端套接字)
返回 ()
.如果真结束
.如果真 (监听 (服务端套接字, 100) = 假)
信息框 ("监听失败!", 0, , )
关闭套接字 (服务端套接字)
返回 ()
.如果真结束
是否运行 = 真
启动线程 (&服务端主循环, , )
编辑框_日志.加入文本 ("服务端启动成功, 监听端口: 8888" + #换行符)
.子程序 服务端主循环
.局部变量 客户端套接字, 整数型
.局部变量 客户端地址, 文本型
.局部变量 客户端端口, 整数型
.局部变量 新客户端, 客户端信息
.判断循环首 (是否运行)
客户端套接字 = 接受连接 (服务端套接字, 客户端地址, 客户端端口)
.如果真 (客户端套接字 ≠ -1)
' 创建新的客户端信息
新客户端.套接字 = 客户端套接字
新客户端.IP地址 = 客户端地址
新客户端.端口 = 客户端端口
新客户端.最后活动时间 = 取现行时间 ()
' 启动接收线程
启动线程 (&客户端数据处理, 取变量地址 (新客户端), 新客户端.接收线程)
' 添加到客户端列表
加入成员 (客户端列表, 新客户端)
编辑框_日志.加入文本 ("新的客户端连接: " + 客户端地址 + ":" + 到文本 (客户端端口) + #换行符)
.如果真结束
程序_延时 (10)
.判断循环尾 ()
.子程序 客户端数据处理
.参数 客户端指针, 整数型
.局部变量 客户端, 客户端信息
.局部变量 接收数据, 文本型
.局部变量 数据长度, 整数型
.局部变量 缓冲区, 字节集
复制内存 (取变量地址 (客户端), 客户端指针, 取数据类型大小 (取数据类型 (“客户端信息”)))
.判断循环首 (是否运行)
' 设置接收超时为100毫秒
缓冲区 = 接收数据 (客户端.套接字, 1024, 100)
数据长度 = 取字节集长度 (缓冲区)
.如果真 (数据长度 > 0)
客户端.最后活动时间 = 取现行时间 ()
接收数据 = 到文本 (缓冲区)
编辑框_日志.加入文本 ("收到数据[" + 客户端.IP地址 + "]: " + 接收数据 + #换行符)
' 处理业务逻辑
处理客户端请求 (客户端, 接收数据)
.如果真结束
.如果真 (数据长度 = -1)
' 连接断开
编辑框_日志.加入文本 ("客户端断开: " + 客户端.IP地址 + #换行符)
关闭客户端连接 (客户端.套接字)
跳出循环 ()
.如果真结束
程序_延时 (10)
.判断循环尾 ()
.子程序 处理客户端请求
.参数 客户端, 客户端信息
.参数 请求数据, 文本型
.局部变量 响应数据, 文本型
.如果 (请求数据 = "TIME")
响应数据 = "当前时间: " + 到文本 (取现行时间 ())
.否则
响应数据 = "ECHO: " + 请求数据
.如果结束
发送数据到客户端 (客户端.套接字, 响应数据)
.子程序 发送数据到客户端, 逻辑型
.参数 套接字, 整数型
.参数 数据, 文本型
.局部变量 发送结果, 整数型
发送结果 = 发送数据 (套接字, 到字节集 (数据), 取文本长度 (数据))
返回 (发送结果 = 取文本长度 (数据))
.子程序 关闭客户端连接
.参数 套接字, 整数型
.局部变量 i, 整数型
.计次循环首 (取数组成员数 (客户端列表), i)
.如果真 (客户端列表 [i].套接字 = 套接字)
关闭套接字 (套接字)
删除成员 (客户端列表, i, 1)
跳出循环 ()
.如果真结束
.计次循环尾 ()
二、稳定可靠的TCP客户端
(1) 带自动重连的客户端
.版本 2
.支持库 sock
.支持库 EThread
.支持库 spec
.程序集 客户端程序集
.程序集变量 客户端套接字, 整数型
.程序集变量 是否连接, 逻辑型
.程序集变量 接收线程, 整数型
.程序集变量 服务器IP, 文本型
.程序集变量 服务器端口, 整数型
.程序集变量 重连次数, 整数型
.子程序 __启动窗口_创建完毕
标题 = "易语言TCP客户端"
服务器IP = "127.0.0.1"
服务器端口 = 8888
.子程序 _按钮_连接_被单击
.如果 (是否连接)
断开连接 ()
.否则
连接服务器 ()
.如果结束
.子程序 连接服务器
.局部变量 连接结果, 逻辑型
客户端套接字 = 创建套接字 (AF_INET, SOCK_STREAM, 0)
.如果真 (客户端套接字 = -1)
信息框 ("创建套接字失败!", 0, , )
返回 ()
.如果真结束
编辑框_日志.加入文本 ("正在连接服务器 " + 服务器IP + ":" + 到文本 (服务器端口) + "..." + #换行符)
连接结果 = 连接 (客户端套接字, 服务器IP, 服务器端口)
.如果真 (连接结果)
是否连接 = 真
重连次数 = 0
按钮_连接.标题 = "断开连接"
编辑框_日志.加入文本 ("连接服务器成功!" + #换行符)
' 启动数据接收线程
启动线程 (&数据接收循环, , 接收线程)
.否则
编辑框_日志.加入文本 ("连接服务器失败!" + #换行符)
关闭套接字 (客户端套接字)
尝试重连 ()
.如果真结束
.子程序 数据接收循环
.局部变量 接收数据, 文本型
.局部变量 数据长度, 整数型
.局部变量 缓冲区, 字节集
.判断循环首 (是否连接)
缓冲区 = 接收数据 (客户端套接字, 1024, 100)
数据长度 = 取字节集长度 (缓冲区)
.如果真 (数据长度 > 0)
接收数据 = 到文本 (缓冲区)
编辑框_日志.加入文本 ("收到服务器数据: " + 接收数据 + #换行符)
处理服务器消息 (接收数据)
.如果真结束
.如果真 (数据长度 = -1)
编辑框_日志.加入文本 ("与服务器的连接已断开!" + #换行符)
断开连接 ()
尝试重连 ()
跳出循环 ()
.如果真结束
程序_延时 (10)
.判断循环尾 ()
.子程序 断开连接
是否连接 = 假
.如果真 (客户端套接字 ≠ -1)
关闭套接字 (客户端套接字)
客户端套接字 = -1
.如果真结束
按钮_连接.标题 = "连接服务器"
.如果真 (接收线程 ≠ 0)
强制结束线程 (接收线程)
接收线程 = 0
.如果真结束
.子程序 尝试重连
.如果真 (重连次数 < 5) ' 最多重连5次
重连次数 = 重连次数 + 1
编辑框_日志.加入文本 ("第 " + 到文本 (重连次数) + " 次尝试重连..." + #换行符)
程序_延时 (2000) ' 等待2秒后重连
连接服务器 ()
.如果真结束
.子程序 _按钮_发送_被单击
.局部变量 发送数据, 文本型
.如果真 (是否连接 = 假)
信息框 ("请先连接服务器!", 0, , )
返回 ()
.如果真结束
发送数据 = 编辑框_发送内容.内容
.如果真 (发送数据 ≠ "")
.如果真 (发送数据到服务器 (发送数据))
编辑框_日志.加入文本 ("发送数据: " + 发送数据 + #换行符)
编辑框_发送内容.内容 = ""
.否则
编辑框_日志.加入文本 ("发送数据失败!" + #换行符)
.如果真结束
.如果真结束
.子程序 发送数据到服务器, 逻辑型
.参数 数据, 文本型
.局部变量 发送结果, 整数型
发送结果 = 发送数据 (客户端套接字, 到字节集 (数据), 取文本长度 (数据))
返回 (发送结果 = 取文本长度 (数据))
.子程序 处理服务器消息
.参数 消息, 文本型
' 根据不同的消息类型进行处理
.如果 (寻找文本 (消息, "时间", , 假) ≠ -1)
' 时间相关消息
标签_状态.标题 = "服务器时间: " + 消息
.否则
标签_状态.标题 = "最新消息: " + 取文本左边 (消息, 20)
.如果结束
三、数据协议设计与粘包处理
(1) 自定义协议格式解决粘包问题
.版本 2
.支持库 spec
.程序集 协议处理模块
.程序集变量 数据缓冲区, 字节集
.数据类型 数据包头
.成员 起始标志, 整数型 ' 固定值0xAA55
.成员 数据长度, 整数型 ' 实际数据长度
.成员 协议版本, 字节型 ' 协议版本号
.成员 命令类型, 字节型 ' 命令类型
.成员 校验和, 字节型 ' 简单的校验和
.子程序 封装数据包, 字节集
.参数 数据内容, 字节集
.参数 命令类型, 字节型
.局部变量 数据包头, 数据包头
.局部变量 完整数据包, 字节集
.局部变量 i, 整数型
.局部变量 校验和, 字节型
' 填充包头
数据包头.起始标志 = 43605 ' 0xAA55
数据包头.数据长度 = 取字节集长度 (数据内容)
数据包头.协议版本 = 1
数据包头.命令类型 = 命令类型
' 计算校验和
校验和 = 0
.计次循环首 (取字节集长度 (数据内容), i)
校验和 = 位异或 (校验和, 取字节集数据 (数据内容, i, #字节型))
.计次循环尾 ()
数据包头.校验和 = 校验和
' 组合完整数据包
完整数据包 = 到字节集 (数据包头) + 数据内容
返回 (完整数据包)
.子程序 解析数据包, 逻辑型
.参数 接收数据, 字节集
.参数 数据包内容, 字节集, 参考
.参数 命令类型, 字节型, 参考
.局部变量 包头大小, 整数型
.局部变量 数据包头, 数据包头
.局部变量 实际数据长度, 整数型
包头大小 = 取数据类型大小 (取数据类型 ("数据包头"))
' 检查数据长度是否足够
.如果真 (取字节集长度 (接收数据) < 包头大小)
返回 (假) ' 数据长度不足
.如果真结束
' 解析包头
复制内存 (取变量地址 (数据包头), 取字节集数据 (接收数据, 1, #整数型), 包头大小)
' 验证起始标志
.如果真 (数据包头.起始标志 ≠ 43605)
返回 (假) ' 起始标志错误
.如果真结束
' 检查数据完整性
实际数据长度 = 取字节集长度 (接收数据) - 包头大小
.如果真 (实际数据长度 < 数据包头.数据长度)
返回 (假) ' 数据不完整
.如果真结束
' 提取数据内容
数据包内容 = 取字节集中间 (接收数据, 包头大小 + 1, 数据包头.数据长度)
命令类型 = 数据包头.命令类型
返回 (真)
.子程序 处理粘包数据, 整数型
.参数 接收数据, 字节集
.参数 数据包列表, 文本型, 参考, "0"
.局部变量 当前位置, 整数型
.局部变量 数据长度, 整数型
.局部变量 包头大小, 整数型
.局部变量 数据包头, 数据包头
.局部变量 处理数量, 整数型
处理数量 = 0
数据长度 = 取字节集长度 (接收数据)
包头大小 = 取数据类型大小 (取数据类型 ("数据包头"))
当前位置 = 1
.判断循环首 (当前位置 ≤ 数据长度)
' 检查剩余数据是否足够解析包头
.如果真 (数据长度 - 当前位置 + 1 < 包头大小)
跳出循环 ()
.如果真结束
' 解析包头
复制内存 (取变量地址 (数据包头), 取字节集数据 (接收数据, 当前位置, #整数型), 包头大小)
' 验证起始标志
.如果真 (数据包头.起始标志 ≠ 43605)
当前位置 = 当前位置 + 1 ' 移动一个字节继续查找
到循环尾 ()
.如果真结束
' 检查是否有完整的数据包
.如果真 (数据长度 - 当前位置 + 1 < 包头大小 + 数据包头.数据长度)
跳出循环 () ' 数据不完整,等待更多数据
.如果真结束
' 提取完整数据包
数据包内容 = 取字节集中间 (接收数据, 当前位置 + 包头大小, 数据包头.数据长度)
加入成员 (数据包列表, 到文本 (数据包内容))
处理数量 = 处理数量 + 1
' 移动当前位置
当前位置 = 当前位置 + 包头大小 + 数据包头.数据长度
.判断循环尾 ()
' 保留未处理的数据
.如果真 (当前位置 ≤ 数据长度)
数据缓冲区 = 取字节集中间 (接收数据, 当前位置, 数据长度 - 当前位置 + 1)
.否则
数据缓冲区 = { }
.如果真结束
返回 (处理数量)
四、网络通信性能优化
(1) 连接池与心跳检测
.版本 2
.支持库 EThread
.支持库 spec
.程序集 连接管理模块
.程序集变量 连接池, 连接信息, , "0"
.程序集变量 心跳线程, 整数型
.程序集变量 是否运行, 逻辑型
.数据类型 连接信息
.成员 套接字, 整数型
.成员 IP地址, 文本型
.成员 端口, 整数型
.成员 最后活动时间, 日期时间型
.成员 是否空闲, 逻辑型
.成员 心跳次数, 整数型
.子程序 初始化连接池
.参数 初始连接数, 整数型
.参数 服务器IP, 文本型
.参数 服务器端口, 整数型
.局部变量 i, 整数型
.局部变量 连接, 连接信息
.计次循环首 (初始连接数, i)
连接.套接字 = 创建连接 (服务器IP, 服务器端口)
.如果真 (连接.套接字 ≠ -1)
连接.IP地址 = 服务器IP
连接.端口 = 服务器端口
连接.最后活动时间 = 取现行时间 ()
连接.是否空闲 = 真
连接.心跳次数 = 0
加入成员 (连接池, 连接)
.如果真结束
.计次循环尾 ()
' 启动心跳检测线程
是否运行 = 真
启动线程 (&心跳检测循环, , 心跳线程)
.子程序 获取空闲连接, 整数型
.局部变量 i, 整数型
.计次循环首 (取数组成员数 (连接池), i)
.如果真 (连接池 [i].是否空闲)
连接池 [i].是否空闲 = 假
连接池 [i].最后活动时间 = 取现行时间 ()
返回 (连接池 [i].套接字)
.如果真结束
.计次循环尾 ()
' 没有空闲连接,创建新的连接
返回 (创建新连接 ())
.子程序 释放连接
.参数 套接字, 整数型
.局部变量 i, 整数型
.计次循环首 (取数组成员数 (连接池), i)
.如果真 (连接池 [i].套接字 = 套接字)
连接池 [i].是否空闲 = 真
连接池 [i].最后活动时间 = 取现行时间 ()
跳出循环 ()
.如果真结束
.计次循环尾 ()
.子程序 心跳检测循环
.局部变量 i, 整数型
.局部变量 当前时间, 日期时间型
.判断循环首 (是否运行)
当前时间 = 取现行时间 ()
.计次循环首 (取数组成员数 (连接池), i)
' 检查连接是否超时(30秒无活动)
.如果真 (取时间间隔 (当前时间, 连接池 [i].最后活动时间, #秒) > 30)
.如果真 (检查连接状态 (连接池 [i].套接字) = 假)
' 连接已断开,重新建立
关闭套接字 (连接池 [i].套接字)
连接池 [i].套接字 = 创建连接 (连接池 [i].IP地址, 连接池 [i].端口)
.否则
' 发送心跳包
发送心跳包 (连接池 [i].套接字)
连接池 [i].心跳次数 = 连接池 [i].心跳次数 + 1
.如果真结束
连接池 [i].最后活动时间 = 取现行时间 ()
.如果真结束
.计次循环尾 ()
程序_延时 (5000) ' 5秒检测一次
.判断循环尾 ()
.子程序 检查连接状态, 逻辑型
.参数 套接字, 整数型
.局部变量 测试数据, 字节集
' 尝试发送0字节数据检测连接状态
返回 (发送数据 (套接字, { }, 0) ≠ -1)
.子程序 发送心跳包, 逻辑型
.参数 套接字, 整数型
.局部变量 心跳数据, 文本型
心跳数据 = "HEARTBEAT:" + 到文本 (取现行时间 ())
返回 (发送数据 (套接字, 到字节集 (心跳数据), 取文本长度 (心跳数据)) = 取文本长度 (心跳数据))
.子程序 创建连接, 整数型
.参数 服务器IP, 文本型
.参数 服务器端口, 整数型
.局部变量 套接字, 整数型
.局部变量 连接结果, 逻辑型
套接字 = 创建套接字 (AF_INET, SOCK_STREAM, 0)
.如果真 (套接字 = -1)
返回 (-1)
.如果真结束
连接结果 = 连接 (套接字, 服务器IP, 服务器端口)
.如果真 (连接结果 = 假)
关闭套接字 (套接字)
返回 (-1)
.如果真结束
返回 (套接字)
五、常见网络问题诊断
(1) 网络故障排查工具
.版本 2
.支持库 sock
.支持库 iext
.程序集 网络诊断工具
.程序集变量 诊断结果, 文本型, , "0"
.子程序 全面网络诊断
.局部变量 主机名, 文本型
.局部变量 IP地址, 文本型
.局部变量 i, 整数型
诊断结果 = { }
加入成员 (诊断结果, "=== 网络诊断报告 ===")
加入成员 (诊断结果, "诊断时间: " + 到文本 (取现行时间 ()))
' 1. 检查本地网络配置
检查本地网络配置 ()
' 2. 测试DNS解析
测试DNS解析 ("www.baidu.com")
' 3. 测试端口连接
测试端口连接 ("127.0.0.1", 8888)
' 4. 路由跟踪测试
路由跟踪测试 ("www.qq.com")
' 显示诊断结果
.计次循环首 (取数组成员数 (诊断结果), i)
编辑框_诊断结果.加入文本 (诊断结果 [i] + #换行符)
.计次循环尾 ()
.子程序 检查本地网络配置
.局部变量 本地IP, 文本型
.局部变量 网关, 文本型
本地IP = 取本机IP ()
网关 = 取默认网关 ()
加入成员 (诊断结果, "本地IP地址: " + 本地IP)
加入成员 (诊断结果, "默认网关: " + 网关)
.如果真 (本地IP = "")
加入成员 (诊断结果, "❌ 网络连接异常: 无法获取本地IP")
.否则
加入成员 (诊断结果, "✅ 网络连接正常")
.如果真结束
.子程序 测试DNS解析, 逻辑型
.参数 域名, 文本型
.局部变量 IP地址, 文本型
IP地址 = 域名转IP (域名)
.如果真 (IP地址 ≠ "")
加入成员 (诊断结果, "✅ DNS解析成功: " + 域名 + " -> " + IP地址)
返回 (真)
.否则
加入成员 (诊断结果, "❌ DNS解析失败: " + 域名)
返回 (假)
.如果真结束
.子程序 测试端口连接, 逻辑型
.参数 主机, 文本型
.参数 端口, 整数型
.局部变量 测试套接字, 整数型
.局部变量 连接结果, 逻辑型
.局部变量 开始时间, 整数型
.局部变量 耗时, 整数型
开始时间 = 取启动时间 ()
测试套接字 = 创建套接字 (AF_INET, SOCK_STREAM, 0)
.如果真 (测试套接字 = -1)
加入成员 (诊断结果, "❌ 创建套接字失败")
返回 (假)
.如果真结束
连接结果 = 连接 (测试套接字, 主机, 端口)
耗时 = 取启动时间 () - 开始时间
.如果真 (连接结果)
加入成员 (诊断结果, "✅ 端口连接成功: " + 主机 + ":" + 到文本 (端口) + " (耗时: " + 到文本 (耗时) + "ms)")
关闭套接字 (测试套接字)
返回 (真)
.否则
加入成员 (诊断结果, "❌ 端口连接失败: " + 主机 + ":" + 到文本 (端口))
关闭套接字 (测试套接字)
返回 (假)
.如果真结束
.子程序 路由跟踪测试
.参数 目标主机, 文本型
加入成员 (诊断结果, "开始路由跟踪: " + 目标主机)
' 这里可以实现简化的路由跟踪逻辑
加入成员 (诊断结果, "路由跟踪功能需要系统权限支持")
总结
易语言网络编程虽然相对简单,但要构建稳定可靠的网络应用仍需注意多个关键点。通过合理的连接管理、数据协议设计、心跳检测和故障诊断,可以显著提升网络应用的稳定性和性能。
核心优化要点:
- 连接管理:使用连接池复用TCP连接,减少建立连接的开销
- 协议设计:自定义数据包格式解决粘包问题,添加校验机制
- 心跳检测:定期检查连接状态,自动重连断开的连接
- 超时处理:设置合理的读写超时,避免线程阻塞
- 错误处理:完善的异常处理和故障诊断机制
【进阶方向】
考虑使用完成端口(IOCP)模型进一步提升高并发场景下的性能表现,或结合SSL/TLS实现安全的加密通信。
© 版权声明
THE END













暂无评论内容