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();
}

函数流程:

  • 发送错误信号
  • 发送结束信号
相关推荐
monstercl24 分钟前
【C#】元组
开发语言·c#
舒克日记35 分钟前
Java:189 基于SSM框架的在线电影评价系统
java·开发语言
Jelena技术达人38 分钟前
深入探索:获取翻译文本与语言词法分析的API接口
开发语言·爬虫
青青丘比特1 小时前
STL.string(下)
开发语言·c++
jjjxxxhhh1231 小时前
C++ 模板是为了解决啥问题
开发语言·c++·算法
gz94561 小时前
Virtualbox安装ubuntu20虚拟机无法打开终端
java·linux·开发语言
奔跑的犀牛先生1 小时前
C#学习1:初接触,C#的一些基础,和相关报错
开发语言·c#
半夏知半秋2 小时前
lua debug相关方法详解
开发语言·学习·单元测试·lua
Andy01_2 小时前
Java八股汇总【MySQL】
java·开发语言·mysql
坊钰2 小时前
【Java 数据结构】合并两个有序链表
java·开发语言·数据结构·学习·链表