Skip to content

分包协议与心跳机制

📦💓 解决 TCP 粘包/分包问题的内置协议,以及保持连接活跃的智能心跳

📖 目录


概述

TCP 是流式协议,没有消息边界——发送方连续发送的数据,接收方可能分多次收到(分包),也可能一次收到多条消息拼接的数据(粘包)。cWinsock 内置封包协议 cPacketProtocol 和智能心跳 cHeartbeat,自动解决这些问题:

  • 封包协议:三种内置协议,自动封包/解包,保证每次收到的都是完整消息
  • 心跳机制:内嵌定时器自动驱动,服务端超时检测 + 客户端智能保活

TCP 粘包与分包问题

什么是粘包/分包?

现象术语说明
一次 Send 的数据,分多次 Receive 到达分包发了 1000 字节,先收到 300,再收到 700
多次 Send 的数据,一次 Receive 全到粘包连续发了 3 条消息,一次收到拼接在一起的数据
上面两种混合出现最常见收到的数据既不完整又混着下一条的开头

现象图解

应用层发送:[Msg1][Msg2][Msg3]
                    ↓ TCP 流式传输(无边界)
接收端可能收到:
  情况1(分包):  [Msg1前半]  [Msg1后半+Msg2前半]  [Msg2后半+Msg3]
  情况2(粘包):  [Msg1+Msg2]  [Msg3]
  情况3(混合):  [Msg1前半]  [Msg1后半+Msg2]  [Msg3前半]  [Msg3后半]
  理想情况(少见):[Msg1]  [Msg2]  [Msg3]

cPacketProtocol 的解决思路

在数据中定义明确的边界,将无界的字节流还原为有界的消息:

原始 TCP 字节流(无边界):
  [Msg1前半][Msg1后半+Msg2前半][Msg2后半]
                        ↓ cPacketProtocol.Decode()
完整消息:
  [Msg1 完整] → 触发 MessageArrival
  [Msg2 完整] → 触发 MessageArrival

封包协议

三种协议对比

协议类型原理分包处理粘包处理优缺点
ppLengthHeader头部指明消息体长度长度不够则缓存,等数据到齐再提取长度够了就切一条,剩余继续解析推荐。不依赖数据内容,不限消息长度
ppDelimiter分隔符标记消息结尾未找到分隔符则缓存找到分隔符就切一条,剩余继续找简单,但分隔符不能出现在消息体中
ppFixedLength每条消息固定长度不足定长则缓存凑够定长就切一条仅适用于定长消息场景

推荐方案:ppLengthHeader

4 字节小端长度头协议是最通用的选择:

  • 不依赖数据内容中出现特殊字符(分隔符协议的硬伤)
  • 不限制单条消息长度(定长协议的硬伤)
  • 每条消息自带长度,接收端精确知道要读多少字节

ppDelimiter - 分隔符协议

使用指定的字符/字符串作为消息边界标记。

适用场景:文本协议、行式命令协议(如聊天、HTTP 头部)

vb
' 配置
m_oServer.PacketProtocol = ppDelimiter
m_oServer.Delimiter = vbCrLf  ' 换行分隔

' 发送自动追加分隔符
Client.SendData "Hello"  ' 实际发送: "Hello" + vbCrLf

' 接收自动去除分隔符
Private Sub m_oServer_MessageArrival(Client As cWinsock, ByVal bytesTotal As Long)
    Debug.Print "完整消息: " & Client.GetDataText()  ' "Hello"
End Sub

常用分隔符

分隔符常量适用场景
\r\nvbCrLf行式文本协议
\nvbLfUnix 风格行协议
\0vbNullCharC 字符串风格
自定义字符串"<EOF>"自定义协议

注意:分隔符不能出现在消息体中,否则消息会被错误拆分。


ppFixedLength - 定长协议

每条消息固定长度,适用于已知长度的结构化数据。

适用场景:状态包、传感器数据、固定格式报文

vb
' 配置
m_oServer.PacketProtocol = ppFixedLength
m_oServer.FixedLength = 256  ' 每条消息固定 256 字节

' 发送:不足 256 字节会补零,超过 256 字节会报错
Client.SendData myData

注意:数据超长时会直接报错(不会静默截断)。


ppLengthHeader - 长度头协议(推荐)

在消息头部添加长度信息,最通用、最推荐的协议。

适用场景:二进制协议、变长消息、任何需要可靠传输的场景

vb
' 配置
m_oServer.PacketProtocol = ppLengthHeader
m_oServer.HeaderBytes = 4                ' 4字节长度头(支持最大约4GB)
m_oServer.HeaderEndian = eeLittleEndian  ' 小端序

