当前位置: 首页 > news >正文

【正点原子FPGA连载】 第三十三章基于lwip的tftp server实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

第三十三章基于lwip的tftp server实验

文件传输是网络环境中的一项基本应用,其作用是将一台电子设备中的文件传输到另一台可能相距很远的电子设备中。TFTP作为TCP/IP协议族中的一个用来在客户机与服务器之间进行文件传输的协议,常用于无盘工作站、路由器以及远程测控设备从主机上获取引导配置文件,实现远程升级。由于TFTP简单且易实现,本实验我们使用lwip协议栈实现TFTP Server的功能。本章包括以下几个部分:
3333.1简介
33.2实验任务
33.3硬件设计
33.4软件设计
33.5下载验证

33.1简介

一、TFTP简介(基于RFC1350版本)
简单文件传输协议TFTP (Trivial File Transfer Protocol) 是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输,基于UDP实现的应用层协议,提供不复杂、开销不大的文件传输服务,端口号为 69。为了保证文件可靠传输TFTP有自己的差错改正措施。TFTP 只支持文件传输、不支持交互、没有庞大的命令集,也没有目录列表功能,以及不能对用户进行身份鉴别。
与常用的文件传送协议 FTP (File Transfer Protocol) 相比,FTP基于TCP协议,提供交互式的访问,允许客户指明文件的类型与格式、允许执行对目录和文件的访问,并且可以完成特定类型的目录操作以及需要进行身份验证。
可以说FTP是完整的、面向会话、常规用途的文件传输协议,而TFTP相当于用作特殊目的简化版的FTP。
TFTP的主要优点有两个。
第一,TFTP可用于UDP环境。例如,当需要将程序或文件同时向许多机器下载时就往往需要使用TFTP。
第二,TFTP代码所占的内存较小。这对较小的计算机或某些特殊用途的设备(如无盘工作站等)是很重要的。这些设备不需要硬盘,只需要固化了TFTP、UDP和IP的小容量只读存储器即可。当接通电源后,设备执行只读存储器中的代码,在网络上广播一个TFTP请求。网络上的TFTP服务器就发送响应,其中包括可执行二进制程序。设备收到此文件后将其放入内存,然后开始运行程序。这种方式增加了灵活性,也减少了开销。
TFTP的主要特点如下:
(1)每次传送的数据报文中有512字节的数据,但最后一次可不足512字节。
(2)数据报文按序编号,从1开始。
(3)支持ASCII码或二进制传送。
(4)可对文件进行读或写。
(5)使用很简单的首部。
(6)实现简单而不是高的系统吞吐量
二、TFTP的五种报文
TFTP的报文格式如图 34.1.1所示,可以看到TFTP有五种报文,每种报文有不同的操作码,这五种报文分别是:RRQ、WRQ、DATA、ACK和ERROR报文。下面我们简单的介绍下这五种报文。
RRQ/WRQ报文
模式字段中,包含两种字符串中的一种,"netascii"表示ASCII文件,"octet"表示二进制文件。对于RRQ,客户向TFTP服务器发送读请求后,服务器返回一个块编号为1的DATA报文。而对于WRQ,客户向TFTP服务器发送写请求后,服务器返回的是块编号为1的ACK报文。总之,不管是RRQ还是WRQ,接收DATA数据的一方发送ACK确认,而发送DATA数据的一方只负责发送数据。
在这里插入图片描述

