打印
[STM32F2]

STM32F207做TFTP客户端收不到TFTP服务器的数据问题

[复制链接]
5465|29
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
ST提供的例程做IAP时STM32是做tftp服务器的。因项目需要,我们需要STM32做客户端。
主要的程序如下:
    UDPpcb = udp_new();
    if (UDPpcb != NULL)
    {
        /*assign destination IP address */
        IP4_ADDR( &dest_ip, 192, 9, 50, 224 );
        /* configure destination IP address and port */
        err= udp_connect(UDPpcb, &dest_ip, 69);
        if (err == ERR_OK)
        {
            /* Set a receive callback for the upcb */
            udp_recv(UDPpcb, tftp_recv_callback, NULL);
            len = sprintf (data, "%c%c%s%c%s%c", 0x00, TFTP_RRQ, "app.bin", 0x00, "octet", 0x00);
            /* allocate pbuf from pool*/
            p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_POOL);
            if (p != NULL)
            {
                /* copy data to pbuf */
                pbuf_take(p, (char*)data, len);
                /* send udp data */
                udp_send(UDPpcb, p);
                /* free pbuf */
                pbuf_free(p);
            }
            else
            {
                printf("Can not allocate pbuf\r\n");
            }
        }
但在测试中发现tftp_recv_callback从来没被调用,导致服务器端收不到ACK而超时终止文件传输。服务器端的现象如图所示:

看了很久,还是看不出问题所在,求大家指点。
沙发
yklstudent| | 2015-12-23 16:24 | 只看该作者
服务器IP搞对了?

使用特权

评论回复
板凳
zhenxizhou|  楼主 | 2015-12-23 16:44 | 只看该作者
本帖最后由 zhenxizhou 于 2015-12-23 16:48 编辑

对的, 上面那图已显示收到客户端发来的读文件请求.

再附上中间过程的一张图.
STM32做服务器时需要先用69端口来侦听, 有客户端连上来的时候再创建一个UDP和客户端通信.
做客户端应该不需要这样做吧, 只需刚开始建立的UDP来通信就可以了?

使用特权

评论回复
地板
yklstudent| | 2015-12-23 17:43 | 只看该作者
贴个我设置的
/* 69 is the port used for TFTP protocol initial transaction */
        uint16_t port = 69;

        /* Create a new UDP control block */
        upcb = udp_new();

        if(upcb != NULL)
        {
                /* assign destination IP address */
                IP4_ADDR(&destIPaddr, DEST_IP_ADDR0, DEST_IP_ADDR1, DEST_IP_ADDR2, DEST_IP_ADDR3);

                /* configure destination IP address and port */
                err = udp_connect(upcb, &destIPaddr, port);

                if(err == ERR_OK)
                {
                        /* Set a receive callback for the upcb */
                        udp_recv(upcb, app_tftp_recv_callback, NULL);

                        //sprintf((char*)data, "sending udp client message %d", (int*)message_count);
                        pstr = &data[0];
                        message_count = 0;
                        *pstr++ = 0x00;
                        *pstr++ = TFTP_RRQ;                        //读报文
                        //*pstr++ = TFTP_WRQ;                //写报文

                        tm_strcpy(pstr, "999407.bin");
                        pstr += (strlen("999407.bin") + 1);

                        tm_strcpy(pstr, "octet");
                        pstr += (strlen("octet") + 1);

                        tm_strcpy(pstr, "blksize");
                        pstr += (strlen("blksize") + 1);

                        memset(buf, 0x00, sizeof(buf));
                        sprintf((char*)buf, "%d", 512);
                        tm_strcpy(pstr, buf);
                        pstr += (tm_strlen(buf) + 1);

                        tm_strcpy(pstr, "tsize");
                        message_count += strlen("tsize");
                        pstr += (strlen("tsize") + 1);

                        memset(buf, 0x00, sizeof(buf));
                        sprintf((char*)buf, "%d", 0);
                        tm_strcpy(pstr, buf);
                        pstr += (tm_strlen(buf) + 1);
                        message_count = pstr - &data[0];

                        /* allocate pbuf from pool */
                        p = pbuf_alloc(PBUF_TRANSPORT, message_count, PBUF_POOL);

                        if(p != NULL)
                        {
                                /* copy data to pbuf */
                                pbuf_take(p, (char*)data, message_count);

                                /* send udp data */
                                udp_send(upcb, p);

                                /* free pbuf */
                                pbuf_free(p);
                        }
                        else
                        {
                       
                        }
                }
                else
                {

                }
        }
        else
        {

        }

