目录
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;
}
函数流程:
- 判断数据包长度小于最小长度4,解析失败返回
- 获取数据包的code。
- 根据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(三)