图 34.1.1 TFTP报文格式
a)DATA报文
发送方用于传送数据块。所有的块都用数字顺序编码,从1开始。在所有的DATA报文中,这个块必须准确地等于512Byte,但最后一个块可以小于或等于512Byte。当发送的DATA报文中数据部分的长度小于512Byte,表示DATA报文发送完毕,所以小于数据部分512Byte的DATA数据报可以作为文件结束的标志。特殊的情况是,当文件中的数据正好是512Byte的整数倍时,那么发送端必须再发送一个具有数据部分为0Byte的额外的DATA数据块以表示传输的结束。数据可以采用ASCII码或二进制来传送。
b)ACK报文
块号表示它所收到的块号(不是下一个期待的块号,这与TCP中的ACK序号不同)。特殊情况是,当客户向服务器发送一个WRQ请求后,服务器返回给客户的是一个块号为0的ACK报文,表示服务器已经准备好了接收来自客户的数据报。
c)EEROR报文(差错报文)
ERROR报文既可以由客户发送,也可以由服务器发送,当一条连接(如读连接或写连接)不能建立或在数据传输中出现问题时使用。差错码定义了差错的类型,差错信息是一个可变字节,包含原文中的差错数据。
从上面的报文格式中可以看出,TFTP报文没有差错检验和字段,所以接收端检验数据是否出现差错的唯一方法是通过该TFTP数据报的UDP首部中的检验和字段。
三、TFTP传输过程
以TFTP客户端向 TFTP 服务器发送写请求为例,说明整个过程。
1)服务器使用默认端口号69被动打开连接;
2)客户主动打开连接,向服务器进程发送WRQ报文,报文中包含写入文件的文件名;
3)TFTP服务器进程选择一个新的端口和TFTP客户进程进行通信,并向TFTP客户进程发送块编号为0的的ACK报文;
4)客户端收到服务器的ACK报文后发送DATA报文,数据段为512Byte,少于512Byte表明是文件的最后的数据,块编号逐次递增;
5)TFTP服务器校验收到的DATA报文的块编号,如果校验正确则将数据写入文件,并发送ACK报文表明已接收到数据,ACK报文的块编号为本次接收的DATA报文的块编号。另外还判断数据段长度是否小于512 Byte,小于则表明文件传输完成,关闭连接,如果等于512Byte,则重复步骤4-5,直到所有请求的数据发送完毕。
从上面的传输过程可以看出,TFTP 是一种类似于停止等待协议(不是真正的停止等待协议,在停止等待协议中,接收方发送的 ack 表示期望收到的下一个分组,而在 TFTP 的 ACK 报文中,ACK的块号表示的是本次成功收到的数据块,而不是下一个期望的下一个数据块)。TFTP 客户端只有收到服务器的确认报文ACK后才会接着向服务器发送新的数据。
另外需要注意的是TFTP 协议中,用于读文件的连接和用于写文件的连接的建立方式不同:建立读连接的时候,客户首先向服务器发送 RRQ 读报文,服务器收到该报文后,直接发回给该客户 DATA 报文,并且包含第一个数据块(块号为 1)。而建立写连接的时候,客户首先先服务器发送 WRQ 写报文,服务器收到该报文后,则发回给客户 ACK 报文,使用的块号为 0;当然上面两种情况如果遇到请求报文出错时,均会发回 ERROR 报文作为响应。
33.2实验任务
本章的实验任务是使用LWIP协议栈搭建TFTP服务器,PC电脑上的客户端可以从TFTP服务器读取文件也可向TFTP服务器写入文件,文件存放在SD卡中。
33.3硬件设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示:
在这里插入图片描述

图 34.3.1 系统框图
在图 34.3.1中,UART用于打印程序相关的信息,LWIP通过以太网传输数据,TF卡用于存放文件,包括服务器创建的文件和客户端写入的文件。
step1:创建Vivado工程
本次实验的硬件设计可以在《lwip echo server》实验的基础上添加SD卡。
1-1 我们先打开《lwip echo server》实验的Vivado工程,打开后将工程另存为“lwip_tftp_server”工程,然后点击“OK”按钮。
step2:使用IP Integrator创建Processing System
2-1 在Vivado界面左侧的Flow Navigator中,点击IP INTEGRATOR下的Open Block Design以打开Diagram窗口。
2-2 在打开的下图Diagram窗口,双击打开Zynq UltraScale+ MPSOC重定义窗口。
在这里插入图片描述

图 34.3.2 重定义Zynq UltraScale+ MPSOC
2-3 在下图所示的重定义窗口,如同《SD卡读写TXT文本实验》那样配置SD卡。点击左侧的I/O Configuration,在右侧的界面中找到SD卡控制器配置选项,并勾选“SD1”,Slot Type选择SD2.0,然后选择MIO46…51,并勾选CD选项,然后点击“OK”,如下图所示。
在这里插入图片描述

图 34.3.3 SD卡接口配置界面
2-4 由于不需要添加其它IP,点击Validate Design验证成功后,按Ctrl+S快捷键保存Diagram。此时我们的第二步完成,进入第三步
step3:生成顶层HDL
在sources面板中,右键点击Block Design设计文件“design_1.bd”,然后执行“Generate Output Products”。
step4:生成Bitstream文件并导出到VITIS
由于本实验未用到PL部分,所以无需生成Bitstream文件,只需导出Hardware硬件平台文件即可。如果使用到PL,则需要添加引脚约束以及对该系统进行综合、实现并生成Bitstream文件。
4-1 导出硬件。
在菜单栏中选择 File > Export > Export hardware。注意导出路径的选择并取消勾选“Include bitstream”,然后点击“OK”按钮。
新建vitis文件夹,将导出的平台文件移动到该文件夹下。
4-2 硬件导出完成后,选择菜单Tools->Launch Vitis,指定工作空间,启动Vitis开发环境。
33.4软件设计
下面我们开始第五步——创建应用工程。
step5:在Vitis中创建应用工程
5-1在菜单栏中选择“File->New->Application Project”,
在弹出的界面中,输入工程名“lwip_tftp_server”,点击“Next >”,添加应用平台文件,添加完成后,接下来依次点击“Next>”,直到弹出选择模板界面,选择“Empty Application”空应用工程,然后点击“Finish”按钮。
5-2 设置板级支持包(BSP)。
打开板级支持包设置界面,具体步骤可参考前面的实验。在弹出的BSP设置界面,勾选“lwip212”和“xilffs”以启用lwip和文件系统,如图 34.4.1所示。
如果没有开启DHCP服务可以开启DHCP服务,点击standalone下的lwip212,在右侧界面中到“dhcp_options”,将其下的两个选项的“Value”设置为“true”,如图 34.4.2所示。
在这里插入图片描述

