Qt实现TFTP Server和 TFTP Client(二)

发布于:2024-03-29 ⋅ 阅读:(24) ⋅ 点赞:(0)

3 实现

3.1 Core

Core模块包括下面4个类:

  • TFTP
  • BaseUdp
  • TFtpClientFile
  • TFtpServerFile
3.1.1 TFTP

TFTP类实现了TFTP协议。

3.1.1.1 TFTP定义
class TFtp
{
public:
    TFtp();

    enum Code {
        RRQ   = 0x0001,//Read request
        WRQ   = 0x0002,//Write request
        DATA  = 0x0003,//Data request
        ACK   = 0x0004,//Acknowledgement
        ERROR = 0x0005 //Error
    };
    enum Mode { BINARY, ASCII, MAIL };

    enum Error {
        NotDefined        = 0x0000,
        FileNotFound      = 0x0001,
        AccessViolation   = 0x0002,
        DiskFull          = 0x0003,
        IllegalOperation  = 0x0004,
        UnknownTransferID = 0x0005,
        FileExists        = 0x0006,
        NoSuchUser        = 0x0007,
    };

    enum Size {
        CODE_SIZE = 2,
        HEADER_SIZE = 4,
        BLOCK_SIZE  = 512
    };
    enum Type { None, Read, Write };

    bool process(uint8_t const *data, uint32_t size);
    bool is_finished() const { return finished_; }
    bool is_error() const { return !error_msg_.empty(); }
    Error error() const { return error_; }
    std::string error_msg() const { return error_msg_; }
protected:
    virtual void on_read_req(std::string const& filename, Mode mode) {}
    virtual void on_write_req(std::string const& filename, Mode mode) {}
    virtual void on_data(uint16_t block_number, uint8_t const*data, uint32_t size) = 0;
    virtual void on_ack(uint16_t block_number) = 0;
    virtual void on_error(uint16_t error, std::string const& error_msg) = 0;

    virtual uint32_t write(uint8_t const *data, size_t size) = 0;

    void read_req(std::string const& filename, Mode mode);
    void write_req(std::string const& filename, Mode mode);
    void send(uint16_t block_number, size_t size);
    void resend();
    void ack(uint16_t block_number);
    void error(Error error, std::string const& error_msg);
    char *data() { return (char *)(data_ + HEADER_SIZE); }
    void set_error(Error error, std::string const& error_msg)
    {
        error_ = error;
        error_msg_ = error_msg;
    }
    void finished() { finished_ = true; }
    size_t get_filesize(const char*filename);
private:
    uint16_t op_code(uint8_t const *data) { return static_cast<uint16_t>((data[0] << 8) | data[1]); }
    uint16_t block_num(uint8_t const *data) { return static_cast<uint16_t>((data[2] << 8) | data[3]); }
    uint16_t error_code(uint8_t const *data) { return static_cast<uint16_t>((data[2] << 8) | data[3]); }
    Mode getMode(std::string const& text);
    std::string getText(Mode mode);
private:
    uint8_t data_[HEADER_SIZE + BLOCK_SIZE];
    bool finished_ = false;
    TFtp::Error error_ = NotDefined;
    std::string error_msg_;
    size_t block_size_ = 0;
};

成员函数说明:

  • process 解析数据包为TFtp::Code的请求包
  • on_read_req 读请求虚函数,TFtpServerFile重载此函数
  • on_write_req 写请求虚函数,TFtpServerFile重载此函数
  • on_data 数据请求虚函数,TFtpServerFile/TFtpClientFile重载此函数
  • on_ack 应答请求虚函数,TFtpServerFile/TFtpClientFile重载此函数
  • on_error 出错处理虚函数,TFtpServerFile/TFtpClientFile重载此函数
  • write 写数据虚函数,TFtpServerFile/TFtpClientFile重载此函数
  • read_req 构造读请求数据包并发送
  • write_req 构造写请求数据包并发送
  • send 构造数据包请求并发送
  • resend 重发数据包
  • ack 构造应答请求数据包并发送
  • error 构造出错数据包并发送
3.1.1.2 TFTP实现
  • process/getMode
