Qt实现XYModem协议(六)

1 概述

XMODEM协议是一种使用拨号调制解调器的个人计算机通信中广泛使用的异步文件运输协议。这种协议以128字节块的形式传输数据,并且每个块都使用一个校验和过程来进行错误检测。使用循环冗余校验的与XMODEM相应的一种协议称为XMODEM-CRC。还有一种是XMODEM-1K,它以1024字节一块来传输数据。YMODEM也是一种XMODEM的实现。它包括XMODEM-1K的所有特征,另外在一次单一会话期间为发送一组文件,增加了批处理文件传输模式。

本文利用C++实现XYModem-1K协议,并利用Qt串口类QSerialPort实现数据读写。

3 实现

3.5 XYModemRecvFile

该模块实现XYModem协议接收文件。

流程图:

3.5.1 XYModemRecvFile定义

cpp 复制代码
class QSerialPort;
class XYModemRecvFile : public QObject, public YModem
{
    Q_OBJECT
public:
    explicit XYModemRecvFile(QSerialPort *serial, QObject *parent = nullptr);

public slots:
    void startYModem(QString const& fileName);
    void startXModem(QString const& fileName);
    void stop();
    void cancel();
signals:
    void gotFileSize(quint64 filesize);
    void progressInfo(quint32 blockNumber, quint64 bytesOfRecv);
    void error(QString const& e);
    void finished();

protected:
    uint32_t write(uint8_t const *data, uint32_t size) override;
    uint32_t read(uint8_t *data, uint32_t size) override;
    uint8_t get_code(bool isWait = true) override;
    uint32_t do_recv(uint8_t code);
private:
    bool singled() { return signal_; }
    void doSignal() { signal_ = true; };
    void doError(QString const& text);
    const char* data() { return reinterpret_cast<const char*>(data_ + ID); }
    uint64_t get_filesize(const char* data, uint32_t size);
private:
     QSerialPort* serial_;
     volatile bool signal_;
     uint8_t data_[DATA_SIZE2];
};

公共接口:

  • startYModem 开始YModem协议接收文件
  • startXModem 开始XModem协议接收文件
  • stop 停止发送
  • cancel 取消发送

信号:

  • gotFileSize 文件大小信号
  • progressInfo 传输进度信号
  • error 出错信号
  • finished 传输结束信号

重载接口:

  • write 向串口写数据
  • read 从串口读取数据
  • get_code 读取操作码
  • do_recv 接收数据并验证

3.5.2 XYModemRecvFile实现

3.5.2.1 startYModem
cpp 复制代码
void XYModemRecvFile::startYModem(QString const& fileName)
{
    uint8_t code = wait_start();
    if(code == MAX)
    {
        doError("cannot get start code!");
        return;
    }

    uint32_t size = do_recv(code);
    if(size == 0)
    {
        doError("cannot get start data!");
        return;
    }
    uint64_t fileSize = get_filesize(data(), size);
    QFile file(fileName);
    if(!file.open(QIODevice::WriteOnly))
    {
        doError(QString("%1 cannot be opened!").arg(fileName));
        return;
    }

    emit gotFileSize(fileSize);
    quint32 blockNumber = 0;
    uint64_t bytesOfRecv = 0;
    while(!singled())
    {
        code = get_code();
        if(code == EOT)
        {
            tx_code(ACK);
            break;
        }

        size = do_recv(code);
        if(size == 0)
            tx_code(NAK);
        else
        {
            if(file.write(data(), size) != size)
            {
                doError("write file error!");
                return;
            }
            blockNumber++;
            bytesOfRecv += size;
            tx_code(ACK);
            emit progressInfo(blockNumber, bytesOfRecv);
        }
    }

    emit finished();
    serial_->moveToThread(QApplication::instance()->thread());
}

函数流程:

  • 发送C码并等待操作码
  • 如果是无效操作码,发送错误信号并返回
  • 接收第一数据包
  • 如果收到数据大小为0,发送错误信号并返回
  • 从第一个数据包中获取文件大小
  • 打开文件,如果打开文件失败发送错误信号并返回
  • 发送文件大小信号gotFileSize
  • 操作码是EOT发送ACK,结束文件传送并返回
  • 读取数据
  • 如果没读到数据则发送NAK码要求发送发重传
  • 保存数据,如果保存失败发送错误信号并返回
  • 发送ACK码,发送进度信号
  • 重复上述5步,直到文件发送完毕或停止
  • 发送传输结束信号finished