图 34.4.1 BSP的设置界面
在这里插入图片描述

图 34.4.2 开启DHCP
5-3 由于Xilinx提供的lwip例程里有tftp server的源代码,所以我们无需自己手动编写,直接添加即可。
双击硬件平台工程design_1_wrapper下的platform.spr,在打开的界面中,单击standalone on psu_cortexa53_0下的Board Support Package,然后展开Libraries。可以看到Libraries标题下有lwip211和xilffs两个库,单击lwip211库后的“Import Example”选项,如下图所示。
在这里插入图片描述

图 34.4.3 Import lwip Example
5-4 在弹出的下图所示界面中,点击下方的“Examples Directory”。
在这里插入图片描述

图 34.4.4打开例程文件目录
5-5 打开例程所在文件的目录,里面有Xilinx关于lwip的全部例程源文件。我们选择本次实验需要的源文件,如图 34.4.5所示,并单击鼠标右键选择复制。复制完成后,关闭该目录,并在打开的图 34.4.4界面中,点击“Cancel”退出。
在这里插入图片描述

图 34.4.5 例程所在文件的目录
5-6 单击Vitis软件的lwip_tftp_server/src目录,按下粘贴快捷键“Ctrl-v”,将复制的文件粘贴到该src目录下,如下图所示。
在这里插入图片描述

图 34.4.6 src目录
5-7 为了方便分析,我们将刚才复制到src目录的源文件重命名,主要是删除不需要的前缀,其中“lwip_example_tftpserver_common.h”改为“lwip_tftp_server.h”,然后进行编译,如下图所示:
在这里插入图片描述

图 34.4.7 删除不相关前缀后的src文件夹内容
5-8 现在我们打开main.c文件,为了方便分析源代码,在main.c文件中将带有下图箭头所指的预编译指令删除。
在这里插入图片描述

图 34.4.8 删除不需要的预编译指令
删除不适用的预编译指令后的main.c代码与我们《lwip echo server实验》的main.c代码基本相同,区别在于本次TFTP server实验没有使用IPv6,所以没有IPv6的预编译指令,其他完全相同,main.c代码讲解见《lwip echo server实验》。
5-9本实验可以说是在《lwip echo server实验》的基础上增加了文件系统,然后将Echo server的实现文件echo.c文件改写成了TFTP Server的实现文件。因而本实验的主要代码是TFTP Server的实现,该实现在lwip_tftp_server.h和lwip_tftp_server.c中,由于这两个文件的总代码有500多行,因此我们挑选部分代码进行讲解。此处以客户端写文件为例讲解lwip_tftp_server.c中的写文件实现源码。讲解以函数调用顺序进行。
首先我们看main函数中调用的start_application函数,该函数实现如下:

352 void start_application()
353 {
354     struct udp_pcb *pcb;
355     err_t err;
356 
357     //创建测试文件用于客户端读取
358     err = tftp_create_test_file();
359     if (err) {
360         xil_printf("Unable to create test file\r\n");
361         return;
362     }
363 
364     //创建新的UDP PCB
365     pcb = udp_new();
366     if (!pcb) {
367         xil_printf("Error creating PCB. Out of Memory\r\n");
368         return;
369     }
370 
371     //绑定端口
372     err = udp_bind(pcb, IP_ADDR_ANY, TFTP_PORT);
373     if (err != ERR_OK) {
374         xil_printf("Unable to bind to port %d; err %d\r\n",
375                 TFTP_PORT, err);
376         udp_remove(pcb);
377         return;
378     }
379 
380     //设置接收回调函数
381     udp_recv(pcb, (udp_recv_fn) tftp_server_recv_cb, NULL);
382 }

可以看到该函数首先通过调用tftp_create_test_file函数创建了测试文件,用于tftp客户端读取tftp服务器的文件数据,测试文件名为sample#.txt,其中“#”为数字1、2、3中的任一值,其文件内容为“----- This is a test file for TFTP server application -----”。如果不执行客户端的读取文件请求,可删除该函数的调用及其实现。
由于TFTP基于UDP协议,从start_application函数可以看到lwip中使用UDP协议很简单。首先通过udp_new函数创建一个新的UDP PCB,然后调用udp_bind函数绑定端口号,IP_ADDR_ANY表明为任意本地地址,TFTP_PORT是在lwip_tftp_server.h宏定义的端口号,其值为69,即TFTP的默认端口。最后调用udp_recv函数设置接收回调函数就完成了UDP服务的创建,服务端的功能即TFTP协议由回调函数实现。回调函数代码如下:

270 static void tftp_server_recv_cb(void *arg, struct udp_pcb *upcb, struct pbuf *p,
271         ip_addr_t *ip, u16_t port)
272 {
273     tftp_opcode op = tftp_get_opcode(p->payload);
274     char fname[512];
275     struct udp_pcb *pcb;
276     err_t err;
277 
278     pcb = udp_new();
279     if (!pcb) {
280         xil_printf("Error creating PCB. Out of Memory\r\n");
281         goto cleanup;
282     }
283 
284     //绑定到端口0以接收下一个可用的空闲端口
285     err = udp_bind(pcb, IP_ADDR_ANY, 0);
286     if (err != ERR_OK) {
287         xil_printf("Unable to bind to port %d; err %d\r\n", port, err);
288         goto cleanup;
289     }
290 
291     switch (op) {
292     case TFTP_RRQ:
293         //从payload中获取文件名
294         strcpy(fname, p->payload + FIL_NAME_OFFSET);
295         printf("TFTP RRQ (read request): %s\r\n", fname);
296         tftp_process_read(pcb, ip, port, fname);
297         break;
298     case TFTP_WRQ:
299         //从payload中获取文件名
300         strcpy(fname, p->payload + FIL_NAME_OFFSET);
301         printf("TFTP WRQ (write request): %s\r\n", fname);
302         tftp_process_write(pcb, ip, port, fname);
303         break;
304     default:
305         //发送访问冲突消息
306         tftp_send_error_packet(pcb, ip, port, TFTP_ERR_ILLEGALOP);
307         printf("TFTP unknown request op: %d\r\n\r\n", op);
308         udp_remove(pcb);
309         break;
310     }
311 
312 cleanup:
313     pbuf_free(p);
314 }

当TFTP客户端发起写入或读取文件的请求后,lwip协议栈调用回调函数tftp_server_recv_cb。该回调函数通过tftp_get_opcode宏获取客户端发送报文的操作码,不同的操作码执行该函数switch分支中的不同的case,如对于写入文件请求,则执行“case TFTP_WRQ”分支语句,该分支语句调用TFTP处理写文件请求函数tftp_process_write,该函数实现如下:

233 //TFTP 处理写文件请求
234 static int tftp_process_write(struct udp_pcb *pcb, ip_addr_t *ip, int port,
235         char *fname)
236 {
237     tftp_connection_args *conn;
238     FIL w_fil;
239     FRESULT Res;
240 
241     Res = f_open(&w_fil, fname, FA_CREATE_ALWAYS | FA_WRITE);
242     if (Res) {
243         xil_printf("Unable to open file %s for writing %d\r\n", fname,
244                Res);
245         tftp_send_error_packet(pcb, ip, port, TFTP_ERR_DISKFULL);
246         udp_remove(pcb);
247         return -1;
248     }
249 
250     conn = mem_malloc(sizeof *conn);
251     if (!conn) {
252         xil_printf("Unable to allocate memory for tftp conn\r\n");
253         tftp_send_error_packet(pcb, ip, port, TFTP_ERR_DISKFULL);
254         udp_remove(pcb);
255         return -1;
256     }
257 
258     memcpy(&conn->fil, &w_fil, sizeof(w_fil));
259     conn->block = 0;
260 
261     //为该pcb设置接收回调
262     udp_recv(pcb, (udp_recv_fn) tftp_server_write_req_recv_cb, conn);
263 
264     //通过发送第一个ACK来启动传输
265     tftp_send_ack_packet(pcb, ip, port, conn->block);
266 
267     return 0;
268 }

该函数首先在文件系统中创建一个文件,文件名为客户端写入的文件名,然后为新创建的UDP PCB设置接收回调函数,用于处理后面接收客户端传入的文件,最后发送块编号为0的ACK报文以应答客户端启动传输。TFTP写入请求的接收回调函数实现如下:

187 //TFTP 写入请求的接收回调函数
188 static void tftp_server_write_req_recv_cb(void *_args, struct udp_pcb *upcb,
189         struct pbuf *p, ip_addr_t *addr, u16_t port)
190 {
191     ip_addr_t ip = *addr;
192     tftp_connection_args *args = (tftp_connection_args *)_args;
193 
194     if (p->len != p->tot_len) {
195         xil_printf("TFTP_WRQ: Tftp server does not support "
196                 "chained pbufs\r\n");
197         pbuf_free(p);
198         return;
199     }
200 
201     //确保数据块是我们所期望的
202     if ((p->len >= TFTP_PACKET_HDR_LEN) &&
203         (tftp_get_block_value(p->payload) ==
204          (u16_t) (args->block + 1))) {
205 
206         //将接收的数据写入文件
207         unsigned int n;
208         f_write(&args->fil, p->payload + TFTP_PACKET_HDR_LEN,
209                 p->len - TFTP_PACKET_HDR_LEN, &n);
210         if (n != p->len - TFTP_PACKET_HDR_LEN) {
211             xil_printf("TFTP_WRQ: Write to file error\r\n");
212             tftp_send_error_packet(upcb, &ip, port,
213                         TFTP_ERR_DISKFULL);
214             pbuf_free(p);
215             return tftp_cleanup(upcb, args);
216         }
217         args->block++;
218     }
219 
220     tftp_send_ack_packet(upcb, &ip, port, args->block);
221 
222     
223      
224      //如果接收到的数据段长度小于指定的字节数,则表明已经接收了整个文件,因此可以退出
225     if (p->len < TFTP_DATA_PACKET_MSG_LEN) {
226         xil_printf("TFTP_WRQ: Transfer completed\r\n\r\n");
227         return tftp_cleanup(upcb, args);
228     }
229 
230     pbuf_free(p);
231 }