#define MIN_PACKET_LEN 4
bool TFtp::process(uint8_t const *data, uint32_t size)
{
    if(size < MIN_PACKET_LEN)
        return false;

    uint16_t code = op_code(data);
    if(code == RRQ || code == WRQ)
    {
        uint8_t const*d = data + sizeof(uint16_t);
        uint8_t const*e = data + size;
        uint8_t const*s = d;

        while(s < e && *s)
            s++;
        std::string filename((char *)d, s - d);

        s++;
        d = s;
        while(s < e && *s)
            s++;
        std::string mode_text((char *)d, s - d);

        if(code == RRQ)
            on_read_req(filename, getMode(mode_text));
        else
            on_write_req(filename, getMode(mode_text));
        return true;
    }
    else if(code == DATA)
    {
        on_data(block_num(data), &data[HEADER_SIZE], size - HEADER_SIZE);
        return true;
    }
    else if(code == ACK)
    {
        on_ack(block_num(data));
        return true;
    }
    else if(code == ERROR)
    {
        uint8_t const* d = data + HEADER_SIZE;
        uint8_t const *e = data + size;
        uint8_t const *s = d;

        while(s < e && *s)
            s++;

        on_error(error_code(data), std::string((char *)d, s - d));
        return true;
    }
    return false;
}
TFtp::Mode TFtp::getMode(std::string const& text)
{
    if(text == "octet")
        return BINARY;
    else if(text == "netascii")
        return ASCII;
    else
        return MAIL;
}

函数流程:

  1. 判断数据包长度小于最小长度4,解析失败返回
  2. 获取数据包的code。
  3. 根据code类型解析数据调用对应的接口函数。
  • read_req/write_req/send/resend/ack/error
#define WITE_CODE(data, code) \
    data[0] = uint8_t(code >> 8); \
    data[1] = uint8_t(code >> 0);

#define WITE_HEAD(data, code, value) \
    data[0] = uint8_t(code >> 8); \
    data[1] = uint8_t(code >> 0); \
    data[2] = uint8_t(value >> 8);\
    data[3] = uint8_t(value >> 0);

void TFtp::read_req(std::string const& filename, Mode mode)
{
    std::string text = getText(mode);
    std::vector<uint8_t> data(CODE_SIZE + filename.size() + text.size() + 2, 0);

    WITE_CODE(data, RRQ)
    memcpy(&data[CODE_SIZE], filename.c_str(), filename.size());
    memcpy(&data[CODE_SIZE + filename.size() + 1], text.c_str(), text.size());

    write(&data[0], data.size());
}

void TFtp::write_req(std::string const& filename, Mode mode)
{
    std::string text = getText(mode);
    std::vector<uint8_t> data(CODE_SIZE + filename.size() + text.size() + 2, 0);

    WITE_CODE(data, WRQ)
    memcpy(&data[CODE_SIZE], filename.c_str(), filename.size());
    memcpy(&data[CODE_SIZE + filename.size() + 1], text.c_str(), text.size());

    write(&data[0], data.size());
}

void TFtp::send(uint16_t block_number, size_t size)
{
   WITE_HEAD(data_, DATA, block_number)
   block_size_ = size;
   write(data_, size + HEADER_SIZE);
}

void TFtp::resend()
{
    write(data_, block_size_ + HEADER_SIZE);
}

void TFtp::ack(uint16_t block_number)
{
    std::vector<uint8_t> data(HEADER_SIZE);
    WITE_HEAD(data, ACK, block_number)
    write(&data[0], data.size());
}

void TFtp::error(Error error, std::string const& error_msg)
{
    std::vector<uint8_t> data(HEADER_SIZE + error_msg.size() + 1);
    error_ = error;
    error_msg_ = error_msg;
    finished();

    WITE_HEAD(data, ERROR, error)
    memcpy(&data[HEADER_SIZE], error_msg.c_str(), error_msg.size());
    data[data.size() - 1] = 0;

    write(&data[0], data.size());
}

函数说明:根据请求类型构造对应的请求包并发送。

3.1.2 BaseUdp
class BaseUdp
{
public:
    virtual ~BaseUdp(){}

    virtual uint32_t write(const char* data, size_t size) = 0;
};

类型说名:

  • 定义udp的写接口,该接口需要TFtpServer和TFtpClient去实现。
3.1.3 TFtpClientFile

TFtpClientFile类实现客户端文件收发

3.1.3.1 TFtpClientFile定义
class TFtpClientFile : public TFtp
{
public:
    TFtpClientFile(BaseUdp *udp)
        : udp_(udp)
        , type_(None)
    {}

    ~TFtpClientFile();

    bool getFile(std::string const& local_filename,
                 std::string const& remote_filename, Mode mode);
    bool putFile(std::string const& local_filename,
                 std::string const& remote_filename, Mode mode);

    size_t filesize() const { return filesize_; }
    size_t file_bytes() const { return file_bytes_; }

    using Ptr = std::shared_ptr<TFtpClientFile>;
protected:
    void on_data(uint16_t block_number, uint8_t const* data, uint32_t size) override;
    void on_ack(uint16_t block_number) override;
    void on_error(uint16_t error, std::string const& error_msg) override;
    uint32_t write(uint8_t const *data, size_t size) override;
private:
    void send_data(uint16_t block_number);
private:
    BaseUdp* udp_;
    Type type_;
    std::ifstream read_file;
    std::ofstream write_file;
    uint16_t block_number_ = 0;
    uint32_t block_size_ = 0;
    size_t filesize_ = 0;
    size_t file_bytes_ = 0;
};