使用特权

评论回复
5
gejigeji521| | 2015-12-23 23:33 | 只看该作者
这个是网络传输数据吗?地址好像是Localhost。

使用特权

评论回复
6
zhenxizhou|  楼主 | 2015-12-24 10:31 | 只看该作者
@yklstudent, 照你的设置, 结果还是一样.


哪里设置错了吗?

使用特权

评论回复
7
yklstudent| | 2015-12-24 11:15 | 只看该作者
zhenxizhou 发表于 2015-12-24 10:31
@yklstudent, 照你的设置, 结果还是一样.

为什么你的port是4096?????不是69吗?

使用特权

评论回复
8
zhenxizhou|  楼主 | 2015-12-24 13:50 | 只看该作者
本帖最后由 zhenxizhou 于 2015-12-24 13:52 编辑
yklstudent 发表于 2015-12-24 11:15
为什么你的port是4096?????不是69吗?

客户端连接服务端的69端口, 客户端不一定要用69端口吧.
就象服务端69端口收到客户端的请求, 重新开一个空闲端口与客户端通信一样.
你的程序服务端打印出来的客户端口是69?

使用特权

评论回复
9
yklstudent| | 2015-12-24 16:06 | 只看该作者
zhenxizhou 发表于 2015-12-24 13:50
客户端连接服务端的69端口, 客户端不一定要用69端口吧.
就象服务端69端口收到客户端的请求, 重新开一个空 ...

应该是服务器设置好IP地址和端口号侦听客户端,客户端连接服务器IP地址和端口号后就可以了
话说楼主用的PC工具软件调试服务器好用,调试客户端好用吗?

使用特权

评论回复
10
zhenxizhou|  楼主 | 2015-12-24 16:48 | 只看该作者
yklstudent 发表于 2015-12-24 16:06
应该是服务器设置好IP地址和端口号侦听客户端,客户端连接服务器IP地址和端口号后就可以了
话说楼主用的P ...

我现在PC做客户端, STM32做服务端是可以在线升级成功的.
反过来就碰到现在这个问题了. 不知问题出在哪里? 郁闷...
tftpd32这个软件做客户端的时候, 好像就没有log输出了.

使用特权

评论回复
评论
失格 2019-8-27 11:22 回复TA
你好 我最近在做这个是相同的问题,按上面原理来说 不是客户端发一个RRQ包,然后服务器就开始发送文件了吗,用wireshark抓包后发现 服务器发送的block为1的DATA包,但是客户端根本没进回调函数,请问您最后是如何解决的 
11
yklstudent| | 2015-12-24 16:52 | 只看该作者
本帖最后由 yklstudent 于 2015-12-24 16:58 编辑
zhenxizhou 发表于 2015-12-24 16:48
我现在PC做客户端, STM32做服务端是可以在线升级成功的.
反过来就碰到现在这个问题了. 不知问题出在哪里? ...

TFTP32做服务器好用?我调试客户端时没用这个软件

使用特权

评论回复
12
zhenxizhou|  楼主 | 2015-12-24 18:00 | 只看该作者
yklstudent 发表于 2015-12-24 16:52
TFTP32做服务器好用?我调试客户端时没用这个软件

好用, 同时升级几百台没问题, 我们别的项目上用了.
所以这个项目也打算继续用, 但现在不知道问题出在哪里.:'(
你用什么调试的, 调试信息多吗