从该回调函数可以看到,TFTP服务端对客户端发送的数据报文的块编号进行校验,如果不是我们期望的块编号就重发上一次发送的ACK报文,如果是期望的块编号,就将数据写入文件中,然后递增块编号,并发送ACK报文给客户端以确认收到数据。
在该函数的最后判断接收到的数据段长度是否小于指定的字节数TFTP_DATA_PACKET_MSG_LEN,如果是,则表明已经接收了整个文件,因此可以结束连接。TFTP_DATA_PACKET_MSG_LEN在lwip_tftp_server.h宏定义为512。
以上大概的讲解了TFTP Server接收客户端写入文件的实现。下面我们进行实际操作,看看TFTP客户端是否能向服务器写入文件。
33.5下载验证
首先我们将下载器与MPSOC开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB UART接口与电脑连接,用于串口通信。使用网线一端连接MPSOC开发板的以太网接口,另一端与电脑或路由器连接。连接完成后,在开发板上插入TF 卡(SD 卡插槽位于开发板背面)。最后连接开发板的电源,给开发板上电。如下图所示:
在这里插入图片描述

图 34.5.1 MPSOC开发板实物图
现在进入最后一步。
step6:板级验证
6-1 在Vitis软件的下方的Vitis Serial Terminal窗口中点击右上角的加号连接串口。
6-2 下载程序。下载完成后,可以看到串口打印的结果如下:
在这里插入图片描述

图 34.5.2 显示打印结果
其中“File system initialization successful”表明SD卡可以正常工作。打印的最后一句表明了该实验如何使用。由于是TFTP服务器实验,所以我们需要TFTP客户端,可以从网上下载,也可以使用Windows系统的CMD命令行界面,如果开启了TFTP客户端,开启方法见步骤6-5。
6-3 下面我们先创建一个文件用来传输到TFTP服务器。文件存放位置任意,文件内容任意。
我们在Vivado工程目录新建一个名为“test”的文件夹,里面新建一个名为testfile.txt的文件,文件内容为“这只是一个测试文件。”,如下图所示:
在这里插入图片描述

图 34.5.3 新建一个名为test_file.txt的文件
6-4 我们打开电脑的CMD(按win+r键后输入cmd),然后输入命令“cd /D F:\ZYNQ\Embedded_System\lwip_tftp_server\test”切换到 “F:\ZYNQ\Embedded_System\lwip_tftp_server\test”目录下,如下图所示:
在这里插入图片描述

图 34.5.4 切换到上传文件所在的目录
然后输入“tftp -i 192.168.1.10 PUT testfile.txt”命令,回车,会显示传输成功字样,如下图所示:
在这里插入图片描述

图 34.5.5 进行tftp连接
此时VITIS串口终端也会打印如下信息:
在这里插入图片描述

图 34.5.6 串口终端打印写入完成信息
如果回车后出现像下图所示界面所示“tftp不是内部或外部命令,也不是可运行的程序或批处理文件”,则表明未开启Windows的tftp客户端功能,开启方式见6-5。
在这里插入图片描述

图 34.5.7 未启用tftp客户端时的界面
如果回车后出现连接请求失败或者串口终端打印“Error creating PCB.Out of Memory”信息时,检查防火墙是否全部关闭。
向服务器写入文件刚才测试完成了,现在测试从服务器端读取文件,可以读取刚才写入的文件,也可以读取服务器程序创建的测试文件。下面我们以读取服务器程序创建的测试文件为例,进行读取文件测试。
在CMD中输入“tftp -i 192.168.1.10 GET sample1.txt”命令,然后回车,会显示传输成功字样,如下图所示:
在这里插入图片描述

图 34.5.8 输入读取文件命令
此时VITIS串口终端也会打印如下信息:
在这里插入图片描述

图 34.5.9 读取成功
此时我们打开test文件夹,会看到其中新增了sample1.txt,双击打开,其内容如下:
在这里插入图片描述

图 34.5.10 读取的sample1.txt文件
可以看到读取文件测试成功。现在我们把SD卡插到电脑上,查看其内容如下:
在这里插入图片描述

