Qt 实现Ymodem协议源码分享
Qt 实现Ymodem协议源码分享
- 一、Ymodem协议介绍
- 1、协议基础特性
- 2、传输流程分阶段
- (1) 、初始化阶段
- (2) 、数据传输阶段
- (3) 、结束阶段
- 3、关键改进与变体
- 4、典型应用场景
- 二、源码分享
- 1、效果展示
- 2、工程结构
- 3、源码分享
- 三、完整工程下载
一、Ymodem协议介绍
Ymodem协议是一种基于串行通信的文件传输协议,由Xmodem协议改进而来,主要用于通过RS-232等低速链路可靠传输文件。其核心设计目标是提高传输效率和支持批处理传输,以下是分层解析:
1、协议基础特性
-
数据块大小
默认采用 1024字节(1KB)数据块(可回退至128字节)。
数学表达传输效率提升:
效率增益=有效数据总传输量∝10241024+头部开销\text{效率增益} = \frac{\text{有效数据}}{\text{总传输量}} \propto \frac{1024}{1024 + \text{头部开销}} 效率增益=总传输量有效数据∝1024+头部开销1024
相比Xmodem的128字节块,传输开销显著降低。 -
错误检测机制
使用CRC-16(16位循环冗余校验)或可选校验和(Checksum)。
校验多项式:$ G(x) = x^{16} + x^{15} + x^2 + 1 $(标准CRC-CCITT)。 -
批处理支持
支持单次会话传输多个文件,通过文件头块传递文件名和大小。
2、传输流程分阶段
(1) 、初始化阶段
- 接收端发送
'C'
(ASCII 0x43)请求CRC模式(或NAK
请求校验和模式)。 - 发送端响应首个块(块编号 0),包含文件头信息:
文件名
+文件大小
+时间戳
(固定128字节,不足补NULL
)。
(2) 、数据传输阶段
- 块结构(每块共1028字节):
| SOH/STX (1B) | 块号 (1B) | ~块号 (1B) | 数据 (1024B) | CRC (2B) |
SOH
(0x01)标识128字节块,STX
(0x02)标识1024字节块。- 块号范围:$ 1 \leq \text{块号} \leq 255 $(模256循环)。
- 数据不足1024字节时以
EOF
(0x1A)填充。
- 接收端响应:
ACK
(0x06):成功接收,请求下一块。NAK
(0x15):校验失败,请求重传。CAN
(0x18):取消传输。
(3) 、结束阶段
- 发送端发出
EOT
(0x04)表示文件结束。 - 接收端回应
ACK
后,若需传输下一文件,重复初始化阶段。 - 无后续文件时,发送端发出
NULL
(0x00)结束会话。
3、关键改进与变体
- Ymodem-g(流式传输)
取消接收端确认(ACK/NAK
),数据连续发送。要求物理链路0误码率,否则整体重传。 - 与Xmodem对比优势:
特性 Xmodem Ymodem 块大小 128字节 1024字节 批处理 不支持 支持 文件名传递 无 文件头块实现 最大文件大小 8MB限制 理论无限制
4、典型应用场景
- 嵌入式系统:固件升级(如路由器Bootloader)。
- 工业控制:PLC程序传输。
- 遗留系统:通过串口/Telnet传输文件(如Linux
sz
/rz
工具)。
注:现代应用中,Ymodem因速率限制(通常<115.2kbps)逐渐被Zmodem取代,但其简洁性仍适用于低资源环境。实际实现需注意超时重传(默认10秒)和块号翻转处理。
二、源码分享
1、效果展示
2、工程结构
3、源码分享
widget.hpp
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include "YmodemFileTransmit.h"
#include "YmodemFileReceive.h"namespace Ui {
class Widget;
}class Widget : public QWidget
{Q_OBJECTpublic:explicit Widget(QWidget *parent = 0);~Widget();private slots:void on_comButton_clicked();void on_transmitBrowse_clicked();void on_receiveBrowse_clicked();void on_transmitButton_clicked();void on_receiveButton_clicked();void transmitProgress(int progress);void receiveProgress(int progress);void transmitStatus(YmodemFileTransmit::Status status);void receiveStatus(YmodemFileReceive::Status status);private:Ui::Widget *ui;QSerialPort *serialPort;YmodemFileTransmit *ymodemFileTransmit;YmodemFileReceive *ymodemFileReceive;bool transmitButtonStatus;bool receiveButtonStatus;
};#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QFileDialog>
#include <QSerialPortInfo>Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget),serialPort(new QSerialPort),ymodemFileTransmit(new YmodemFileTransmit),ymodemFileReceive(new YmodemFileReceive)
{transmitButtonStatus = false;receiveButtonStatus = false;ui->setupUi(this);QSerialPortInfo serialPortInfo;foreach(serialPortInfo, QSerialPortInfo::availablePorts()){ui->comPort->addItem(serialPortInfo.portName());}serialPort->setPortName("COM1");serialPort->setBaudRate(115200);serialPort->setDataBits(QSerialPort::Data8);serialPort->setStopBits(QSerialPort::OneStop);serialPort->setParity(QSerialPort::NoParity);serialPort->setFlowControl(QSerialPort::NoFlowControl);connect(ymodemFileTransmit, SIGNAL(transmitProgress(int)), this, SLOT(transmitProgress(int)));connect(ymodemFileReceive, SIGNAL(receiveProgress(int)), this, SLOT(receiveProgress(int)));connect(ymodemFileTransmit, SIGNAL(transmitStatus(YmodemFileTransmit::Status)), this, SLOT(transmitStatus(YmodemFileTransmit::Status)));connect(ymodemFileReceive, SIGNAL(receiveStatus(YmodemFileReceive::Status)), this, SLOT(receiveStatus(YmodemFileReceive::Status)));
}Widget::~Widget()
{delete ui;delete serialPort;delete ymodemFileTransmit;delete ymodemFileReceive;
}void Widget::on_comButton_clicked()
{static bool button_status = false;if(button_status == false){serialPort->setPortName(ui->comPort->currentText());serialPort->setBaudRate(ui->comBaudRate->currentText().toInt());if(serialPort->open(QSerialPort::ReadWrite) == true){button_status = true;ui->comPort->setDisabled(true);ui->comBaudRate->setDisabled(true);ui->comButton->setText(u8"关闭串口");ui->transmitBrowse->setEnabled(true);ui->receiveBrowse->setEnabled(true);if(ui->transmitPath->text().isEmpty() != true){ui->transmitButton->setEnabled(true);}if(ui->receivePath->text().isEmpty() != true){ui->receiveButton->setEnabled(true);}}else{QMessageBox::warning(this, u8"串口打开失败", u8"请检查串口是否已被占用!", u8"关闭");}}else{button_status = false;serialPort->close();ui->comPort->setEnabled(true);ui->comBaudRate->setEnabled(true);ui->comButton->setText(u8"打开串口");ui->transmitBrowse->setDisabled(true);ui->transmitButton->setDisabled(true);ui->receiveBrowse->setDisabled(true);ui->receiveButton->setDisabled(true);}
}void Widget::on_transmitBrowse_clicked()
{ui->transmitPath->setText(QFileDialog::getOpenFileName(this, u8"打开文件", ".", u8"任意文件 (*.*)"));if(ui->transmitPath->text().isEmpty() != true){ui->transmitButton->setEnabled(true);}else{ui->transmitButton->setDisabled(true);}
}void Widget::on_receiveBrowse_clicked()
{ui->receivePath->setText(QFileDialog::getExistingDirectory(this, u8"选择目录", ".", QFileDialog::ShowDirsOnly));if(ui->receivePath->text().isEmpty() != true){ui->receiveButton->setEnabled(true);}else{ui->receiveButton->setDisabled(true);}
}void Widget::on_transmitButton_clicked()
{if(transmitButtonStatus == false){serialPort->close();ymodemFileTransmit->setFileName(ui->transmitPath->text());ymodemFileTransmit->setPortName(ui->comPort->currentText());ymodemFileTransmit->setPortBaudRate(ui->comBaudRate->currentText().toInt());if(ymodemFileTransmit->startTransmit() == true){transmitButtonStatus = true;ui->comButton->setDisabled(true);ui->receiveBrowse->setDisabled(true);ui->receiveButton->setDisabled(true);ui->transmitBrowse->setDisabled(true);ui->transmitButton->setText(u8"取消");ui->transmitProgress->setValue(0);}else{QMessageBox::warning(this, u8"失败", u8"文件发送失败!", u8"关闭");}}else{ymodemFileTransmit->stopTransmit();}
}void Widget::on_receiveButton_clicked()
{if(receiveButtonStatus == false){serialPort->close();ymodemFileReceive->setFilePath(ui->receivePath->text());ymodemFileReceive->setPortName(ui->comPort->currentText());ymodemFileReceive->setPortBaudRate(ui->comBaudRate->currentText().toInt());if(ymodemFileReceive->startReceive() == true){receiveButtonStatus = true;ui->comButton->setDisabled(true);ui->transmitBrowse->setDisabled(true);ui->transmitButton->setDisabled(true);ui->receiveBrowse->setDisabled(true);ui->receiveButton->setText(u8"取消");ui->receiveProgress->setValue(0);}else{QMessageBox::warning(this, u8"失败", u8"文件接收失败!", u8"关闭");}}else{ymodemFileReceive->stopReceive();}
}void Widget::transmitProgress(int progress)
{ui->transmitProgress->setValue(progress);
}void Widget::receiveProgress(int progress)
{ui->receiveProgress->setValue(progress);
}void Widget::transmitStatus(Ymodem::Status status)
{switch(status){case YmodemFileTransmit::StatusEstablish:{break;}case YmodemFileTransmit::StatusTransmit:{break;}case YmodemFileTransmit::StatusFinish:{transmitButtonStatus = false;ui->comButton->setEnabled(true);ui->receiveBrowse->setEnabled(true);if(ui->receivePath->text().isEmpty() != true){ui->receiveButton->setEnabled(true);}ui->transmitBrowse->setEnabled(true);ui->transmitButton->setText(u8"发送");QMessageBox::warning(this, u8"成功", u8"文件发送成功!", u8"关闭");break;}case YmodemFileTransmit::StatusAbort:{transmitButtonStatus = false;ui->comButton->setEnabled(true);ui->receiveBrowse->setEnabled(true);if(ui->receivePath->text().isEmpty() != true){ui->receiveButton->setEnabled(true);}ui->transmitBrowse->setEnabled(true);ui->transmitButton->setText(u8"发送");QMessageBox::warning(this, u8"失败", u8"文件发送失败!", u8"关闭");break;}case YmodemFileTransmit::StatusTimeout:{transmitButtonStatus = false;ui->comButton->setEnabled(true);ui->receiveBrowse->setEnabled(true);if(ui->receivePath->text().isEmpty() != true){ui->receiveButton->setEnabled(true);}ui->transmitBrowse->setEnabled(true);ui->transmitButton->setText(u8"发送");QMessageBox::warning(this, u8"失败", u8"文件发送失败!", u8"关闭");break;}default:{transmitButtonStatus = false;ui->comButton->setEnabled(true);ui->receiveBrowse->setEnabled(true);if(ui->receivePath->text().isEmpty() != true){ui->receiveButton->setEnabled(true);}ui->transmitBrowse->setEnabled(true);ui->transmitButton->setText(u8"发送");QMessageBox::warning(this, u8"失败", u8"文件发送失败!", u8"关闭");}}
}void Widget::receiveStatus(YmodemFileReceive::Status status)
{switch(status){case YmodemFileReceive::StatusEstablish:{break;}case YmodemFileReceive::StatusTransmit:{break;}case YmodemFileReceive::StatusFinish:{receiveButtonStatus = false;ui->comButton->setEnabled(true);ui->transmitBrowse->setEnabled(true);if(ui->transmitPath->text().isEmpty() != true){ui->transmitButton->setEnabled(true);}ui->receiveBrowse->setEnabled(true);ui->receiveButton->setText(u8"接收");QMessageBox::warning(this, u8"成功", u8"文件接收成功!", u8"关闭");break;}case YmodemFileReceive::StatusAbort:{receiveButtonStatus = false;ui->comButton->setEnabled(true);ui->transmitBrowse->setEnabled(true);if(ui->transmitPath->text().isEmpty() != true){ui->transmitButton->setEnabled(true);}ui->receiveBrowse->setEnabled(true);ui->receiveButton->setText(u8"接收");QMessageBox::warning(this, u8"失败", u8"文件接收失败!", u8"关闭");break;}case YmodemFileReceive::StatusTimeout:{receiveButtonStatus = false;ui->comButton->setEnabled(true);ui->transmitBrowse->setEnabled(true);if(ui->transmitPath->text().isEmpty() != true){ui->transmitButton->setEnabled(true);}ui->receiveBrowse->setEnabled(true);ui->receiveButton->setText(u8"接收");QMessageBox::warning(this, u8"失败", u8"文件接收失败!", u8"关闭");break;}default:{receiveButtonStatus = false;ui->comButton->setEnabled(true);ui->transmitBrowse->setEnabled(true);if(ui->transmitPath->text().isEmpty() != true){ui->transmitButton->setEnabled(true);}ui->receiveBrowse->setEnabled(true);ui->receiveButton->setText(u8"接收");QMessageBox::warning(this, u8"失败", u8"文件接收失败!", u8"关闭");}}
}
三、完整工程下载
https://download.csdn.net/download/qq_15181569/91667788