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

函数流程:

  • 发送错误信号
  • 发送结束信号
相关推荐
Swift社区3 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht3 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht3 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20243 小时前
Swift 数组
开发语言
stm 学习ing4 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc5 小时前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe6 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin6 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python