LwIP应用开发笔记之四:LwIP无操作系统TFTP服务器

前面我们已经实现了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 0;}/* i初始化连接结构体  */args->op = TFTP_RRQ;args->remote_port = to_port;args->block = 1;/* 块号从1开始 */args->tot_bytes = 10*1024*1024;/* 注册回调函数 */udp_recv(upcb, RrqReceiveCallback, args);/* 通过发送第一个块来建立连接,后续块在收到ACK后发送*/SendNextBlock(upcb, args, to, to_port);return 1;
}

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 0;}args->op = TFTP_WRQ;args->remote_port = to_port;args->block = 0;      //WRQ响应的块号为0args->tot_bytes = 0;/* 为控制块注册回调函数 */udp_recv(upcb, WrqReceiveCallback, args);/* 通过发送第一个ack来发起写事务 */SendTftpAckPacket(upcb, to, to_port, args->block);  return 0;
}

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 + 4);
}

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[512];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[50] = {0};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客户端工具对这一服务器进行了基本测试,最终结果符合我们的预期。

欢迎关注:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499389.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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

前面我们实现了UDP服务器及客户端以及基于其上的TFTP应用服务器。接下来我们将实现同样广泛应用的TCP协议各类应用。 1、TCP简述 TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由…

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

上一篇我们基于LwIP协议栈的RAW API实现了一个TCP服务器的简单应用,接下来一节我们来实现一个TCP客户端的简单应用。 1、TCP简述 TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议&a…

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

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

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

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

LwIP应用开发笔记之九:LwIP无操作系统TELNET服务器

前面我们已经实现了基于RAW API的TCP服务器和客户端,也在此基础上实现了HTTP应用。接下来我们实现一个基于TCP的Telnet服务器应用。 1、Telnet协议简介 Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了…

在ARM Cortex-M上实现FreeRTOS性能计数器

说明:本文翻译自Erich Styger的文章《Implementing FreeRTOS Performance Counters on ARM Cortex-M》,文章的权属属于原作者。 当使用像FreeRTOS这样的RTOS时,迟早要问一个问题:每个任务花费多少时间?基于Eclipse的M…

STM32学习及开发笔记八:采用主从计时器实现精确脉冲输出

脉冲信号用于设备控制是非常常见的,但在一些情况下,我们希望精确的控制脉冲的数量以实现对运动的精确控制。实现的方式也许有多种多样,但使用计时器来实现此类操作是人们比较容易想到的。 1、原理概述 我们知道在STM32平台上,使…

外设驱动库开发笔记0:EPD总体设计

在产品开发过程中,不可避免需要使用很多外部的元件及传感器,这些元器件也许是板载的,也许是板外的,但不管怎样,为其开发驱动程序都是必须的。每次都需要为这些元器件编写驱动程序。但每次重复编写调试很麻烦&#xff0…

外设驱动库开发笔记1:AD56xx系列DAC驱动

DAC在我们的项目中经常使用到,而使用最多的就是AD56xx系列,包括有单通道的AD5662、双通道的AD5623和AD5663、以及四通道的AD5624和AD5664等。出于方便复用的原因,我们设计并实现AD56xx系列DAC的驱动。 1、功能概述 AD56xx系列DAC属于nanoDA…

外设驱动库开发笔记2:AD8400系列数字电位器驱动

一些时候我们需要在系统使用过程中改变某些电路电阻值以达到改变设定的目的,这时候我们就会使用电位器。在我们使用数字控制电路时多选择数字电位器。在这一篇我们就来设计AD8400系列数字电位器的驱动。 1、功能概述 AD8400/AD8402/AD8403分别是单通道/双通道/四通…

外设驱动库开发笔记3:AD527x系列数字电位器驱动

在一些时候我们需要使用精度更高的数字电位器来实现我们的应用。我们经常使用AD527x系列数字电位器来实现这类应用。在通常情况下,AD527x系列数字电位器完全能够满足要求。为了减少重复工作,在这里我们将分系并实现AD527x系列数字电位器的驱动。 1、功能…

PID控制器改进笔记之一:改进PID控制器之参数动态调整

前面我们发布了一系列PID控制器相关的文章,包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能,也在实际使用中取得了良好效果,但还有很多的细节部分可以改进以提高性能和灵活性。所以在这篇中我们来讨论改进…

外设驱动库开发笔记4:AD9833函数发生器驱动

很多时候我们需要输出某种函数信号,如方波、三角波、正弦波等,但想要获得这样的函数信号,不论是硬件电路还是软件实现,却并不是一件简单的事情。不过AD9833这类函数生成芯片可以简化这方面的操作,这一节我们就来设计并…

PID控制器改进笔记之二:改进PID控制器之手自动切换

前面我们发布了一系列PID控制器相关的文章,包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能,也在实际使用中取得了良好效果,但还有很多的细节部分可以改进以提高性能和灵活性。所以在这篇中我们来讨论改进…

外设驱动库开发笔记5:AD7705系列ADC驱动

我们的经常需要采集一些精度要求较高的模拟信号,使用MCU集成的ADC难以达到要求、所以我们需要独立的ADC芯片。这一节我们就来设计并实现AD7705芯片的驱动、并探讨驱动的使用方法。 1、功能概述 AD7705/AD7706是用于低频测量的完整模拟前端。可以直接从传感器接收低…

PID控制器改进笔记之三:改进PID控制器之正反作用

前面我们发布了一系列PID控制器相关的文章,包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能,也在实际使用中取得了良好效果,但还有很多的细节部分可以改进以提高性能和灵活性。所以在这篇中我们来讨论改进…

PID控制器改进笔记之四:改进PID控制器之设定值响应

前面我们发布了一系列PID控制器相关的文章,包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能,也在实际使用中取得了良好效果,但还有很多的细节部分可以改进以提高性能和灵活性。所以在这篇中我们来讨论改进…

PID控制器改进笔记之五:改进PID控制器之串级设定

前面我们发布了一系列PID控制器相关的文章,包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能,也在实际使用中取得了良好效果,但还有很多的细节部分可以改进以提高性能和灵活性。所以在这篇中我们来讨论改进…

滤波器开发之一:基于算数平均的平滑滤波器

信号采集是非常常见的需求,我们也总是希望采集到的数据是纯净而真实的,但这只是我们的希望。环境中存在太多的干扰信号,为了让我们得到的数据尽可能地接近实际值,我们需要降低这些干扰信号的影响,于是就有了滤波器的用…

外设驱动库开发笔记6:AD719x系列ADC驱动

前面我们讨论了AD7705这种ADC器件的驱动开发,在实际中我们使用更多的是AD719x系列的ADC芯片、包括有AD7191、AD7192和AD7193等。接下来我们就来设计并开发AD719x的驱动程序。 1、功能概述 AD7192是一款适合高精密测量应用的低噪声完整模拟前端,内置一个…