图 34.5.11 SD卡上的文件
可以看到客户端上传给TFTP服务器的文件确实写到SD卡中。
6-5 下面我们介绍一下如何开启Windows的tftp客户端功能。在Win10或Win7系统中,按“Win+r”快捷键后,在下图所示界面中输入“control”。
在这里插入图片描述

图 34.5.12 打开控制面板界面
进入下图所示控制面板界面,将查看方式设置为“类别”,单击“程序”下的“卸载程序”,如下图所示:
在这里插入图片描述

图 34.5.13 点击进入“程序和功能”界面
在弹出的界面中,单击“启用或关闭Windows功能”,如下图所示:
在这里插入图片描述

图 34.5.14 点击“启用或关闭Windows功能”
在弹出的“Windows功能”界面中,找到“Tftp Client”,并勾选,如下图所示:
在这里插入图片描述

图 34.5.15 勾选tftp client
单击确定后,如果出现“Windows需要重启电脑才能完成安装所请求的更改”字样,重新启动电脑即可。现在 Windows的tftp客户端服务已启用。
至此,本实验完成。

相关文章:

【正点原子FPGA连载】 第三十三章基于lwip的tftp server实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

第三十三章基于lwip的tftp server实验 文件传输是网络环境中的一项基本应用&#xff0c;其作用是将一台电子设备中的文件传输到另一台可能相距很远的电子设备中。TFTP作为TCP/IP协议族中的一个用来在客户机与服务器之间进行文件传输的协议&#xff0c;常用于无盘工作站、路由器…...

蓝桥冲刺31天之316

如果生活突然向你发难 躲不过那就迎面而战 所谓无坚不摧 是能享受最好的&#xff0c;也能承受最坏的 大不了逢山开路&#xff0c;遇水搭桥 若你决定灿烂&#xff0c;山无遮&#xff0c;海无拦 A&#xff1a;特殊日期 问题描述 对于一个日期&#xff0c;我们可以计算出年份的各个…...

说一个通俗易懂的PLC工程师岗位要求

你到了一家新的单位&#xff0c;人家接了一套新的设备&#xff0c;在了解设备工艺流程之后&#xff0c;你就能决定用什么电气元件&#xff0c;至少95%以上电气原件不论你用过没用过都有把握拍板使用&#xff0c;剩下5%&#xff0c;3%你可以先买来做实验&#xff0c;这次不能用&…...

今年还能学java么?

“Java很卷”、“大家不要再卷Java了”&#xff0c;经常听到同学这样抱怨。但同时&#xff0c;Java的高薪也在吸引越来越多的同学。不少同学开始疑惑&#xff1a;既然Java这么卷&#xff0c;还值得我入行吗&#xff1f; 首先先给你吃一颗定心丸&#xff1a;现在选择Java依然有…...

ajax学习1

不刷新页面的情况下&#xff0c;向服务端发送请求&#xff0c;异步的js和XMLajax不是新的编程语言&#xff0c;只是把现有标准组合到一起使用的新方式...

一题多解-八数码(万字长文)

16 张炜皓 (ζ͡顾念̶) LV 5 1 周前 在做这道题前&#xff0c;先来认识一下deque双端队列 C STL 中的双端队列 题目连接 使用前需要先引入 头文件。 #include; STL 中对 deque 的定义 // clang-format off template< class T, class Allocator std::allocator class d…...

九种跨域方式实现原理(完整版)

前言 前后端数据交互经常会碰到请求跨域&#xff0c;什么是跨域&#xff0c;以及有哪几种跨域方式&#xff0c;这是本文要探讨的内容。 一、什么是跨域&#xff1f; 1.什么是同源策略及其限制内容&#xff1f; 同源策略是一种约定&#xff0c;它是浏览器最核心也最基本的安…...

fighting

目录Mysqlgroup by和 distinct哪个性能好java觉得Optional类怎么样isEmpty和isBlank的用法区别使用大对象时需要注意什么内存溢出和内存泄漏的区别及详解SpringResource和Autowired的起源既生“Resource”&#xff0c;何生“Autowired”使用Autowired时为什么Idea会曝出黄色警告…...

网络安全日志监控管理

内部安全的重要性 无论大小&#xff0c;每个拥有IT基础设施的组织都容易发生内部安全攻击。您的损失等同于黑客的收益&#xff1a;访问机密数据、滥用检索到的信息、系统崩溃&#xff0c;以及其他等等。专注于网络外部的入侵是明智的&#xff0c;但同时&#xff0c;内部安全也…...

线程池的使用:如何写出高效的多线程程序?

目录1.线程池的使用2.编写高效的多线程程序Java提供了Executor框架来支持线程池的实现&#xff0c;通过Executor框架&#xff0c;可以快速地创建和管理线程池&#xff0c;从而更加方便地编写多线程程序。 1.线程池的使用 在使用线程池时&#xff0c;需要注意以下几点&#xff…...

React 架构流程概览