使用特权

评论回复
13
mintspring| | 2015-12-24 21:25 | 只看该作者
  • 件平台:STM32F103C8T6有线通信板


1、TCP/IP协议栈LWIP1.1、LWIP认识
       LWIP是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈,是Light Weight (轻型)IP协议,有无操作系统的支持都可以运行。LWIP提供三种API,分别是RAW API、LWIP API 、BSD API。其中RAW API把协议栈和应用程序放到一个进程里边,该接口基于函数回调技术来实现的,适合于无操作系统的场合运行,如单片机。本文使用的就是LWIP的RAW API来实现网络层的通信的。
1.2、TFTP在LWIP中的实现        关于LWIP的移植,就不在本文中多讲,读者可以在网上找到众多资料或在另外的专题中再详细讲解,在这里我们专注其应用。在LWIP中实现一个TFTP服务器非常简单,根据RAW API的编程方法,在初始化的时候创建一个UDP PCB(TFTP使用UDP协议通信),且绑定69端口(TFTP默认通信端口),最后指定该UDP PCB的数据接收回调函数即可。
以上的创建TFTP服务器的方法需要在LWIP初始化,并启动网卡后进行:

使用特权

评论回复
14
mintspring| | 2015-12-24 21:26 | 只看该作者
 LwIP_Config();
    printf("ipaddr:%d.%d.%d.%d\r\n", net_ip[0], net_ip[1], net_ip[2], net_ip[3]);

    tftpd_init();
在tftpd_init函数中创建TFTP服务器:
void tftpd_init(void)
{
  err_t err;
  unsigned port = 69;

  /* create a new UDP PCB structure  */
  UDPpcb = udp_new();
  if (!UDPpcb)
  {  /* Error creating PCB. Out of Memory  */
    return;
  }

  /* Bind this PCB to port 69  */
  err = udp_bind(UDPpcb, IP_ADDR_ANY, port);
  if (err != ERR_OK)
  {    /* Unable to bind to port  */
    return;
  }

  /* TFTP server start  */
  udp_recv(UDPpcb, recv_callback_tftp, NULL);
}
OK,到这里就完成了TFTP服务器在LWIP中建立起来了,接下来的主要事情就是根据TFTP协议进行协议解释、数据处理。


2、TFTP协议分析2.1、TFTP通信基本流程(摘自网络)


2.2、TFTP报文格式(摘自网络)


2.3、TFTP协议理解

     从以上两张图片,我们了解到什么有用信息呢?

  • 每一次文件传输,首先需要发起一个请求,根据请求帧的操作码判断是读文件还是写文件。
  • 每一帧都有一个操作码用来标识读写。
  • 数据包的长度有一个块编号用来表示数据包的顺序。
  • 数据包中的数据长度为512个字节(在后面的软件我们可以了解到这个长度是可以设定的)。

使用特权

评论回复
15
mintspring| | 2015-12-24 21:28 | 只看该作者
3、实现TFTP文件传输3.1、文件传输协议实现

有了第2节的协议分析,我们基本了解了TFTP通信的协议,在这里,我们来实现TFTP的服务器端代码。

在监听的回调函数被触发调用时,首先从请求帧中获取操作码:

3、实现TFTP文件传输
3.1、文件传输协议实现
有了第2节的协议分析,我们基本了解了TFTP通信的协议,在这里,我们来实现TFTP的服务器端代码。

在监听的回调函数被触发调用时,首先从请求帧中获取操作码:
根据操作码进行相应的处理:
<div class="blockcode"><blockquote>tftp_opcode op = tftp_decode_op(pkt_buf->payload);  
  