' 发送自动加长度头
Client.SendData "Hello"  ' 实际发送: [4字节长度=5] + "Hello"

' 接收自动剥离长度头
Private Sub m_oServer_MessageArrival(Client As cWinsock, ByVal bytesTotal As Long)
    Dim baData() As Byte
    baData = Client.GetDataByteArray()  ' 100% 是一条完整消息
End Sub

HeaderBytes 选项

类型最大消息长度说明
2Unsigned Integer65,535 字节(~64KB)小消息场景,节省带宽
4Unsigned Long2,147,483,647 字节(~2GB)通用场景,推荐

HeaderEndian 字节序

说明适用场景
eeLittleEndian小端序(默认)x86/x64 平台内部通信
eeBigEndian大端序(网络字节序)与 Java/C 服务端通信

安全限制属性

防止恶意数据包耗尽内存,2026-06-09 新增:

MaxPacketSize

单包最大大小限制,防止恶意超大包声明耗尽内存。

vb
Property Get MaxPacketSize() As Long
Property Let MaxPacketSize(ByVal Value As Long)
  • 默认值:1MB(1048576 字节)
  • 作用:长度头协议解析时,如果声明的消息长度超过此值,直接报错丢弃
  • 适用协议ppLengthHeader
vb
' 调整最大包限制
m_oServer.MaxPacketSize = 524288  ' 512KB

' 新客户端自动继承此配置

MaxBufferSize

缓冲区累积上限,防止大量不完整包慢慢吃内存。

vb
Property Get MaxBufferSize() As Long
Property Let MaxBufferSize(ByVal Value As Long)
  • 默认值:4MB(4194304 字节)
  • 作用:Decode 合并缓冲区前检查,超限报错
  • 适用协议:所有协议
vb
' 调整缓冲区上限
m_oServer.MaxBufferSize = 8388608  ' 8MB

超限行为

超限类型行为
单包超过 MaxPacketSize抛出明确错误信息,丢弃缓冲区
累积超过 MaxBufferSize抛出明确错误信息,丢弃缓冲区
数据超长(FixedLength)抛出错误(不静默截断)

事件模型

协议模式与无协议模式的区别