React 架构流程概览 文章目录React 架构流程概览启动React项目流程分析各部分解析调度器协调器渲染器总结启动React项目 启动项目&#xff0c;并打开 Performance 面板 流程分析 首先找到入口函数 整个 render 下面的调用栈就是首屏渲染要执行的流程。 render 过程大致分为…...

【Linux】进程管理之kill、killall、pkill

一、kill 命令 Linux 中的 kill 命令用来终止指定的进程的运行&#xff0c;是 Linux 下进程管理的常用命令。通常&#xff0c;终止一个前台进程可以使用 CtrlC 键&#xff0c;但是&#xff0c;对于一个后台进程就须用 kill 命令来终止&#xff0c;那就需要先使用 ps、pidof、ps…...

LSTM从入门到精通(形象的图解,详细的代码和注释,完美的数学推导过程)

先附上这篇文章的一个思维导图什么是RNN按照八股文来说&#xff1a;RNN实际上就是一个带有记忆的时间序列的预测模型RNN的细胞结构图如下&#xff1a;softmax激活函数只是我举的一个例子&#xff0c;实际上得到y<t>也可以通过其他的激活函数得到其中a<t-1>代表t-1时…...

19.特殊工具与技术

文章目录特殊工具与技术19.1控制内存分配19.1.1重载new和deleteoperator new接口和operator delete接口malloc函数与free函数19.1.2定位new表达式显式的析构函数调用19.2运行时类型识别(run-time type identification, RTTI)19.2.1dynamic_cast运算符指针类型的dynamic_cast引用…...

518. 零钱兑换 II ——【Leetcode每日一题】

518. 零钱兑换 II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 3…...

django DRF请求访问频率限制

Django REST framework&#xff08;DRF&#xff09;提供了一个throttle_classes属性&#xff0c;可以用于限制API的访问频率。它可以防止恶意用户发送大量请求以消耗服务器资源。使用throttle_classes属性&#xff0c;需要在settings.py中配置REST_FRAMEWORK&#xff1a;REST_F…...

二分查找创新性总结

LeetCode题目 704.二分查找35.搜索插入位置69.x 的平方根367.有效的完全平方数34.在排序数组中查找元素的第一个和最后一个位置 二分查找适用范围 可随机访问的数据结构数据已经有序要查找的值只有一个 上述的前四题都可直接使用二分查找&#xff0c;第五题要求查找上限和下限&…...

Java Web 实战 13 - 多线程进阶之 synchronized 原理以及 JUC 问题

文章目录一 . synchronized 原理1.1 synchronized 使用的锁策略1.2 synchronized 是怎样自适应的? (锁膨胀 / 升级 的过程)1.3 synchronized 其他的优化操作锁消除锁粗化1.4 常见面试题二 . JUC (java.util.concurrent)2.1 Callable 接口2.2 ReentrantLock2.3 原子类2.4 线程池…...

【解决】elementui ——tooltip提示在循环中点击一个,同时显示多个的问题!

同时显示多个tooltip——效果图&#xff1a; 点击第一个二维码把循环el-card中所有的tooltip都触发了 解决后效果图&#xff1a; 只显示点击的当前tooltip 解决办法&#xff1a; 通过循环item中定义字段&#xff0c;进行控制tooltip显示隐藏 代码&#xff1a; 页面代码&am…...

SpringBoot-核心技术篇

技术掌握导图 六个大标题↓ 配置文件web开发数据访问单元测试指标指控原理解析 配置文件 1.文件类型 1.1、properties 同以前的properties用法 1.2、yaml 1.2.1、简介 YAML是 “YAML Aint Markup Language”&#xff08;YAML不是一种标记语言&#xff09;的递归缩写。在…...

2023还有人不知道kubernetes?| 初步理解kubernetes

文章目录Kubernetes(K8s)一、Openstack&VM1、**认识虚拟化****1.1**、什么是虚拟化**1.2、虚拟化分类**2、OpenStack与KVM、VMWare2.1、OpenStack2.2、KVM2.3、VMWare二、容器&编排技术1、容器发展史1.1、Chroot1.2、FreeBSD Jails1.3、Solaris Zones1.4、LXC1.5、Dock…...

Docker 环境搭建

RabbitMq 安装与启动安装&#xff1a;运行命令&#xff1a;docker pull rabbitmq 默认版本是&#xff1a;latest启动rabbitmq&#xff1a;运行命令&#xff1a;docker run \ # 运行-e RABBITMQ_DETAULT_USERroot \ # 设置用户名-e RABBITMQ_DETAULT_PASS123456 \ # 设置 密码--…...

css实现炫酷充电动画

先绘制一个电池&#xff0c;电池头部和电池的身体 这里其实就是两个div&#xff0c;使用z-index改变层级&#xff0c;电池的身体盖住头部&#xff0c;圆角使用border-radius完成 html部分,完整的css部分在最后 <div class"chargerBox"><div class"ch…...

【Effective C++详细总结】第二章 构造/析构/赋值运算

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4da;专栏地址&#xff1a;C/C知识点 &#x1f4e3;专栏定位&#xff1a;整理一下 C 相关的知识点&#xff0c;供大家学习参考~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;…...

