当前位置: 首页 > news >正文

YMODEM 协议介绍以及通信流程分析和Lua语言实现

目录

概述

1 YMODEM 协议介绍

1.1 YMODEM vs XMODEM

1.2 协议实现

1.2.1  数据包格式

1.2.2 通信流程

2 YMODEM的核心特征

2.1 关键特性介绍

2.2 通信示例

3 YMODEM协议的Lua实现


概述

本文主要介绍YMODEM ,YMODEM协议是对XMODEM协议的扩展,主要用于文件传输。它由Chuck Forsberg在XMODEM的基础上改进而来,支持批量文件传输、文件名、文件大小、时间戳等元数据的传输,并使用CRC-16进行错误校验。YMODEM协议通常使用1024字节(1K)的数据块,但也可以回退到128字节(类似于XMODEM)的数据块。

YMODEM 协议介绍

YMODEM 是由 Chuck Forsberg 在 XMODEM 基础上开发的增强型文件传输协议,它结合了 XMODEM-1K 和 CRC 校验的优点,并增加了批量文件传输、文件信息传输等现代功能。

1.1 YMODEM vs XMODEM

特性XMODEMYMODEM
数据块大小128字节128字节或1024字节
校验方式校验和或CRC强制CRC-16
文件信息不支持支持文件名、大小、时间戳
批量传输不支持支持多文件连续传输
传输模式单一模式标准模式和流模式
传输效率较低较高

1.2 协议实现

1.2.1  数据包格式

YMODEM 使用两种主要的数据包类型:

1) .文件头包(启动传输)

[SOH][0][0xFF][文件名][0][文件大小][0][修改时间][0][...][0][填充][CRC16]
  • 包编号为 0:标识这是文件头包

  • 包含文件信息

    • 文件名(以NULL结束)

    • 文件大小(十进制ASCII字符串)

    • 文件修改时间(Unix时间戳,可选)

    • 其他文件属性

2) 数据包

[STX/SOH][包编号][~包编号][数据][CRC16]
  • STX (0x02):1024字节数据包

  • SOH (0x01):128字节数据包

  • 包编号从 1 开始递增

1.2.2 通信流程

1) 单文件传输流程:

发送方                            接收方|                                || <------------- 'C' ----------- | (接收方请求CRC模式)|                                || -------- 文件头包(包0) ------> ||                                || <------------ ACK ------------ | (确认文件头)|                                || -------- 数据包(包1) --------> ||                                || <------------ ACK ------------ | |                                || -------- 数据包(包2) --------> ||                                || <------------ ACK ------------ ||              ...               ||                                || ------------ EOT ------------> | (文件结束)|                                || <------------ ACK ------------ |

2) 多文件传输流程:

发送方                            接收方|                                || <------------- 'C' ----------- ||                                || ----- 文件1头包(包0) --------> ||                                || <------------ ACK ------------ ||                                || ----- 文件1数据包... --------> ||                                || <------------ ACK ------------ ||                                || ------------ EOT ------------> | (文件1结束)|                                || <------------ ACK ------------ ||                                || ----- 文件2头包(包0) --------> | (开始下一个文件)|                                || <------------ ACK ------------ ||              ...               ||                                || ----- 空文件头包(包0) ------> | (所有文件传输结束)|                                || <------------ ACK ------------ |

YMODEM的核心特征

2.1 关键特性介绍

1) 文件信息传输

YMODEM 在文件头包中传输丰富的文件信息:

// 文件头包示例数据结构
struct ymodem_header {uint8_t  type;          // SOHuint8_t  block_num;     // 0uint8_t  block_num_comp;// 0xFFchar     filename[128]; // "example.txt\0"char     filesize[16];  // "1024\0"char     timestamp[16]; // "1634567890\0" (Unix时间戳)// ... 填充到128或1024字节uint16_t crc16;
};

2) 批量文件传输

YMODEM 支持连续传输多个文件,无需重新建立连接:

文件1 → ACK → 文件2 → ACK → ... → 空文件头包 → 传输结束

3) 动态块大小

YMODEM 可以根据需要选择数据块大小:

  • 1024字节:大文件,提高效率

  • 128字节:小文件或不可靠信道,减少重传开销

3) 增强的错误处理

  • 强制使用CRC-16,提供更强的错误检测能力

  • 完善的超时和重传机制

  • 支持传输取消(CAN字符)

2.2 通信示例

接收方: 'C'          // 请求CRC模式
发送方: SOH 00 FF "test.txt" NUL "1024" NUL NUL ... [CRC16]  // 文件头包
接收方: ACK          // 确认文件头
发送方: STX 01 FE [1024字节数据] [CRC16]  // 第一个数据块
接收方: ACK          // 确认
发送方: STX 02 FD [1024字节数据] [CRC16]  // 第二个数据块
接收方: ACK          // 确认
发送方: SOH 03 FC [128字节数据] [CRC16]   // 最后一个块(128字节)
接收方: ACK          // 确认
发送方: EOT          // 文件结束
接收方: ACK          // 确认结束// 如果是最后一个文件:
发送方: SOH 00 FF NUL NUL ... [CRC16]     // 空文件头包
接收方: ACK          // 确认传输完全结束

3 YMODEM协议的Lua实现

这里提供一个完整的 YMODEM 协议的 Lua 实现。由于 Lua 本身没有内置的串口和文件操作,这个实现使用了通用的接口,可以轻松适配到各种 Lua 环境(如嵌入式 Lua、Lua with serial libraries 等)。

完整的 YMODEM Lua 实现代码如下:

-- ymodem.lua
-- YMODEM 协议 Lua 实现local bit = bit or bit32 or require("bit")  -- 兼容不同的 bit 操作库-- YMODEM 协议常量
local YMODEM = {SOH = 0x01,     -- 128字节数据包开始STX = 0x02,     -- 1024字节数据包开始EOT = 0x04,     -- 传输结束ACK = 0x06,     -- 确认NAK = 0x15,     -- 否认,请求重传CAN = 0x18,     -- 取消传输C   = 0x43,     -- 'C' - CRC模式请求-- 数据包大小DATA_SIZE_128 = 128,DATA_SIZE_1K = 1024,-- 超时和重试配置TIMEOUT = 3000,     -- 超时时间(ms)MAX_RETRIES = 10,   -- 最大重试次数-- 错误代码ERROR = {SUCCESS = 0,TIMEOUT = 1,CANCEL = 2,IO_ERROR = 3,INVALID_PACKET = 4,TOO_MANY_RETRIES = 5,EOF = 6,FILE_ERROR = 7}
}-- CRC16 计算函数 (YMODEM 使用)
function YMODEM.crc16(data)local crc = 0for i = 1, #data docrc = bit.bxor(crc, bit.lshift(data:byte(i), 8))for _ = 1, 8 doif bit.band(crc, 0x8000) ~= 0 thencrc = bit.bxor(bit.lshift(crc, 1), 0x1021)elsecrc = bit.lshift(crc, 1)endcrc = bit.band(crc, 0xFFFF)endendreturn crc
end-- YMODEM 协议类
local YModem = {}
YModem.__index = YModem-- 构造函数
function YModem.new(config)local self = setmetatable({}, YModem)-- 默认配置self.config = {use_1k = true,          -- 使用1K数据块timeout = YMODEM.TIMEOUT,max_retries = YMODEM.MAX_RETRIES}-- 合并用户配置if config thenfor k, v in pairs(config) doself.config[k] = vendend-- 必须的回调函数self.serial_read = config.serial_readself.serial_write = config.serial_writeself.file_open = config.file_openself.file_close = config.file_closeself.file_read = config.file_readself.file_write = config.file_write-- 可选的回调函数self.get_char = config.get_char or self._default_get_charself.file_seek = config.file_seekself.progress_callback = config.progress_callbackself.get_time = config.get_time or os.time-- 协议状态self.packet_number = 0self.retry_count = 0self.current_file = nilself.current_fd = nilreturn self
end-- 默认的获取字符实现(基于 serial_read)
function YModem:_default_get_char()local buffer = self.serial_read(1)if buffer and #buffer > 0 thenreturn buffer:byte(1)endreturn -1
end-- 带超时的获取字符
function YModem:_get_char_timeout(timeout_ms)local start_time = self.get_time()local current_time = start_timewhile (current_time - start_time) * 1000 < timeout_ms dolocal char = self.get_char()if char >= 0 thenreturn charend-- 这里可以添加小的延时current_time = self.get_time()endreturn nil  -- 超时
end-- 发送数据包
function YModem:_send_data_packet(data, packet_num)local header, data_size-- 选择数据包头和大小if #data == YMODEM.DATA_SIZE_1K and self.config.use_1k thenheader = YMODEM.STXdata_size = YMODEM.DATA_SIZE_1Kelseheader = YMODEM.SOHdata_size = YMODEM.DATA_SIZE_128if #data > data_size thendata = data:sub(1, data_size)endend-- 构建数据包local packet = string.char(header,packet_num,0xFF - packet_num  -- 包编号补码)-- 添加数据(需要填充到标准大小)if #data < data_size then-- 用 0x1A 填充剩余空间data = data .. string.rep(string.char(0x1A), data_size - #data)endpacket = packet .. data-- 计算并添加CRC16local crc = YMODEM.crc16(data)packet = packet .. string.char(bit.rshift(bit.band(crc, 0xFF00), 8),  -- CRC高字节bit.band(crc, 0xFF)                    -- CRC低字节)-- 发送数据包return self.serial_write(packet)
end-- 发送文件头包
function YModem:_send_file_header_packet(file_info)-- 构建文件信息字符串local file_info_str = ""if file_info and file_info.filename and file_info.filename ~= "" thenfile_info_str = string.format("%s\0%d\0%d\0%d",file_info.filename,file_info.file_size or 0,file_info.modify_time or 0,file_info.mode or 0)end-- 如果文件名为空,表示传输结束-- 构建数据包local packet = string.char(YMODEM.SOH,  -- 头包总是使用SOH0x00,        -- 包编号00xFF         -- 包编号补码)-- 添加文件信息(填充到128字节)if #file_info_str < YMODEM.DATA_SIZE_128 thenfile_info_str = file_info_str .. string.rep("\0", YMODEM.DATA_SIZE_128 - #file_info_str)elsefile_info_str = file_info_str:sub(1, YMODEM.DATA_SIZE_128)endpacket = packet .. file_info_str-- 计算并添加CRC16local crc = YMODEM.crc16(file_info_str)packet = packet .. string.char(bit.rshift(bit.band(crc, 0xFF00), 8),bit.band(crc, 0xFF))-- 发送数据包return self.serial_write(packet)
end-- 接收数据包
function YModem:_receive_packet()-- 读取包头local header = self:_get_char_timeout(self.config.timeout)if not header thenreturn nil, YMODEM.ERROR.TIMEOUTendlocal expected_data_size, is_1k_packet-- 检查包头类型if header == YMODEM.SOH thenexpected_data_size = YMODEM.DATA_SIZE_128is_1k_packet = falseelseif header == YMODEM.STX and self.config.use_1k thenexpected_data_size = YMODEM.DATA_SIZE_1Kis_1k_packet = trueelseif header == YMODEM.EOT thenreturn {type = "EOT"}, YMODEM.ERROR.SUCCESSelseif header == YMODEM.CAN thenreturn {type = "CAN"}, YMODEM.ERROR.CANCELelsereturn nil, YMODEM.ERROR.INVALID_PACKETend-- 读取包编号和补码local packet_num = self:_get_char_timeout(self.config.timeout)local packet_num_comp = self:_get_char_timeout(self.config.timeout)if not packet_num or not packet_num_comp thenreturn nil, YMODEM.ERROR.TIMEOUTend-- 验证包编号if bit.band(packet_num + packet_num_comp, 0xFF) ~= 0xFF thenreturn nil, YMODEM.ERROR.INVALID_PACKETend-- 读取数据local data_buffer = ""for _ = 1, expected_data_size dolocal char = self:_get_char_timeout(self.config.timeout)if not char thenreturn nil, YMODEM.ERROR.TIMEOUTenddata_buffer = data_buffer .. string.char(char)end-- 读取CRClocal crc_high = self:_get_char_timeout(self.config.timeout)local crc_low = self:_get_char_timeout(self.config.timeout)if not crc_high or not crc_low thenreturn nil, YMODEM.ERROR.TIMEOUTendlocal received_crc = bit.bor(bit.lshift(crc_high, 8), crc_low)local calculated_crc = YMODEM.crc16(data_buffer)-- 验证CRCif received_crc ~= calculated_crc thenreturn nil, YMODEM.ERROR.INVALID_PACKETend-- 去除填充字符,找到实际数据长度local actual_data = data_bufferlocal last_valid = 0for i = 1, #actual_data dolocal byte = actual_data:byte(i)if byte ~= 0x1A and byte ~= 0x00 thenlast_valid = iendendif last_valid > 0 thenactual_data = actual_data:sub(1, last_valid)elseactual_data = ""endreturn {type = "DATA",packet_num = packet_num,data = actual_data,data_size = #actual_data,is_1k = is_1k_packet}, YMODEM.ERROR.SUCCESS
end-- 解析文件头包
function YMODEM.parse_file_header(data)local file_info = {filename = "",file_size = 0,modify_time = 0,mode = 0}-- 检查是否是空文件头 (传输结束)if #data == 0 or data:byte(1) == 0 thenreturn file_info, true  -- 空文件头end-- 解析文件名local null_pos = data:find("\0")if not null_pos thenreturn file_info, falseendfile_info.filename = data:sub(1, null_pos - 1)data = data:sub(null_pos + 1)-- 解析文件大小null_pos = data:find("\0")if not null_pos thenreturn file_info, falseendlocal size_str = data:sub(1, null_pos - 1)file_info.file_size = tonumber(size_str) or 0data = data:sub(null_pos + 1)-- 解析修改时间null_pos = data:find("\0")if not null_pos thenreturn file_info, true  -- 时间是可选的endlocal time_str = data:sub(1, null_pos - 1)file_info.modify_time = tonumber(time_str) or 0data = data:sub(null_pos + 1)-- 解析文件模式null_pos = data:find("\0")if null_pos thenlocal mode_str = data:sub(1, null_pos - 1)file_info.mode = tonumber(mode_str) or 0endreturn file_info, false
end-- YMODEM 接收文件
function YModem:receive()if not self.serial_read or not self.serial_write ornot self.file_open or not self.file_write or not self.file_close thenreturn 0, YMODEM.ERROR.IO_ERRORend-- 初始化状态self.packet_number = 0self.retry_count = 0self.current_file = nilself.current_fd = nil-- 发送 'C' 启动CRC模式的传输if not self.serial_write(string.char(YMODEM.C)) thenreturn 0, YMODEM.ERROR.IO_ERRORendlocal total_received = 0local file_opened = falsewhile self.retry_count < self.config.max_retries dolocal packet, err = self:_receive_packet()if err ~= YMODEM.ERROR.SUCCESS then-- 错误处理if err == YMODEM.ERROR.TIMEOUT or err == YMODEM.ERROR.INVALID_PACKET thenself.retry_count = self.retry_count + 1self.serial_write(string.char(YMODEM.NAK))elsereturn total_received, errendelse-- 成功接收包if packet.type == "EOT" then-- 文件传输结束self.serial_write(string.char(YMODEM.ACK))if file_opened thenself.file_close(self.current_fd)file_opened = falseendelseif packet.type == "CAN" then-- 取消传输if file_opened thenself.file_close(self.current_fd)endreturn total_received, YMODEM.ERROR.CANCELelseif packet.packet_num == 0 then-- 文件头包local file_info, is_empty = YMODEM.parse_file_header(packet.data)if is_empty then-- 空文件头,所有传输结束self.serial_write(string.char(YMODEM.ACK))breakelse-- 新文件开始self.current_file = file_infoself.current_fd = self.file_open(file_info.filename, "wb")if not self.current_fd thenreturn total_received, YMODEM.ERROR.FILE_ERRORendfile_opened = truetotal_received = 0-- 发送ACK确认文件头self.serial_write(string.char(YMODEM.ACK))self.retry_count = 0-- 调用进度回调if self.progress_callback thenself.progress_callback(0, file_info.file_size, "start")endendelse-- 数据包if not file_opened thenreturn total_received, YMODEM.ERROR.FILE_ERRORend-- 写入文件数据if not self.file_write(self.current_fd, packet.data) thenself.file_close(self.current_fd)return total_received, YMODEM.ERROR.FILE_ERRORendtotal_received = total_received + packet.data_size-- 调用进度回调if self.progress_callback and self.current_file.file_size > 0 thenself.progress_callback(total_received, self.current_file.file_size, "progress")end-- 发送ACK确认self.serial_write(string.char(YMODEM.ACK))self.retry_count = 0-- 更新期望的下一个包编号self.packet_number = packet.packet_numendendendif self.retry_count >= self.config.max_retries thenif file_opened thenself.file_close(self.current_fd)endreturn total_received, YMODEM.ERROR.TOO_MANY_RETRIESendif file_opened thenself.file_close(self.current_fd)-- 最终进度回调if self.progress_callback thenself.progress_callback(total_received, self.current_file.file_size, "complete")endendreturn total_received, YMODEM.ERROR.SUCCESS
end-- YMODEM 发送文件
function YModem:transmit(filename)if not self.serial_read or not self.serial_write ornot self.file_open or not self.file_read or not self.file_close thenreturn 0, YMODEM.ERROR.IO_ERRORend-- 初始化状态self.packet_number = 1self.retry_count = 0-- 打开文件local fd, file_size = self.file_open(filename, "rb")if not fd thenreturn 0, YMODEM.ERROR.FILE_ERRORend-- 获取文件信息(这里需要平台特定的实现)local file_info = {filename = filename,file_size = file_size or 0,modify_time = os.time(),mode = 420  -- 0644 八进制}-- 等待接收方的启动信号 'C'while self.retry_count < self.config.max_retries dolocal response = self:_get_char_timeout(self.config.timeout)if response == YMODEM.C thenbreakelseif response == YMODEM.CAN thenself.file_close(fd)return 0, YMODEM.ERROR.CANCELelseself.retry_count = self.retry_count + 1endendif self.retry_count >= self.config.max_retries thenself.file_close(fd)return 0, YMODEM.ERROR.TOO_MANY_RETRIESend-- 发送文件头包if not self:_send_file_header_packet(file_info) thenself.file_close(fd)return 0, YMODEM.ERROR.IO_ERRORend-- 等待文件头确认local response = self:_get_char_timeout(self.config.timeout)if not response or response ~= YMODEM.ACK thenself.file_close(fd)return 0, YMODEM.ERROR.TIMEOUTend-- 发送文件数据local total_sent = 0self.retry_count = 0while total_sent < file_info.file_size and self.retry_count < self.config.max_retries do-- 读取文件数据local to_read = self.config.use_1k and YMODEM.DATA_SIZE_1K or YMODEM.DATA_SIZE_128if (total_sent + to_read) > file_info.file_size thento_read = file_info.file_size - total_sentendlocal data = self.file_read(fd, to_read)if not data or #data == 0 thenbreakend-- 发送数据包if not self:_send_data_packet(data, self.packet_number) thenself.file_close(fd)return total_sent, YMODEM.ERROR.IO_ERRORend-- 等待响应response = self:_get_char_timeout(self.config.timeout)if response == YMODEM.ACK then-- 成功,移动到下一个数据包total_sent = total_sent + #dataself.packet_number = self.packet_number + 1self.retry_count = 0-- 调用进度回调if self.progress_callback thenself.progress_callback(total_sent, file_info.file_size, "progress")endelseif response == YMODEM.NAK then-- 重传当前数据包self.retry_count = self.retry_count + 1-- 回退文件指针if self.file_seek thenself.file_seek(fd, total_sent)endelseif response == YMODEM.CAN thenself.file_close(fd)return total_sent, YMODEM.ERROR.CANCELelseself.retry_count = self.retry_count + 1endendif self.retry_count >= self.config.max_retries thenself.file_close(fd)return total_sent, YMODEM.ERROR.TOO_MANY_RETRIESend-- 发送EOT结束文件传输self.serial_write(string.char(YMODEM.EOT))-- 等待ACKresponse = self:_get_char_timeout(self.config.timeout)if not response or response ~= YMODEM.ACK thenself.file_close(fd)return total_sent, YMODEM.ERROR.TIMEOUTend-- 发送空文件头包表示传输结束self:_send_file_header_packet({})-- 等待最终的ACKresponse = self:_get_char_timeout(self.config.timeout)if not response or response ~= YMODEM.ACK thenself.file_close(fd)return total_sent, YMODEM.ERROR.TIMEOUTendself.file_close(fd)-- 最终进度回调if self.progress_callback thenself.progress_callback(total_sent, file_info.file_size, "complete")endreturn total_sent, YMODEM.ERROR.SUCCESS
endreturn {YMODEM = YMODEM,YModem = YModem
}