switch (op)  
  {  
  
    case TFTP_RRQ:    /* TFTP RRQ (read request) */  
      tftp_extract_filename(FileName, pkt_buf->payload);  
      tftp_process_read(upcb, addr, port, FileName);  
      break;  
  
    case TFTP_WRQ:    /* TFTP WRQ (write request) */   
      tftp_extract_filename(FileName, pkt_buf->payload);  
      //在这个加入擦FALSH  
       tftp_process_write(upcb, addr, port, FileName);  
      break;  
  
    default:  
      /* sEndTransfera generic access violation message */  
      tftp_send_error_message(upcb, addr, port, TFTP_ERR_ACCESS_VIOLATION);  
      /* TFTP unknown request op */  
      /* no need to use tftp_cleanup_wr because no "tftp_connection_args" struct has been malloc'd   */  
      udp_remove(upcb);  
  
      break;  
  }  



使用特权

评论回复
16
mintspring| | 2015-12-24 21:29 | 只看该作者
这里当STM32接收到写操作请求时,通过tftp_extract_filename函数把文件名读出来。接下来通过tftp_process_write函数来完成文件数据的传输:
int tftp_process_write(struct udp_pcb *upcb, struct ip_addr *to, int to_port, char *FileName)
{
  ... ...
  udp_recv(upcb, wrq_recv_callback, args);
  tftp_send_ack_packet(upcb, to, to_port, args->block);

  return 0;
}
设定数据传输回调函数后,根据TFTP协议,回复一个ACK,之后TFTP客户端开始传输文件数据,从而触发调用wrq_recv_callback
void wrq_recv_callback(void *_args, struct udp_pcb *upcb, struct pbuf *pkt_buf, struct ip_addr *addr, u16_t port)
{
  tftp_connection_args *args = (tftp_connection_args *)_args;
  int n = 0;

  if (pkt_buf->len != pkt_buf->tot_len)
  {
    return;
  }

  /* Does this packet have any valid data to write? */
  if ((pkt_buf->len > TFTP_DATA_PKT_HDR_LEN) &&
      (tftp_extract_block(pkt_buf->payload) == (args->block + 1)))
  {
    /* 在这里处理接收到的数据pkt_buf->payload */

    /* update our block number to match the block number just received */
    args->block++;
    /* update total bytes  */
    (args->tot_bytes) += (pkt_buf->len - TFTP_DATA_PKT_HDR_LEN);

    /* This is a valid pkt but it has no data.  This would occur if the file being
       written is an exact multiple of 512 bytes.  In this case, the args->block
       value must still be updated, but we can skip everything else.    */
  }
  else if (tftp_extract_block(pkt_buf->payload) == (args->block + 1))
  {
    /* update our block number to match the block number just received  */
    args->block++;
  }

  /* SEndTransferthe appropriate ACK pkt (the block number sent in the ACK pkt echoes
   * the block number of the DATA pkt we just received - see RFC1350)
   * NOTE!: If the DATA pkt we received did not have the appropriate block
   * number, then the args->block (our block number) is never updated and
   * we simply sEndTransfera "duplicate ACK" which has the same block number as the
   * last ACK pkt we sent.  This lets the host know that we are still waiting
   * on block number args->block+1. */
  tftp_send_ack_packet(upcb, addr, port, args->block);

  /* If the last write returned less than the maximum TFTP data pkt length,
   * then we've received the whole file and so we can quit (this is how TFTP
   * signals the EndTransferof a transfer!)
   */
  if (pkt_buf->len < TFTP_DATA_PKT_LEN_MAX)
  {
    tftp_cleanup_wr(upcb, args);
    pbuf_free(pkt_buf);
  }
  else
  {
    pbuf_free(pkt_buf);
    return;
  }

}


使用特权

评论回复
17
mintspring| | 2015-12-24 21:30 | 只看该作者
OK!至此STM32就完成了整个TFTP协议文件的接收。
3.2、保存文件数据

接收到完整的文件数据之后,我们需要把数据写到STM32的FLASH中,保存起来。

由于STM32内存较小,不可能开辟一个大的内存空间把文件数据保存起来再写到FLASH,

所以需要边接收边写FLASH。

