前面我们已经实现了UDP的回环客户端和回环服务器的简单应用,接下来我们实现一个基于UDP的简单文件传输协议TFTP。

1TFTP协议简介

  TFTP是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。端口号为69

  TFTP是一种简单的文件传输协议。目标是在UDP之上上建立一个类似于FTP的但仅支持文件上传和下载功能的传输协议,所以它不包含FTP协议中的目录操作和用户权限等内容。

  TFTP报文的头两个字节表示操作码,共有5中操作码,如下表:

  读请求和写请求功能码的数据报文格式是一样的,所以TFTP报文又可表述为4种形式。对于读请求或者写请求,文件名字段说明客户要读或写的位于服务器的上的文件并以0字节作为结束,模式字段是一个ASCII码串,同样以0字节结束。读请求和写请求的报文格式:

  其次是数据包,起包括2个字节的块编号以及0-512个字节的数据信息。数据包相对比较简单,其报文格式:

  再者为确认包。确认包也有2个字节的块编号。其数据格式:

  最后一种TFTP报文类型是差错报文,它的操作码为5.它用于服务器不能处理读请求或者写请求的情况。在文件传输的过程中的读和写也会导致传送这种报文,接着停止传输。错误包的报文格式:

   TFTP的工作过程很像停止等待协议,发送完一个文件块后就等待对方的确认,确认时应指明所确认的块号。发送完数据后在规定时间内收不到确认就要重发数据PDU,发送确认PDU的一方若在规定时间内收不到下一个文件块,也要重发确认PDU。这样保证文件的传送不致因某一个数据报的丢失而告失败。

2TFTP协议栈设计

  前面我们简单的介绍了TFTP协议,接下来我们看看该如何实现其编程。它有5种操作码,我们要做的就是实现对这5种操作码的响应。

2.1、读请求实现

  所谓读请求,就是客户端请求从服务器获取文件,那么服务器需要做的自然是响应客户端的请求。但我们并没有文件,所以不管它请求什么文件,我们均给它返回内容和大小相同的测试文件。

 /* TFTP读请求处理*/
int TftpReadProcess(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, char* FileName)
{
tftp_connection_args *args = NULL; /* 这个函数在回调函数中被调用,因此中断被禁用,因此我们可以使用常规的malloc */
args = mem_malloc(sizeof(tftp_connection_args)); if (!args)
{
/* 内存分配失败 */
SendTftpErrorMessage(upcb, to, to_port, TFTP_ERR_NOTDEFINED); CleanTftpConnection(upcb, args); return ;
} /* i初始化连接结构体 */
args->op = TFTP_RRQ;
args->remote_port = to_port;
args->block = ; /* 块号从1开始 */
args->tot_bytes = **; /* 注册回调函数 */
udp_recv(upcb, RrqReceiveCallback, args); /* 通过发送第一个块来建立连接,后续块在收到ACK后发送*/
SendNextBlock(upcb, args, to, to_port); return ;
}

2.2、写请求实现

  写请求就是客户端希望向服务器传送文件,在这里我们只是实现TFTP服务器的功能,没必要将收到的文件真正保存到一个地方,所以只是做接收文件的过程并不将其写到存储器,简单的说就是只在内存中而不会写入Flash等。

 /* TFTP写请求处理 */
int TftpWriteProcess(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, char *FileName)
{
tftp_connection_args *args = NULL; /* 这个函数在回调函数中被调用,因此中断被禁用,因此我们可以使用常规的malloc */
args = mem_malloc(sizeof(tftp_connection_args)); if (!args)
{
SendTftpErrorMessage(upcb, to, to_port, TFTP_ERR_NOTDEFINED); CleanTftpConnection(upcb, args); return ;
} args->op = TFTP_WRQ;
args->remote_port = to_port;
args->block = ; //WRQ响应的块号为0
args->tot_bytes = ; /* 为控制块注册回调函数 */
udp_recv(upcb, WrqReceiveCallback, args); /* 通过发送第一个ack来发起写事务 */
SendTftpAckPacket(upcb, to, to_port, args->block); return ;
}

2.3、数据包操作

  无论是读请求还是写请求,最终的目的无非是要传送数据,所以数据包自然也是我们需要构造和传送的。其对应的就是数据包操作码,我们设计程序如下:

 /* 构造并且传送数据包 */
static int SendTftpDataPacket(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, int block,char *buf, int buflen)
{
/* 将开始的2个字节设置为功能码 */
SetTftpOpCode(buf, TFTP_DATA); /* 将后续2个字节设置为块号 */
SetTftpBlockNumber(buf, block); /* 在后续设置n个字节的数据 */ /* 发送数据包 */
return SendTftpMessage(upcb, to, to_port, buf, buflen + );
}

2.4、确认包操作

  在传送数据包后,收到没收到,发送方是不知道的,怎么办呢?这时候接受方接收到后,会给出一个确认包。其对应的就是确认操作码,那么我们还需实现确认包的构造和发送。

 /*构造并发送确认包*/