webpack基础

webpack基础 webpack基础目录webpack基础前言Webpack 是什么&#xff1f;Webpack 有什么用&#xff1f;一、webpack的基本使用webpack如何使用文件和文件夹创建创建文件下载依赖二、基本配置5 大核心概念准备 Webpack 配置文件修改配置文件处理样式资源处理图片资源修改输出资源…...

jQuery《一篇搞定》

今日内容 一、JQuery 零、 复习昨日 1 写出至少15个标签 2 写出至少7个css属性font-size,color,font-familytext-algin,background-color,background-image,background-sizewidth,heighttop,bottom ,left ,rightpositionfloatbordermarginpadding 3 写出input标签的type的不…...

Spring Cloud学习笔记【负载均衡-Ribbon】

文章目录什么是Spring Cloud RibbonLB&#xff08;负载均衡&#xff09;是什么Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别&#xff1f;Ribbon架构工作流程Ribbon Demo搭建IRule规则Ribbon负载均衡轮询算法的原理配置自定义IRule新建MyRuleConfig配置类启动类添加Rib…...

第九章:C语言数据结构与算法初阶之堆

系列文章目录 文章目录系列文章目录前言一、堆的定义二、堆的实现三、堆的接口函数1、初始化2、销毁3、插入4、删除5、判空6、元素个数四、堆排序1、建堆2、排序五、堆的应用——TOPK1、什么是TOPK问题&#xff1f;2、解决方法总结前言 堆就是完全二叉树。 一、堆的定义 我们…...

Mysql架构初识

&#x1f972; &#x1f978; &#x1f90c; &#x1fac0; &#x1fac1; &#x1f977; &#x1f43b;‍❄️&#x1f9a4; &#x1fab6; &#x1f9ad; &#x1fab2; &#x1fab3; &#x1fab0; &#x1fab1; &#x1fab4; &#x1fad0; &#x1fad2; &#x1fad1;…...

字符串函数和内存函数

&#x1f355;博客主页&#xff1a;️自信不孤单 &#x1f36c;文章专栏&#xff1a;C语言 &#x1f35a;代码仓库&#xff1a;破浪晓梦 &#x1f36d;欢迎关注&#xff1a;欢迎大家点赞收藏关注 字符串函数和内存函数 文章目录字符串函数和内存函数前言1. 字符串函数介绍1.1 s…...

facebook做网站/seo关键词排名价格

第一部分&#xff1a;介绍hadoop集群架构 此部分共分为如下两部分&#xff1a; 第一章&#xff1a;介绍hadoop及其环境介始 欢迎来到管理hadoop的课程。hadoop在处理大数据处于领先地,被称为大数据hadoop。hadoop是一个开源的项目&#xff0c;大约在2005年推出。hadoop已成为…...

做a免费网站有哪些/免费推广软件工具

生物信息很多时候要爬数据。最近也看了一些这些方面的。 url<-"要爬取的网址" url.html<-htmlParse(url,encoding"UTF-8") 如果要获得部分信息&#xff0c;则使用XPath方法。 xpath<-"//*[id填写目标id]/span[id细分标签的id] 目标id.node&l…...

上海有哪几家做新房的网站/谷歌网页版登录入口

随着互联网的发展&#xff0c;越来越多的企业和个人想要制作自己的H5网站。你真的了解它吗&#xff1f;又该如何制作呢&#xff1f;下面就跟大家解答一下关于h5网页的相关问题。1.H5到底是什么&#xff1f;通俗地讲&#xff0c;H5是一个网页&#xff0c;就像一个很大的容器&…...

水果电商网站建设相关文献/搜索引擎优化服务公司哪家好

React是个啥 React 是一个用于构建用户界面的 JAVASCRIPT 库。 React主要用于构建UI&#xff0c;很多人认为 React 是 MVC 中的 V&#xff08;视图&#xff09;。 React 起源于 Facebook 的内部项目&#xff0c;用来架设 Instagram 的网站&#xff0c;并于 2013 年 5 月开源。 …...

网站推广员如何做/什么是论坛推广

队长链接&#xff1a;http://www.cnblogs.com/zhanghongjian/p/7608590.html html书写规范 1. 文档类型声明及编码: 统一为html5声明类型<!DOCTYPE html>; 编码统一为<meta charset”gbk” />, 书写时利用IDE实现层次分明的缩进; 2. 非特殊情况下样式文件必须外链至…...

上海网站怎么备案表/优书网

曾几何时&#xff0c;云计算在中国市场成为企业竞相追逐的热点。这其中既有财大气粗的互联网企业&#xff0c;例如BAT所属的阿里云、百度云、腾讯云&#xff0c;也有传统ICT企业所属的华为云、浪潮云、联想云等&#xff0c;此外还有专攻云计算的专业厂商&#xff0c;例如小鸟云…...