3.5.2.2 startXModem
cpp 复制代码
void XYModemRecvFile::startXModem(QString const& fileName)
{
    Q_UNUSED(fileName)
    uint8_t code = wait_start();
    if(code == MAX)
    {
        doError("cannot get start code!");
        return;
    }
    QFile file(fileName);
    if(!file.open(QIODevice::WriteOnly))
    {
        doError(QString("%1 cannot be opened!").arg(fileName));
        return;
    }
    quint32 blockNumber = 0;
    uint64_t bytesOfRecv = 0;
    do
    {
        uint32_t size = do_recv(code);
        if(size == 0)
            tx_code(NAK);
        else
        {
            if(file.write(data(), size) != size)
            {
                doError("write file error!");
                return;
            }
            tx_code(ACK);
            blockNumber++;
            bytesOfRecv += size;
            emit progressInfo(blockNumber, bytesOfRecv);
        }
        code = get_code();
        if(code == EOT)
        {
            tx_code(ACK);
            break;
        }
    }while(!singled());

    emit finished();
    serial_->moveToThread(QApplication::instance()->thread());
}

函数流程:

  • 发送C码并等待操作码
  • 如果是无效操作码,发送错误信号并返回
  • 打开文件,如果打开文件失败发送错误信号并返回
  • 读取数据
  • 如果没读到数据则发送NAK码要求发送发重传
  • 保存数据,如果保存失败发送错误信号并返回
  • 发送ACK码,发送进度信号
  • 获取操作码
  • 操作码是EOT发送ACK,结束文件传送并返回
  • 重复上述6步,直到文件发送完毕或停止
  • 发送传输结束信号finished
3.5.2.3 do_recv
cpp 复制代码
uint32_t XYModemRecvFile::do_recv(uint8_t code)
{
    uint32_t size = 0;
    if(code == SOH)
        size = read(data_, DATA_SIZE1);
    else if(code == STX)
        size = read(data_, DATA_SIZE2);

    if(size < ID + CRC16)
        return 0;

    uint16_t crc = (data_[size - 2] << 8) | data_[size - 1];
    if(crc16(data_ + ID, size - ID - CRC16) == crc)
        return size - ID + CRC16;

    return 0;
}

函数流程:

  • 根据code是SOH还是STX读取不同数据包
  • 如果读到数据太小返回0
  • 对收到数据做CRC校准与数据包末尾CRC比较,如果相同返回读取数据字节数
  • 失败返回0
3.5.2.4 write/read
cpp 复制代码
uint32_t XYModemSendFile::write(uint8_t const *data, uint32_t size)
{
    return serial_->write((const char *)data, size);
}
uint32_t XYModemSendFile::read(uint8_t *data, uint32_t size)
{
    return serial_->read((char *)data, size);
}

函数说明:

  • 直接调用串口读写接口实现读写接口。
3.4.2.5 get_code
cpp 复制代码
uint8_t XYModemSendFile::get_code(bool isWait)
{
    while(!singled())
    {
        if(serial_->waitForReadyRead(10))
        {
            uint8_t data[1] = { 0 };
            read(data, sizeof(data));
            return data[0];
        }
        if(!isWait)
            break;
    }
    return MAX;
}

函数流程:

  • 等待串口输入
  • 如果有输入则读取一字节做为code码并返回.
  • 如果一直没有输入直到调用stop后返回无效码MAX.
3.4.2.6 doError
cpp 复制代码
void XYModemSendFile::doError(QString const& text)
{
    emit error(text);
    serial_->moveToThread(QApplication::instance()->thread());
    emit finished();
}

函数流程:

  • 发送错误信号
  • 发送结束信号
相关推荐
DieSnowK7 分钟前
[项目][WebServer][Makefile & Shell]详细讲解
开发语言·c++·http·makefile·shell·项目·webserver
Freak嵌入式7 分钟前
全网最适合入门的面向对象编程教程:50 Python函数方法与接口-接口和抽象基类
java·开发语言·数据结构·python·接口·抽象基类
冷凝女子10 分钟前
【QT】基于HTTP协议的网络应用程序
开发语言·qt·http
QT界面美化11 分钟前
QT硬件通讯基础
qt·qt6·qt quick
知识分享小能手13 分钟前
mysql学习教程,从入门到精通,SQL 删除数据(DELETE 语句)(19)
大数据·开发语言·数据库·sql·学习·mysql·数据开发
鸽芷咕21 分钟前
【Python报错已解决】libpng warning: iccp: known incorrect sRGB profile
开发语言·python·机器学习·bug
白总Server27 分钟前
MongoDB解说
开发语言·数据库·后端·mongodb·golang·rust·php
XyLin.30 分钟前
Msf之Python分离免杀
开发语言·python·网络安全·系统安全
声学黑洞仿真工作室32 分钟前
Matlab Delany-Bazley和Miki模型预测多孔材料吸声性能
开发语言·人工智能·算法·matlab·微信公众平台
计算机学姐40 分钟前
基于python+django+vue的家居全屋定制系统
开发语言·vue.js·后端·python·django·numpy·web3.py