模式触发事件说明
无协议(ppNoneDataArrival原始字节流,可能不完整或粘连
有协议MessageArrival每次一定是完整的一条消息

关键规则:协议模式下只触发 MessageArrival,不触发 DataArrival,避免同一数据被两个事件重复读取。

MessageArrival 事件

vb
Private Sub object_MessageArrival(Client As cWinsock, ByVal bytesTotal As Long)
参数类型说明
ClientcWinsock接收消息的客户端对象
bytesTotalLong完整消息的字节数

使用示例

vb
' 设置长度头协议
m_oServer.PacketProtocol = ppLengthHeader
m_oServer.HeaderBytes = 4
m_oServer.HeaderEndian = eeLittleEndian

' 接收完整消息
Private Sub m_oServer_MessageArrival(Client As cWinsock, ByVal bytesTotal As Long)
    ' 此时缓冲区中已是完整消息,100% 完整
    Dim sData As String
    sData = Client.GetDataText()
    Debug.Print "完整消息: " & sData
End Sub

与 DataArrival 的区别

事件触发时机数据完整性适用场景
DataArrival每次收到原始数据可能是分片或粘包数据无协议模式
MessageArrival协议解析出完整消息后保证是一条完整消息协议模式

智能心跳机制

cWinsock 内置 cHeartbeat 心跳管理器,内嵌 cTimer 自动驱动,无需外部定时器。

服务端:超时检测

服务端定期检查所有客户端的空闲时间,超时则自动断开僵尸连接。

vb
' 配置
m_oServer.AutoHeartbeat = True
m_oServer.HeartbeatTimeout = 120  ' 2分钟无活动则超时

' 超时事件
Private Sub m_oServer_ClientTimeout(Client As cWinsock)
    Debug.Print "客户端 " & Client.Tag & " 超时,已自动断开"
End Sub

客户端:心跳保活

客户端定期发送心跳包,保持连接活跃。具有智能跳过机制——有数据收发时跳过心跳,节省带宽。

vb
' 配置
m_oClient.AutoHeartbeat = True
m_oClient.HeartbeatInterval = 50  ' 50秒无活动则发心跳

' 心跳事件
Private Sub m_oClient_HeartbeatSent(Client As cWinsock)
    Debug.Print "心跳已发送,空闲: " & Client.IdleSeconds & "秒"
End Sub

自定义心跳包

vb
' 默认心跳包为单字节 &H00,可自定义
Dim baHB(0 To 3) As Byte
baHB(0) = &H50  ' P
baHB(1) = &H49  ' I
baHB(2) = &H4E  ' N
baHB(3) = &H47  ' G
m_oClient.HeartbeatData = baHB

心跳与协议的一致性

心跳数据经过协议编码发送,不会污染协议状态机。即:

  • 心跳包通过 SendData 发送,会经过协议 Encode
  • 接收端心跳数据经过协议 Decode
  • 心跳不会导致粘包/分包状态混乱

工作原理

  1. 心跳管理器内嵌 cTimer,每 10 秒触发一次 Tick
  2. 服务端:检查所有客户端的 IdleSeconds,超时则触发 ClientTimeout 并自动断开
  3. 客户端:如果空闲超过 HeartbeatInterval 则发送心跳包,有数据收发时智能跳过
  4. 每次收发数据自动重置 LastActivityTime

心跳相关属性

属性类型读写说明
AutoHeartbeatBoolean读写启用/禁用自动心跳
HeartbeatTimeoutLong读写服务端超时秒数(默认120)
HeartbeatIntervalLong读写客户端心跳间隔秒数(默认50)
HeartbeatDataByte()读写心跳包内容(默认单字节0)
IdleSecondsLong只读当前空闲秒数

UDP 协议支持

UDP 客户端同样支持分包协议。UDP 虽然是报文协议(天然有边界),但协议模式仍然可以用于:

  • 自定义消息格式处理
  • 与 TCP 端共享协议逻辑
  • 利用安全限制属性
vb
' UDP 服务端设置协议
m_oUdp.PacketProtocol = ppLengthHeader
m_oUdp.HeaderBytes = 4
m_oUdp.MaxPacketSize = 65536  ' UDP 单包通常不超过 64KB

' UDP 虚拟客户端自动继承协议配置
Private Sub m_oUdp_MessageArrival(Client As cWinsock, ByVal bytesTotal As Long)
    Dim sData As String
    sData = Client.GetDataText()
    Debug.Print "UDP 完整消息: " & sData
End Sub

完整示例

TCP 服务端 + 长度头协议 + 心跳

vb
Private WithEvents m_oServer As cWinsock

Private Sub Form_Load()
    Set m_oServer = New cWinsock
    
    ' 设置封包协议(推荐:长度头协议)
    m_oServer.PacketProtocol = ppLengthHeader
    m_oServer.HeaderBytes = 4
    m_oServer.HeaderEndian = eeLittleEndian
    m_oServer.MaxPacketSize = 1048576   ' 1MB
    m_oServer.MaxBufferSize = 4194304   ' 4MB
    
    ' 设置心跳
    m_oServer.AutoHeartbeat = True
    m_oServer.HeartbeatTimeout = 120    ' 2分钟超时
    
    ' 启动服务器
    m_oServer.Listen 8080
    Debug.Print "服务器已启动"
End Sub

Private Sub m_oServer_ConnectionRequest(Client As cWinsock, ByRef DisConnect As Boolean)
    Debug.Print "新客户端: " & Client.RemoteHostIP & ":" & Client.RemotePort
    ' 新客户端自动继承协议和心跳配置
End Sub

Private Sub m_oServer_MessageArrival(Client As cWinsock, ByVal bytesTotal As Long)
    ' 100% 是完整消息
    Dim sData As String
    sData = Client.GetDataText()
    Debug.Print "[" & Client.Tag & "] " & sData
    
    ' 回显
    Client.SendData "Echo: " & sData
End Sub

Private Sub m_oServer_ClientTimeout(Client As cWinsock)
    Debug.Print "客户端超时: " & Client.Tag
End Sub

Private Sub m_oServer_CloseEvent(Client As cWinsock)
    Debug.Print "客户端断开: " & Client.Tag
End Sub

Private Sub Form_Unload(Cancel As Integer)
    On Error Resume Next
    m_oServer.Close_
End Sub

TCP 客户端 + 长度头协议 + 心跳

vb
Private WithEvents m_oClient As cWinsock

Private Sub Form_Load()
    Set m_oClient = New cWinsock
    
    ' 设置封包协议(必须与服务端一致)
    m_oClient.PacketProtocol = ppLengthHeader
    m_oClient.HeaderBytes = 4
    m_oClient.HeaderEndian = eeLittleEndian
    
    ' 设置心跳
    m_oClient.AutoHeartbeat = True
    m_oClient.HeartbeatInterval = 50    ' 50秒间隔
    
    ' 连接
    m_oClient.Connect "127.0.0.1", 8080
End Sub

Private Sub m_oClient_Connect(Client As cWinsock)
    Debug.Print "已连接"
    Client.SendData "Hello, Server!"
End Sub

Private Sub m_oClient_MessageArrival(Client As cWinsock, ByVal bytesTotal As Long)
    Dim sData As String
    sData = Client.GetDataText()
    Debug.Print "收到: " & sData
End Sub

Private Sub m_oClient_HeartbeatSent(Client As cWinsock)
    Debug.Print "心跳已发送"
End Sub

Private Sub Form_Unload(Cancel As Integer)
    On Error Resume Next
    m_oClient.Close_
End Sub

聊天服务端 + 分隔符协议

vb
Private WithEvents m_oServer As cWinsock

Private Sub Form_Load()
    Set m_oServer = New cWinsock
    
    ' 使用换行符作为消息分隔符
    m_oServer.PacketProtocol = ppDelimiter
    m_oServer.Delimiter = vbCrLf
    
    ' 心跳保活
    m_oServer.AutoHeartbeat = True
    m_oServer.HeartbeatTimeout = 180
    
    m_oServer.Listen 9090
End Sub

Private Sub m_oServer_MessageArrival(Client As cWinsock, ByVal bytesTotal As Long)
    Dim sMsg As String
    sMsg = Client.GetDataText()
    
    ' 广播给所有客户端
    Dim oClient As cWinsock
    For Each oClient In m_oServer.Clients
        If Not oClient Is Client Then
            oClient.SendData "[" & Client.Tag & "] " & sMsg
        End If
    Next
End Sub

最佳实践

1. 选择合适的协议类型

协议类型适用场景优缺点
ppLengthHeader二进制协议、变长消息最通用,推荐
ppDelimiter文本协议(聊天、命令行式)简单直观,但数据中不能包含分隔符
ppFixedLength固定格式消息(状态包、传感器数据)解析最快,但不灵活

2. 协议模式下使用 MessageArrival

vb
' ✅ 正确:协议模式下使用 MessageArrival
Private Sub m_oServer_MessageArrival(Client As cWinsock, ByVal bytesTotal As Long)
    Dim sData As String
    sData = Client.GetDataText()  ' 保证是完整消息
End Sub

' ❌ 错误:协议模式下使用 DataArrival
' 协议模式下 DataArrival 不会触发

3. 长度头协议的字节序

vb
' 与 C/Java 服务端通信时用大端序
m_oServer.HeaderEndian = eeBigEndian

' 纯 VB6 内部通信可用小端序(默认)
m_oServer.HeaderEndian = eeLittleEndian

4. 配置安全限制

vb
' 根据业务需求调整安全限制
m_oServer.MaxPacketSize = 524288   ' 512KB,单包最大
m_oServer.MaxBufferSize = 8388608  ' 8MB,缓冲区最大

5. 服务器配置协议在 Listen 之前

vb
' ✅ 正确:Listen 之前设置协议
m_oServer.PacketProtocol = ppLengthHeader
m_oServer.HeaderBytes = 4
m_oServer.Listen 8080

' 新客户端自动继承服务器配置,创建独立协议实例

6. 心跳与协议配合

心跳包走协议编码,不会污染协议状态机,无需手动过滤心跳。


常见问题

❓ 为什么要用封包协议?

TCP 是流式协议,没有消息边界。不使用协议时,DataArrival 可能收到不完整或粘连的数据,需要手动拼接/拆分,容易出错。使用封包协议后,MessageArrival 每次触发的都是一条完整消息,开发者无需关心底层字节流的分合。

❓ MaxPacketSize 和 MaxBufferSize 有什么区别?

  • MaxPacketSize:单条消息的最大长度,针对长度头协议中声明的消息体长度
  • MaxBufferSize:接收缓冲区的累积上限,防止大量不完整包慢慢吃内存

❓ 协议模式下还会触发 DataArrival 吗?

不会。协议模式下只触发 MessageArrival,避免同一数据被两个事件重复读取。无协议模式下仍触发 DataArrival

❓ 心跳包会影响协议解析吗?

不会。心跳数据经过协议编码发送和接收,不会污染协议状态机。

❓ UDP 需要用封包协议吗?

UDP 天然有消息边界,不需要封包协议来解决粘包问题。但 UDP 客户端同样支持协议模式,可用于统一消息格式、利用安全限制属性等场景。

❓ 2 字节长度头能传多大消息?

2 字节头可表示 0~65535(最大约 64KB)。超过 65535 字节会报错。建议大数据使用 4 字节头。


相关文档

文档描述
属性参考协议和心跳相关的属性详细说明
事件详解MessageArrival、ClientTimeout 等事件
方法参考GetDataText、GetDataByteArray 等方法
TCP编程TCP 客户端和服务器编程指南
最佳实践常见场景的解决方案和性能优化建议

最后更新: 2026-06-09

VB6及其LOGO版权为微软公司所有