int SendTftpAckPacket(struct udp_pcb *upcb,const ip_addr_t *to, int to_port, int block)
{
/* 创建一个TFTP ACK包 */
char packet[TFTP_ACK_PKT_LEN]; /* 将开始的2个字节设置为功能码 */
SetTftpOpCode(packet, TFTP_ACK); /* 制定ACK的块号 */
SetTftpBlockNumber(packet, block); return SendTftpMessage(upcb, to, to_port, packet, TFTP_ACK_PKT_LEN);
}

2.5、错误包操作

  在包传送的过程中,有没有可能出现错误呢?当然是有的,这就需要所谓的错误包操作码。在服务器不能处理读请求或者写请求的情况下。在文件传输的过程中的读和写也会导致传送这种报文,接着停止传输。我们也需要开发构造和传送错误包的函数。

 /* 构造并向客户端发送一条错误消息 */
static int SendTftpErrorMessage(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, tftp_errorcode err)
{
char buf[];
int error_len; error_len = ConstructTftpErrorMessage(buf, err); return SendTftpMessage(upcb, to, to_port, buf, error_len);
}

3TFTP服务器实现

  我们已经实现了UDP服务器,而且也实现了简单的TFTP协议栈,接下来的工作就是在UDP基础上实现TFTP服务器功能。前面我们已经提到过,复杂的服务器应用只是回到函数的功能不一样,所以开发的过程并无区别。

  首先我们来实现初始化部分。创建新的UDP控制块。绑定到制定的服务器端口,我们要实现TFTP服务器,而TFTP协议的端口号为69,所以我们将其绑定到该端口。最后注册TFTP服务器的回调函数。

 /* 初始化TFTP服务器 */
void Tftp_Server_Initialization(void)
{
err_t err;
struct udp_pcb *tftp_server_pcb = NULL; /* 生成新的 UDP PCB控制块 */
tftp_server_pcb = udp_new(); /* 判断UDP控制块是否正确生成 */
if (NULL == tftp_server_pcb)
{
return;
} /* 绑定PCB控制块到指定端口 */
err = udp_bind(tftp_server_pcb, IP_ADDR_ANY, UDP_TFTP_SERVER_PORT); if (err != ERR_OK)
{
udp_remove(tftp_server_pcb);
return;
} /* 注册TFTP服务器处理函数 */
udp_recv(tftp_server_pcb, TftpServerCallback, NULL);
}

  在初始化中注册了回调函数,所以我们还要实现TFTP服务器的回调函数。这部分出于结构清晰的考虑,我们分成两个函数来写。

 /* TFTP服务器回调函数 */
static void TftpServerCallback(void *arg, struct udp_pcb *upcb, struct pbuf *p,const ip_addr_t *addr, u16_t port)
{
/* 处理新的连接请求 */
ProcessTftpRequest(p, addr, port); pbuf_free(p);
}
/* 从每一个来自addr:port的新请求创建一个新的端口来服务响应,并启动响应过程 */
static void ProcessTftpRequest(struct pbuf *pkt_buf, const ip_addr_t *addr, u16_t port)
{
tftp_opcode op = ExtractTftpOpcode(pkt_buf->payload);
char FileName[] = {};
struct udp_pcb *upcb = NULL;
err_t err; /* 生成新的UDP PCB控制块 */
upcb = udp_new();
if (!upcb)
{
return;
} /* 连接 */
err = udp_connect(upcb, addr, port);
if (err != ERR_OK)
{
return;
} ExtractTftpFilename(FileName, pkt_buf->payload); switch (op)
{
case TFTP_RRQ:
{ TftpReadProcess(upcb, addr, port, FileName);
break;
}
case TFTP_WRQ:
{
/* 启动TFTP写模式 */
TftpWriteProcess(upcb, addr, port, FileName);
break;
}
default:
{
/* 异常,发送错误消息 */
SendTftpErrorMessage(upcb, addr, port, TFTP_ERR_ACCESS_VIOLATION); udp_remove(upcb); break;
}
}
}

  在回调函数中,我们实现了对TFTP读请求和写请求的响应,但这足以验证我们想要实现的TFTP服务器的功能。

4、结论

  本篇我们基于LwIP的UDP实现了一个简单的FTP服务器。这个FTP服务器只是实现FTP协议的功能,具体的应用可根据需要添加。我们使用了TFTP客户端工具对这一服务器进行了基本测试,最终结果符合我们的预期。

欢迎关注:

LwIP应用开发笔记之四:LwIP无操作系统TFTP服务器的更多相关文章

  1. LwIP应用开发笔记之一:LwIP无操作系统基本移植

    现在,TCP/IP协议的应用无处不在.随着物联网的火爆,嵌入式领域使用TCP/IP协议进行通讯也越来越广泛.在我们的相关产品中,也都有应用,所以我们结合应用实际对相关应用作相应的总结. 1.技术准备 ...

  2. LwIP应用开发笔记之六:LwIP无操作系统TCP客户端

    上一篇我们基于LwIP协议栈的RAW API实现了一个TCP服务器的简单应用,接下来一节我们来实现一个TCP客户端的简单应用. 1.TCP简述 TCP(Transmission Control Pro ...

  3. LwIP应用开发笔记之五:LwIP无操作系统TCP服务器

    前面我们实现了UDP服务器及客户端以及基于其上的TFTP应用服务器.接下来我们将实现同样广泛应用的TCP协议各类应用. 1.TCP简述 TCP(Transmission Control Protoco ...

  4. 【嵌入式开发】嵌入式 开发环境 (远程登录 | 文件共享 | NFS TFTP 服务器 | 串口连接 | Win8.1 + RedHat Enterprise 6.3 + Vmware11)

    作者 : 万境绝尘 博客地址 : http://blog.csdn.net/shulianghan/article/details/42254237 一. 相关工具下载 嵌入式开发工具包 : -- 下 ...

  5. LwIP应用开发笔记之七:LwIP无操作系统HTTP服务器

    前面我们实现了TCP服务器和客户端的简单应用,接下来我们实现一个基于TCP协议的应用协议,那就是HTTP超文本传输协议 1.  HTTP协议简介   超文本传输协议(Hyper Text Transf ...

  6. LwIP应用开发笔记之八:LwIP无操作系统HTTP客户端

    前面我们实现了TCP服务器和客户端的简单应用,接下来我们实现一个基于TCP协议的应用协议,那就是HTTP超文本传输协议 1.HTTP协议简介 超文本传输协议(Hyper Text Transfer P ...

  7. LwIP应用开发笔记之二:LwIP无操作系统UDP服务器

     前面我们已经完成了LwIP协议栈基于逻辑的基本移植,在这一节我们将以RAW API来实现UDP服务器. 1.UDP协议简述 UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包, ...

  8. LwIP应用开发笔记之三:LwIP无操作系统UDP客户端

    前一节我们实现了基于RAW API的UDP服务器,在接下来,我们进一步利用RAW API实现UDP客户端. 1.UDP协议简述 UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包 ...

  9. Modbus库开发笔记之四:Modbus TCP Client开发

    这一次我们封装Modbus TCP Client应用.同样的我们也不是做具体的应用,而是实现TCP客户端的基本功能.我们将TCP客户端的功能封装为函数,以便在开发具体应用时调用. 对于TCP客户端我们 ...

随机推荐

  1. 为什么使用正则RegExp.test( )方法时第一次是 true,第二次是false?

    今天朋友问我一个问题,我现在需要多次匹配同一个内容,但是为什么我第一次匹配,直接是 true,而第二次匹配确实 false 呢? var s1 = "MRLP"; var s2 = ...

  2. 31、Python程序中的协程操作(greenlet\gevent模块)

    一.协程介绍 协程:是单线程下的并发,又称微线程,纤程.英文名Coroutine.一句话说明什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的. 对比操作系统控制线程的切换,用 ...

  3. curl多线程下载类

    <?php /** * curl多线程下载类 */class MultiHttpRequest{ public $urls = array (); private $res = array () ...

  4. SpringBoot 过滤器, 拦截器, 监听器 对比及使用场景

    1. 过滤器 (实现 javax.servlet.Filter 接口) ① 过滤器是在web应用启动的时候初始化一次, 在web应用停止的时候销毁. ② 可以对请求的URL进行过滤, 对敏感词过滤, ...

  5. MySQL binlog2sql实现MySQL误操作的恢复

    对于MySQL数据库中的误操作删除数据的恢复问题,可以使用基于MySQL中binlog做到类似于闪回或者生成反向操作的SQL语句来实现,是MySQL中一个非常实用的功能.原理不难理解,基于MySQL的 ...

  6. Linux安全加固(二)禁止普通用户su到root/设置SSH终端接入白名单/修改history条数

    一.禁止普通用户su到root管理员.设置可以su到root的白名单 1.首先看一下正常情况 2.可以看到普通用户使用su root命令,输入密码即可登录到root用户 3.下面开始配置禁止所有普通用 ...

  7. minio gataway 模式快速提供s3 兼容的文件服务

    实际很多场景我们已经有了遗留系统的文件存储方式(ftp,或者共享目录),但是这个方式可能不是很好,对于web 不是很友好 实际上minio 也提供了gateway 的模式,可以方便快速的将遗留系统的存 ...

  8. luogu P2353 背单词

    二次联通门 : luogu P2353 背单词 一眼看过去, 卧槽,AC自动机板子题 写完后T成SB 卧槽10^6 做个篮子啊 重构思路... 恩..Hash + 莫队... 恶心啊.. 找xxy d ...

  9. 第04组alpha冲刺(2/4)

    队名:斗地组 组长博客:地址 作业博客:Alpha冲刺(2/4) 各组员情况 林涛(组长) 过去两天完成了哪些任务: 1.收集各个组员的进度 2.写博客 展示GitHub当日代码/文档签入记录: 接下 ...

  10. [题解向] Luogu2146[NOI2015]软件包管理器

    #\(\mathcal{\color{red}{Description}}\) \(Link\) 一道\(zz\)的树剖题\(qwq\). #\(\mathcal{\color{red}{Soluti ...