http://www.dtcms.com/a/470380.html

相关文章:

  • 视频直播点播平台EasyDSS如何助力餐饮行业实现“明厨亮灶”直播?
  • 通过网站做外贸广告公司有哪些
  • 关于网站建设的好处seo搜索优化邵阳
  • 百家号淄博圻谷网站建设做网站页面一般用什么软件
  • CCF-GESP 等级考试 2024年3月认证C++三级真题解析
  • 本地部署 DeepSeek 私有助手:从零到上线的完整方案
  • CTF攻防世界WEB精选基础入门:weak_auth
  • 石家庄网站建设蓝点公路建设网站
  • 免费制作网页的网站企业app定制开发公司
  • 若依框架Springboot开发开放接口供他人调用
  • 在Centos上安装Python指定版本
  • 体育赛事 APP 开发:从技术到体验的全方位突破
  • 【阿里云】记一次oss攻击
  • MySQL高效备份实战指南
  • OpenBLT移植教程
  • 怎样做站长建网站荥阳市建设局网站
  • 虚拟仿真实训:打破时空界限,重塑未来技能,引领教育新变革
  • MySQL字符集配置全攻略:告别乱码
  • 「机器学习笔记10」贝叶斯学习——从逆向思维到简化现实的强大武器
  • 01-Python简介与环境搭建-教程
  • 高端设计网站都有哪些微信公众号推广赚钱
  • 数字化转型:概念性名词浅谈(第七十二讲)
  • 济南网站建设泉诺上海手机网站哪家最好
  • 鸿蒙Next文件上传下载:全场景高效数据传输方案
  • STM32G474单片机开发入门(九)低功耗模式实战
  • 怎么样给一个网站做横向导航栏搜索引擎优化的概念是什么
  • 网站开发近期市场做一个网站的预算
  • mac使用国内源安装brew并且配置使用国内源安装软件
  • 基因组组装:3. juicer 比对 HiC 数据至参考基因组
  • 工信部网站原来是wordpress发送自定义邮件