先介绍一下网络通信的过程:通过 HTTP 发出请求,HTTP 的报文被打包在 TCP 的报文段中,然后又被放到 IP 层的数据报中,最后形成链路层的帧,通过网卡发出去。这个过程就是比 OSI 七层模型更符合实际情况的 TCP/IP 五层模型:应用层 <- 传输层 <- 网络层 <- 链路层 <- 物理层。分层的好处是能够隔离变化,在接口不变的情况下,某一层的变化只局限于本层次内。
1 协议设计
1.1 包头完整结构定义
| 索引 | 字段名称 | 长度(字节) | 说明 |
|---|---|---|---|
| 0 | Magic Number | 2(uint16) | 固定为 0x0870 |
| 1 | 协议版本 | 1(uint8) | 标识通信双方使用的协议版本,初始版本为 1 |
| 2 | 数据类型 | 4(uint32) | 消息的类型(见 1.2) |
| 3 | 数据长度 | 4(uint32) | 数据的长度 |
| 4 | CRC32 | 4(uint32) | 数据的校验和,此处只校验数据部分,不包括包头 |
| 5 | Reserved | 4(uint32) | 保留字段 |
| 6 | 数据 | 实际数据(块大小约定为 4096 * 10 字节) | |
| 7 | 结束标志 | 2(uint16) | 固定为 0x7008 |
校验和(Checksum):防止消息被篡改或损坏,并非加密技术,一般是对指定字段进行算法处理后得到,比如 IP/TCP/UDP 生成校验和的规则为:基于反码求和,接收方用相同规则生成后与发送方提供的对比,若不一致,则消息可能已损坏。常见的算法比如 CRC32,可以通过 CRC(循环冗余校验)在线计算 验证。
1.2 包头数据类型定义
| 类型 | 含义 | 说明 |
|---|---|---|
| 0x01 | 心跳消息 | 发送心跳,不需要回应 |
| 0x02 | 身份消息 | 验证身份,如发送 IP 地址 |
| 0x03 | 文件传输消息 | 重点,详情请看下文(见 1.3) |
| 0x04 | 远程调用消息 |
1.3 文件传输消息协议
提供一个可靠、高效、支持断点续传的文件传输协议,即使网络不稳定也能使用。协议通过 ACK/NAK 机制、累积确认(支持乱序数据块接收和重组)、数据完整性校验(块级 CRC32 校验和文件级 SHA256 校验)和流量控制(滑动窗口机制控制发送速率)确保文件传输的可靠性。
1.3.1 协议头
| 索引 | 字段名称 | 长度(字节) | 说明 |
|---|---|---|---|
| 0 | Magic Number | 4(uint32) | 魔数:协议标识符,固定值 0x46545032 (“FTP2”) |
| 1 | SessionId | 4(uint32) | 会话 ID |
| 2 | 版本号 | 1(uint8) | 初始版本号是 0x01 |
| 3 | 消息类型 | 1(uint8) | 区分不同的消息类型(见 1.3.2) |
| 4 | 序列号/块号(seqNum) | 4(uint32) | 在 DATA 消息中表示块序号,在 ACK 中表示最后连续块号 |
| 5 | 文件总大小或块数量 | 4(uint32) | 在 START 消息中表示文件大小,在 NAK/ACK 中表示块数量 |
| 6 | 当前块大小(chunkSize) | 4(uint32) | DATA 消息中有效载荷的大小 |
| 7 | CRC32 校验和 | 4(uint32) | DATA 消息中数据的 CRC32 校验值 |
文件传输所有消息的协议头之后都需要带上路径信息:
| 索引 | 字段名称 | 长度(字节) | 说明 |
|---|---|---|---|
| 8 | dest_path_len | 4(uint32) | 目标路径名的长度 |
| 9 | dest_path | 变长 | 目标路径,表示这个文件是发给谁的 |
1.3.2 消息类型
| 类型 | 名称 | 方向 | 说明 |
|---|---|---|---|
| 0x01 | START | 发送端 => 接收端 | 传输开始,包含文件元数据 |
| 0x02 | START_ACK | 接收端 => 发送端 | 确认开始,准备接收数据 |
| 0x03 | DATA | 发送端 => 接收端 | 文件数据块 |
| 0x04 | ACK | 接收端 => 发送端 | 累积确认,确认多个块 |
| 0x05 | NAK | 接收端 => 发送端 | 否定确认,请求重传 |
| 0x06 | END | 发送端 => 接收端 | 传输结束,包含文件哈希 |
1.3.3 START 消息
| 协议头 | 路径信息 | 文件名 | 文件哈希值 | 用户名 | 文件类型 |
|---|---|---|---|---|---|
| 26 字节 | 变长 | 256 字节 | 32 字节 | 32 字节 | 1 字节 |
1.3.4 START_ACK 消息
| 协议头 | 路径信息 | 状态(0:接受传输,1:拒绝传输) | 拒绝原因(如果状态为 1) |
|---|---|---|---|
| 26 字节 | 变长 | 1 字节 | 64 字节 |
1.3.5 DATA 消息
| 协议头 | 路径信息 | 数据信息(长度由头中的 chunkSize 决定) |
|---|---|---|
| 26 字节 | 变长 | 变长 |
1.3.6 ACK 消息
| 协议头 | 路径信息 | 需额外确认的块列表(累积确认) |
|---|---|---|
| 26 字节 | 变长 | 变长 |
协议头中的 seqNum:已确认的最后一个连续的块号。
协议头中的 totalSize:需额外确认的块数量。
需额外确认的块列表:非连续但已确认的块序号数组。
1.3.7 NAK 消息
| 协议头 | 路径信息 | 缺失块列表 |
|---|---|---|
| 26 字节 | 变长 | 变长 |
协议头中的 totalSize:缺失块数量。
缺失块列表:需要重传的块序号数组。
1.3.8 END 消息
| 协议头 | 路径信息 | 文件哈希值 |
|---|---|---|
| 26 字节 | 变长 | 32 字节 |
2 代码实现
2.1 核心打包工具类
1 | package com.guoguocai.protocol.util; |
2.2 常量类
1 | package com.guoguocai.protocol.constant; |
2.3 包头结构
1 | package com.guoguocai.protocol.dto; |
2.4 START_ACK 结构
1 | package com.guoguocai.protocol.dto; |
2.5 ACK/NAK 结构
1 | package com.guoguocai.protocol.dto; |