成员函数说明:

  • getFile 下载文件
  • putFile 上传文件
  • on_data 实现数据请求
  • on_ack 实现应答请求
  • on_error 实现出错处理
  • write 实现写功能
  • send_data 从文件读取数据包并发送。
3.1.3.2 TFtpClientFile实现
  • getFile
bool TFtpClientFile::getFile(std::string const& local_filename,
                             std::string const& remote_filename,
                             Mode mode)
{
    if(mode == TFtp::BINARY)
        write_file.open(local_filename.c_str(),
                        std::ifstream::out | std::ifstream::binary);
    else
        write_file.open(local_filename.c_str());

    if(!write_file.is_open())
        return false;

    read_req(remote_filename, mode);
    type_ = Write;
    return true;
}

函数流程:

  • 以写方式打开本地文件

  • 发送读文件请求

  • 将类型设置为写

  • putFile

    if(mode == TFtp::BINARY)
        read_file.open(local_filename.c_str(),
                       std::ifstream::in | std::ifstream::binary);
    else
        read_file.open(local_filename.c_str());

    if(!read_file.is_open())
        return false;

    filesize_ = get_filesize(local_filename.c_str());
    write_req(remote_filename, mode);
    type_ = Read;
    return true;

函数流程:

  • 以读方式打开本地文件

  • 发送写文件请求

  • 将类型设置为读

  • on_data

void TFtpClientFile::on_data(uint16_t block_number, uint8_t const* data, uint32_t size)
{
    if(type_ != Write)
    {
        error(IllegalOperation, "Illegal TFTP Operation in Data");
        return;
    }
    if(block_size_ == 0)
        block_size_ = size;
    write_file.write((char *)data, size);
    file_bytes_ += size;
    ack(block_number);
    if(size < block_size_)
    {
        filesize_ = file_bytes_;
        finished();
        write_file.close();
    }
}

函数流程:

  • 保存数据包

  • 发送应答

  • 处理最后一个包

  • 下载结束

  • on_ack

void TFtpClientFile::on_ack(uint16_t block_number)
{
    if(type_ != Read)
    {
        error(IllegalOperation, "Illegal TFTP Operation in ACK");
        return;
    }
    if(read_file.eof())
    {
        std::cout << "send data is finished" << std::endl;
        finished();
        return;
    }
    if(block_number_ != block_number)
        resend();
    else
    {
        block_number_++;
        send_data(block_number_);
    }
}

函数流程:

  • 如果文件上传完毕,结束上传。

  • BlockNumber不同,则重传。

  • 上传下一Block。

  • on_error

void TFtpClientFile::on_error(uint16_t error, std::string const& error_msg)
{
     set_error((Error)error, error_msg + std::string(" come from remote"));
     finished();
}
  • send_data
void TFtpClientFile::send_data(uint16_t block_number)
{
    char* d = data();

    read_file.read(d, TFtp::BLOCK_SIZE);
    file_bytes_ += read_file.gcount();
    send(block_number, read_file.gcount());
}
  • write
uint32_t TFtpClientFile::write(uint8_t const *data, size_t size)
{
    return udp_->write((const char*)data, size);
}

3.1.4 TFtpServerFile

TFtpServerFile类实现服务端文件收发

3.1.4.1 TFtpServerFile定义
class TFtpServerFile : public TFtp
{
public:
    TFtpServerFile(BaseUdp *udp, std::string const& path, std::string const& id)
        : udp_(udp)
        , type_(None)
        , file_path_(path)
        , transfer_id_(id)
        , block_number_(0)
    {}

    ~TFtpServerFile();

    using Ptr = std::shared_ptr<TFtpServerFile>;

    std::string transfer_id() const { return transfer_id_; }
    Type type() const { return type_; }
    std::string filename() const { return filename_; }
    uint16_t block_number() const { return block_number_; }
    uint16_t block_numbers() const { return static_cast<uint16_t>((filesize_ + BLOCK_SIZE - 1) / BLOCK_SIZE); }
    size_t filesize() const { return filesize_; }
    size_t file_bytes() const { return file_bytes_; }
protected:
    void on_read_req(std::string const& filename, Mode mode) override;
    void on_write_req(std::string const& filename, Mode mode) override;
    void on_data(uint16_t block_number, uint8_t const* data, uint32_t size) override;
    void on_ack(uint16_t block_number) override;
    void on_error(uint16_t error, std::string const& error_msg) override;
    uint32_t write(uint8_t const *data, size_t size) override;
private:
    void send_data(uint16_t block_number);
    std::string full_fileaname(std::string const& filename) const {
        return file_path_ + filename;
    }