首先在接收到写操作请求后,把存储区域的FLASH擦除:

    case TFTP_WRQ:    /* TFTP WRQ (write request) */ 
。    ... ...
      FlashDestination = HtmlDataAddress;
          /* Erase the needed pages where the user application will be loaded */
      /* Define the number of page to be erased */
      NbrOfPage = FLASH_PagesMask(HtmlTotalSize);//擦除HTML区域

      /* Erase the FLASH pages */
      FLASH_Unlock();
      for (EraseCounter = 0; (EraseCounter < NbrOfPage) && (FLASHStatus == FLASH_COMPLETE); EraseCounter++)
       {
         FLASHStatus = FLASH_ErasePage(HtmlSizeAddress + (PageSize * EraseCounter));
       }
      FLASH_Lock();

在文件数据传输过程中,把<=512BYTE的数据写到FLASH:

     filedata = (uint32_t)pkt_buf->payload + TFTP_DATA_PKT_HDR_LEN;
     FLASH_Unlock();
     for (n = 0;n < (pkt_buf->len - TFTP_DATA_PKT_HDR_LEN);n += 4)
     {
      /* Program the data received into STM32F10x Flash */
      FLASH_ProgramWord(FlashDestination, *(uint32_t*)filedata);

       if (*(uint32_t*)FlashDestination != *(uint32_t*)filedata)
        {
          /* End session */
         tftp_send_error_message(upcb, addr, port, FLASH_VERIFICATION_FAILED);
         /* close the connection */
         tftp_cleanup_wr(upcb, args); /* close the connection */
        }
        FlashDestination += 4;
        filedata += 4;
     }
     FLASH_Lock();
到这里,就实现了STM32接收TFTP客户端传输的文件数据,并保存到FLASH地址为FlashDestination的区域中。

使用特权

评论回复
18
mintspring| | 2015-12-24 21:30 | 只看该作者
3.3、演示操作

将通信板连接到与电脑在同一局域的路由器,并正确配置好IP信息。在电脑端打开软件Tftpd32.exe:


点击“上传”按键,就会把文件html.bin文件发送到STM32通信板:



可以在STM32通信板中把文件内容读出来使用,在下一篇博客物联网WEB开发中会使用TFTP传输HTML文件。

4、TFTP的应用

    TFTP主要是实现文件传输,在固件升级、程序调试中极大提高效率,有重要的意义。

使用特权

评论回复
19
yklstudent| | 2015-12-24 22:56 | 只看该作者
mintspring 发表于 2015-12-24 21:30
3.3、演示操作将通信板连接到与电脑在同一局域的路由器,并正确配置好IP信息。在电脑端打开软件Tftpd32.exe ...

兄弟应该用的也是客户端模式(STM32),楼主这里要的是服务器模式(STM32);
楼主在线调试时可以查看下发送了WRQ数据包后,客户端是否能进回调函数app_tftp_recv_callback;
这里估计不会进,但这时TFTP32其实已经应答了。
问题就出在这里,为什么没有进回调函数,我也不知道什么原因,楼主可以仔细研究下。
楼主可以网上找找网络监控工具,那样可以监控数据的收发情况,有利于分析查找问题

使用特权

评论回复
20
yklstudent| | 2015-12-24 22:56 | 只看该作者
mintspring 发表于 2015-12-24 21:30
3.3、演示操作将通信板连接到与电脑在同一局域的路由器,并正确配置好IP信息。在电脑端打开软件Tftpd32.exe ...

兄弟应该用的也是客户端模式(STM32),楼主这里要的是服务器模式(STM32);
楼主在线调试时可以查看下发送了WRQ数据包后,客户端是否能进回调函数app_tftp_recv_callback;
这里估计不会进,但这时TFTP32其实已经应答了。
问题就出在这里,为什么没有进回调函数,我也不知道什么原因,楼主可以仔细研究下。
楼主可以网上找找网络监控工具,那样可以监控数据的收发情况,有利于分析查找问题

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

37

主题

76

帖子

2

粉丝