    TFtpServerFile(TFtpServerFile const&);
    TFtpServerFile(TFtpServerFile &&);
    TFtpServerFile operator == (TFtpServerFile const&);
    TFtpServerFile operator == (TFtpServerFile &&);
private:
    BaseUdp* udp_;
    Type type_;
    std::string filename_;
    std::string file_path_;
    std::string transfer_id_;
    std::ifstream read_file;
    std::ofstream write_file;
    uint16_t block_number_;
    uint32_t block_size_ = 0;
    size_t filesize_ = 0;
    size_t file_bytes_ = 0;
};

成员函数说明:

  • transfer_id 返回唯一传输ID,用来管理多个TFtpServerFile实例。
  • on_read_req 实现读请求。
  • on_write_req 实现写请求。
  • on_data 实现数据请求。
  • on_ack 实现应答请求。
  • on_error 实现出错处理。
  • write 实现写功能。
  • send_data 从文件读取数据包并发送。
3.1.4.2 TFtpServerFile实现
  • on_read_req
void TFtpServerFile::on_read_req(std::string const& filename, Mode mode)//read
{
    if(type_ != None)
    {
        error(IllegalOperation, "Illegal TFTP Operation in RRQ");
        return;
    }

    type_ = Read;
    filename_ = full_fileaname(filename);

    if(mode == TFtp::BINARY)
        read_file.open(filename_.c_str(),
                       std::ifstream::in | std::ifstream::binary);
    else
        read_file.open(filename_.c_str());

    if(!read_file.is_open())
        error(FileNotFound,  std::string("File(") + filename + std::string(") Not Found"));
    else
    {
        block_number_ = 1;
        filesize_ = get_filesize(filename_.c_str());
        send_data(block_number_);
    }
}

函数流程:

  • 以读方式打开文件

  • 设置类型为读

  • 发送第一个Block数据

  • on_write_req

void TFtpServerFile::on_write_req(std::string const& filename, Mode mode)//write
{
    if(type_ != None)
    {
        error(IllegalOperation, "Illegal TFTP Operation in WRQ");
        return;
    }

    filename_ = full_fileaname(filename);
    if(get_filesize(filename_.c_str()) > 0)
    {
        error(FileExists, "File Exists in WRQ");
        return;
    }

    type_ = Write;

    if(mode == TFtp::BINARY)
        write_file.open(filename_.c_str(),
                        std::ifstream::out | std::ifstream::binary);
    else
        write_file.open(filename_.c_str());

    if(!write_file.is_open())
        error(AccessViolation, "Access Violation");
    else
        ack(block_number_);//ack(0)
}

函数流程:

  • 以写方式打开文件。

  • 设置类型为写

  • 请求第一块数据

  • on_data

void TFtpServerFile::on_data(uint16_t block_number, uint8_t const* data, uint32_t size) //write
{
    if(type_ != Write)
    {
        error(IllegalOperation, "Illegal TFTP Operation in Data");
        return;
    }

    if(block_number != block_number_ + 1)
        ack(block_number_);
    else
    {
        if(block_size_ == 0)
            block_size_ = size;

        write_file.write((char *)data, size);
        file_bytes_ += size;
        ack(block_number);
        block_number_ = block_number;
        if(size < block_size_)
        {
            filesize_ = file_bytes_;
            write_file.close();
            finished();
        }
    }
}

函数流程:

  • Block号不一致,则请求前一个块号。

  • 保存数据包

  • 保存最后数据包,

  • 上传结束

  • on_ack

void TFtpServerFile::on_ack(uint16_t block_number) // read
{
    if(type_ != Read)
    {
        error(IllegalOperation, "Illegal TFTP Operation in ACK");
        return;
    }

    if(read_file.eof())
    {
        finished();
        return;
    }

    if(block_number != block_number_)
        resend();
    else
    {
        block_number_++;
        send_data(block_number_);
    }
}

函数流程:

  • 如果文件发送完毕,则结束

  • BlockNumber不一致,则重传

  • 增加BlockNumber,发送Block数据

  • on_error

void TFtpServerFile::on_error(uint16_t error, std::string const& error_msg) //read/write
{
    set_error((Error)error, error_msg + std::string(" come from remote"));
    finished();
}
  • write
uint32_t TFtpServerFile::write(uint8_t const *data, size_t size)
{
    return udp_->write((const char*)data, size);
}
  • send_data
void TFtpServerFile::send_data(uint16_t block_number)
{
    char* d = data();

    read_file.read(d, TFtp::BLOCK_SIZE);
    file_bytes_ += read_file.gcount();
    send(block_number, read_file.gcount());
}

Qt实现TFTP Server和 TFTP Client(一)Qt实现TFTP Server和 TFTP Client(三)

本文含有隐藏内容,请 开通VIP 后查看