一、基于freertos下对LAN8720模块进行通信测试

一、硬件环境:

1.LAN8720模块:

引脚说明:

STM32,

开发环境,cubeide,cubemx。

2、界面端配置:

默认即可,当mac地址冲突时,修改mac地址。

LAN8720和LAN8742无特别大的差异,仅是功能的优化与提升的差别。

开启常用的配置,TCP与UDP相关的。

二、代码部分

lwipopts.h

c 复制代码
// 基础配置
#define NO_SYS                  0       // 启用RTOS(必须)
#define LWIP_SOCKET             1       // 启用Socket API(核心)
#define LWIP_TCP                1       // 启用TCP
#define LWIP_UDP                1       // 启用UDP

#define LWIP_IPV4 1        // 必须=1,否则IPv4相关宏被禁用
#define LWIP_IPV6 0        // 若不用IPv6,直接禁用(减少宏冲突)
#define LWIP_NETIF_LINK_CALLBACK 1     // 链路状态回调

// 内存配置(根据需求调整)
#define MEM_SIZE                16384   // 内存池大小
#define MEMP_NUM_TCP_PCB        5       // TCP连接数
#define TCP_MSS                 1460    // TCP最大分段
#define TCP_WND                 8192    // TCP窗口大小

// Socket相关
#define LWIP_SO_REUSE           1       // 依赖项:启用SO_REUSE系列选项
#define LWIP_SOCKET             1       // 确保Socket API已启用
#define LWIP_SO_RCVTIMEO        1       // 接收超时
#define LWIP_SO_SNDTIMEO        1       // 发送超时

#define MEMP_NUM_UDP_PCB         5       // UDP连接控制块(至少1,建议5)
#define LWIP_SO_RCVTIMEO         1       // 必须启用接收超时(否则setsockopt无效)

添加函数

lwip.c

c 复制代码
#include "lwip/ip_addr.h"  // 必须包含,否则宏无法展开
#include "lwip/ip4_addr.h" // 若用IPv4专用宏,需包含
#include "lwip/sys.h"

void MX_LWIP_Process(void)
{
  // LwIP核心事件处理(标准实现)
  sys_check_timeouts();
}

连接网络相关的信息

c 复制代码
#include "lwip/sockets.h"
#include "lwip/netdb.h"

#define TCP_SERVER_PORT 8080 // 监听端口
#define TCP_BUF_SIZE    1024

#define TCP_SERVER_IP     "192.168.1.5"  // PC的实际IP
#define TCP_SERVER_PORT   8080           // PC服务端端口
#define RECV_BUF_SIZE     1024           // 接收缓冲区大小
#define CONNECT_RETRY_CNT 5              // 连接失败重试次数

#define UDP_TEST_PORT    8081
#define UDP_TARGET_IP    "192.168.1.5" // PC 端的IP 地址
#define UDP_SEND_INTERVAL 1000 // 发送间隔(ms)

LAN8720 初始化 任务

c 复制代码
void StartDefaultTask(void *argument)
{
  /* init code for LWIP */
  MX_LWIP_Init();
  /* USER CODE BEGIN StartDefaultTask */
  osDelay(1000);
  uint32_t dhcp_timeout = 0;
   ip4_addr_t ipaddr, netmask, gw;

   printf("=== Network Init Start ===\r\n");

   // DHCP自动获取IP(超时5秒切换静态IP)
 #ifdef LWIP_DHCP
   printf("Waiting for DHCP IP...\r\n");

   while (netif_default->ip_addr.addr == 0)
   {
     MX_LWIP_Process();  // 处理LwIP事件
     osDelay(100);
     dhcp_timeout++;
     if (dhcp_timeout > 50)  // 5秒超时
     {
       printf("DHCP Timeout! Use Static IP\r\n");
       // 配置静态IP(根据你的网段修改)
       IP4_ADDR(&ipaddr, 192, 168, 1, 7);
       IP4_ADDR(&netmask, 255, 255, 255, 0);
       IP4_ADDR(&gw, 192, 168, 1, 1);
       netif_set_addr(&netif_default, &ipaddr, &netmask, &gw);
       break;
     }
   }
 #else
   // 直接用静态IP(CubeMX配置后无需此段)
   IP4_ADDR(&ipaddr, 192, 168, 1, 100);
   IP4_ADDR(&netmask, 255, 255, 255, 0);
   IP4_ADDR(&gw, 192, 168, 1, 1);
   netif_set_addr(&netif_default, &ipaddr, &netmask, &gw);
 #endif
   // 打印网络信息
   printf("IP Address: %s\r\n", ip4addr_ntoa(&netif_default->ip_addr));
   printf("Netmask   : %s\r\n", ip4addr_ntoa(&netif_default->netmask));
   printf("Gateway   : %s\r\n", ip4addr_ntoa(&netif_default->gw));
   printf("=== Network Init Success ===\r\n");
   // 初始化完成,删除自身任务
   osThreadTerminate(defaultTaskHandle);
}

TCP SERVER 任务

c 复制代码
void StartTask02(void *argument)
{
  int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    char recv_buf[1024];
    int recv_len;
    // 1. 创建TCP Socket(增加错误日志)
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0)
    {
      printf("TCP Socket Create Failed! Err: %d\r\n", server_fd); // 打印具体错误码
      osThreadTerminate(myTask02Handle);
      return;
    }
    printf("TCP Socket Create Success! fd: %d\r\n", server_fd);
     // 3. 绑定IP和端口(增加错误日志)
     memset(&server_addr, 0, sizeof(server_addr));
     server_addr.sin_family = AF_INET;
     server_addr.sin_addr.s_addr = INADDR_ANY;  // 监听所有网卡
     server_addr.sin_port = htons(TCP_SERVER_PORT);
     if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
     {
       printf("TCP Bind Port %d Failed! Err: %d\r\n", TCP_SERVER_PORT, errno); // 打印绑定错误
       close(server_fd);
       osThreadTerminate(myTask02Handle);
       return;
     }
     printf("TCP Bind Port %d Success!\r\n", TCP_SERVER_PORT);
     // 4. 开始监听(增加错误日志,backlog设为1,减少资源占用)
     if (listen(server_fd, 1) < 0)
     {
       printf("TCP Listen Failed! Err: %d\r\n", errno);
       close(server_fd);
       osThreadTerminate(myTask02Handle);
       return;
     }
     printf("TCP Server: Listen on Port %d\r\n", TCP_SERVER_PORT);
   // 5. 循环处理客户端连接
   while (1)
   {
     // 等待客户端连接(阻塞)
     client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
     if (client_fd < 0)
     {
       printf("TCP Accept Failed!\r\n");
       osDelay(100);
       continue;
     }
     // 打印客户端信息
     printf("TCP Client Connected: IP=%s, Port=%d\r\n",
            inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

     // 6. 循环接收并回显数据
     while (1)
     {
       // 接收客户端数据(阻塞,超时由LWIP_SO_RCVTIMEO控制)
       recv_len = recv(client_fd, recv_buf, sizeof(recv_buf)-1, 0);
       if (recv_len > 0)
       {
         recv_buf[recv_len] = '\0';  // 字符串结束符
         printf("TCP Recv: %s (Len: %d)\r\n", recv_buf, recv_len);

         // 回显数据给客户端
         send(client_fd, recv_buf, recv_len, 0);
       }
       else if (recv_len == 0)
       {
         // 客户端主动关闭连接
         printf("TCP Client Disconnected\r\n");
         break;
       }
       else
       {
         // 接收错误
         printf("TCP Recv Error! Err: %d\r\n", recv_len);
         break;
       }
     }
     // 关闭客户端连接
     close(client_fd);
     printf("TCP Connection Closed\r\n");
     // 处理LwIP事件
     MX_LWIP_Process();
     osDelay(10);
   }
}

UDP 任务

c 复制代码
void StartTask03(void *argument)
{
  int udp_fd;
   struct sockaddr_in target_addr, local_addr;
   char send_buf[64];
   uint32_t send_count = 0;
   struct timeval timeout; // 接收超时配置
   // 1. 创建UDP Socket
   udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
   if (udp_fd < 0)
   {
     printf("UDP Socket Create Failed!\r\n");
     osThreadTerminate(myTask03Handle);
     return;
   }
   // 2. 设置UDP接收超时(关键:500ms超时,避免阻塞)
   timeout.tv_sec = 0;
   timeout.tv_usec = 500000; // 500毫秒
   setsockopt(udp_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
   // 3. 绑定STM32本地端口(必须绑定,否则无法接收)
   memset(&local_addr, 0, sizeof(local_addr));
   local_addr.sin_family = AF_INET;
   local_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
   local_addr.sin_port = htons(UDP_TEST_PORT); // 绑定8081端口
   if (bind(udp_fd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0)
   {
     printf("UDP Bind Local Port Failed! Err: %d\r\n", errno);
     close(udp_fd);
     osThreadTerminate(myTask03Handle);
     return;
   }
   // 4. 配置PC的目标IP和端口
   memset(&target_addr, 0, sizeof(target_addr));
   target_addr.sin_family = AF_INET;
   target_addr.sin_port = htons(UDP_TEST_PORT);
   if (inet_pton(AF_INET, UDP_TARGET_IP, &target_addr.sin_addr) <= 0)
   {
     printf("UDP Target IP Invalid!\r\n");
     close(udp_fd);
     osThreadTerminate(myTask03Handle);
     return;
   }
   printf("UDP Init OK! Target IP: %s:%d\r\n", UDP_TARGET_IP, UDP_TEST_PORT);
   // 5. 循环发送+非阻塞接收
   while (1)
   {
     // ---- 发送UDP包到PC ----
     snprintf(send_buf, sizeof(send_buf), "UDP Socket Test %lu", send_count++);
     int send_len = sendto(udp_fd, send_buf, strlen(send_buf), 0,
                           (struct sockaddr *)&target_addr, sizeof(target_addr));
     if (send_len > 0)
     {
       printf("UDP Send: %s (Len: %d)\r\n", send_buf, send_len);
     }
     else
     {
       printf("UDP Send Failed! Err: %d\r\n", send_len);
     }
     // ---- 接收PC的UDP数据(非阻塞,超时500ms)----
     char recv_buf[64];
     struct sockaddr_in recv_addr;
     socklen_t recv_addr_len = sizeof(recv_addr);
     int recv_len = recvfrom(udp_fd, recv_buf, sizeof(recv_buf)-1, 0,
                             (struct sockaddr *)&recv_addr, &recv_addr_len);
     // 处理接收结果(区分"超时"和"错误")
     if (recv_len > 0)
     {
       recv_buf[recv_len] = '\0'; // 字符串结束符
       printf("✅ UDP Recv from PC(%s:%d): %s\r\n",
              inet_ntoa(recv_addr.sin_addr), ntohs(recv_addr.sin_port), recv_buf);
     }
     else if (recv_len == 0)
     {
       printf("UDP Recv: Peer closed\r\n");
     }
     else
     {
       // 超时属于正常情况(不是错误),无需打印
       if (errno != EAGAIN && errno != EWOULDBLOCK)
       {
         printf("UDP Recv Error! Err: %d\r\n", errno);
       }
     }
     // LwIP事件处理
     sys_check_timeouts();
     osDelay(1000); // 1秒循环一次
   }
}

TCP CLIENT 任务

c 复制代码
void StartTask04(void *argument)
{
  int tcp_fd = -1;
   struct sockaddr_in server_addr;
   char recv_buf[RECV_BUF_SIZE];
   char send_buf[64];
   uint32_t send_count = 0;
   int retry_cnt = 0;
   struct timeval timeout; // 接收超时配置

   // 等待网络就绪(IP非0)

   while (netif_default->ip_addr.addr == 0)
   {
     osDelay(100);
   }
   printf("Network Ready, Try Connect to PC TCP Server...\r\n");
   // 循环尝试连接PC服务端(失败重试)
   while (1)
   {
     // ---- 1. 创建TCP Socket ----
     tcp_fd = socket(AF_INET, SOCK_STREAM, 0);
     if (tcp_fd < 0)
     {
       printf("TCP Socket Create Failed! Err: %d\r\n", tcp_fd);
       osDelay(1000);
       continue;
     }
     // ---- 2. 设置接收超时(避免阻塞)----
     timeout.tv_sec = 1;    // 1秒超时
     timeout.tv_usec = 0;
     setsockopt(tcp_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

     // ---- 3. 配置PC服务端地址 ----
     memset(&server_addr, 0, sizeof(server_addr));
     server_addr.sin_family = AF_INET;
     server_addr.sin_port = htons(TCP_SERVER_PORT);
     // 转换PC IP为网络字节序
     if (inet_pton(AF_INET, TCP_SERVER_IP, &server_addr.sin_addr) <= 0)
     {
       printf("TCP Server IP(%s) Invalid!\r\n", TCP_SERVER_IP);
       close(tcp_fd);
       osDelay(1000);
       continue;
     }

     // ---- 4. 主动连接PC服务端 ----
     printf("Try Connect to %s:%d (Retry: %d/%d)...\r\n",
            TCP_SERVER_IP, TCP_SERVER_PORT, retry_cnt+1, CONNECT_RETRY_CNT);
     if (connect(tcp_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == 0)
     {
       printf("✅ TCP Connect to PC Server Success!\r\n");
       retry_cnt = 0; // 连接成功,重置重试计数
       break; // 退出重试循环,进入数据收发
     }
     else
     {
       printf("❌ TCP Connect Failed! Err: %d\r\n", errno);
       close(tcp_fd);
       retry_cnt++;
       if (retry_cnt >= CONNECT_RETRY_CNT)
       {
         printf("Connect Retry Max, Wait 5s to Retry...\r\n");
         retry_cnt = 0;
         osDelay(5000);
       }
       else
       {
         osDelay(1000);
       }
     }
   }
   // ---- 5. 连接成功,循环收发数据 ----
   while (1)
   {
     // ---- 发送数据到PC服务端 ----
     snprintf(send_buf, sizeof(send_buf), "STM32 Client Data %lu", send_count++);
     int send_len = send(tcp_fd, send_buf, strlen(send_buf), 0);
     if (send_len > 0)
     {
       printf("TCP Send: %s (Len: %d)\r\n", send_buf, send_len);
     }
     else if (send_len == 0)
     {
       printf("TCP Send: PC Server Closed\r\n");
       break; // 连接断开,退出收发循环
     }
     else
     {
       printf("TCP Send Failed! Err: %d\r\n", send_len);
       break;
     }

     // ---- 接收PC服务端数据(超时1秒)----
     int recv_len = recv(tcp_fd, recv_buf, RECV_BUF_SIZE-1, 0);
     if (recv_len > 0)
     {
       recv_buf[recv_len] = '\0'; // 字符串结束符
       printf("TCP Recv from PC: %s (Len: %d)\r\n", recv_buf, recv_len);
       // 示例:响应PC指令(如PC发"LED_ON",STM32回复确认)
       if (strstr(recv_buf, "LED_ON") != NULL)
       {
         char ack_buf[] = "STM32: LED_ON OK!\r\n";
         send(tcp_fd, ack_buf, strlen(ack_buf), 0);
         printf("Send ACK: %s", ack_buf);
       }
       else if (strstr(recv_buf, "LED_OFF") != NULL)
       {
         char ack_buf[] = "STM32: LED_OFF OK!\r\n";
         send(tcp_fd, ack_buf, strlen(ack_buf), 0);
         printf("Send ACK: %s", ack_buf);
       }
     }
     else if (recv_len == 0)
     {
       printf("TCP Recv: PC Server Closed Connection\r\n");
       break; // 连接断开
     }
     else
     {
       // 超时属于正常情况,无需打印(非错误)
       if (errno != EAGAIN && errno != EWOULDBLOCK)
       {
         printf("TCP Recv Error! Err: %d\r\n", errno);
       }
     }
     // ---- LwIP事件处理 + 延时 ----
     sys_check_timeouts();
     osDelay(1000); // 1秒收发一次
   }
   // ---- 6. 连接断开,关闭Socket并重试连接 ----
   printf("TCP Connection Closed, Reconnect After 5s...\r\n");
   close(tcp_fd);
   osDelay(5000);
}

TFTP传输任务

c 复制代码
#define TFTP_PORT              69        // TFTP默认端口
#define TFTP_BLOCK_SIZE        512       // TFTP数据块大小(协议规定)
#define TFTP_OP_RRQ            1         // 读请求(客户端下载,暂不支持)
#define TFTP_OP_WRQ            2         // 写请求(客户端上传,核心支持)
#define TFTP_OP_DATA           3         // 数据块
#define TFTP_OP_ACK            4         // 确认块
#define TFTP_OP_ERROR          5         // 错误

/* TFTP错误码 */
#define TFTP_ERR_NOT_DEFINED   0
#define TFTP_ERR_UNSUPPORT_OP  1         // 不支持的操作
#define TFTP_ERR_INVALID_BLOCK 2         // 无效块号
#define TFTP_ERR_DISK_FULL     3

/* 存储配置(示例:SD卡,路径为"/tftp/") */
#define TFTP_STORAGE_PATH      "/tftp/"
/* TFTP会话信息(仅保留必要字段,无文件存储) */
typedef struct {
  struct sockaddr_in client_addr;  // 客户端地址
 // FIL file;                        // 打开的文件对象
  uint16_t block_num;              // 当前数据块号
  uint8_t is_active;               // 会话是否激活
} tftp_session_t;
static tftp_session_t tftp_session = {0};


/**
 * @brief 发送TFTP ACK包(确认数据块)
 */
static err_t tftp_send_ack(int udp_fd, struct sockaddr_in *client_addr, uint16_t block_num)
{
  uint8_t ack_pkt[4];
  // 操作码(4=ACK) + 块号(网络字节序)
  ack_pkt[0] = 0;
  ack_pkt[1] = TFTP_OP_ACK;
  ack_pkt[2] = (block_num >> 8) & 0xFF;
  ack_pkt[3] = block_num & 0xFF;

  // 发送ACK到客户端
  int send_len = sendto(udp_fd, ack_pkt, sizeof(ack_pkt), 0,
                        (struct sockaddr *)client_addr, sizeof(*client_addr));

  return (send_len == sizeof(ack_pkt)) ? ERR_OK : ERR_VAL;
}


/**
 * @brief 发送TFTP ERROR包(错误通知)
 */
static err_t tftp_send_error(int udp_fd, struct sockaddr_in *client_addr, uint16_t err_code, const char *err_msg) {
  uint8_t err_pkt[512];
  uint16_t len = 0;
  // 操作码(5=ERROR) + 错误码(网络字节序)
  err_pkt[len++] = 0;
  err_pkt[len++] = TFTP_OP_ERROR;
  err_pkt[len++] = (err_code >> 8) & 0xFF;
  err_pkt[len++] = err_code & 0xFF;
  // 错误信息(以0结尾)
  strncpy((char*)&err_pkt[len], err_msg, sizeof(err_pkt)-len-1);
  len += strlen(err_msg);
  err_pkt[len++] = 0;

  // 发送错误包到客户端
  sendto(udp_fd, err_pkt, len, 0, (struct sockaddr *)client_addr, sizeof(*client_addr));
  return ERR_OK;
}


/**
 * @brief 解析TFTP WRQ包(写请求:客户端要上传文件)
 */
static err_t tftp_parse_wrq(uint8_t *pkt, uint16_t pkt_len, char *filename, uint16_t filename_max_len) {
  if (pkt[1] != TFTP_OP_WRQ) {
    return ERR_VAL; // 不是WRQ包
  }

  // 解析文件名(以0结尾的字符串)
  uint16_t idx = 2; // 跳过操作码(前2字节)
  uint16_t fn_len = 0;
  while (idx < pkt_len && pkt[idx] != 0 && fn_len < filename_max_len-1) {
    filename[fn_len++] = pkt[idx++];
  }
  filename[fn_len] = 0; // 字符串结束符

  // 跳过模式(如"octet"),无需处理
  while (idx < pkt_len && pkt[idx] != 0) idx++;
  return (fn_len > 0) ? ERR_OK : ERR_VAL;
}


/**
 * @brief 打印TFTP数据块内容(ASCII+十六进制双格式)
 */
static void tftp_print_data_block(uint16_t block_num, uint8_t *data, uint16_t data_len) {
  printf("\n=====================================\n");
  printf("TFTP Block %d (Len: %d Bytes)\n", block_num, data_len);
  printf("ASCII Content:\n");
  // 打印ASCII(不可见字符用.替代)
  for (uint16_t i=0; i<data_len; i++) {
    if (data[i] >= 0x20 && data[i] <= 0x7E) { // 可打印ASCII
      printf("%c", data[i]);
    } else {
      printf(".");
    }
    if ((i+1) % 16 == 0) printf("\n"); // 每16字节换行
  }
  printf("\n\nHex Content:\n");
  // 打印十六进制(每行16字节,带地址)
  for (uint16_t i=0; i<data_len; i++) {
    if (i % 16 == 0) printf("0x%04X: ", i);
    printf("%02X ", data[i]);
    if ((i+1) % 8 == 0) printf(" ");  // 每8字节空格分隔
    if ((i+1) % 16 == 0) printf("\n"); // 每16字节换行
  }
  printf("\n=====================================\n");
}
用于观察TFTP 接收文件的内容任务
void StartTask05(void *argument)
{
  int udp_fd;
  struct sockaddr_in server_addr, client_addr;
  socklen_t client_addr_len = sizeof(client_addr);
  uint8_t recv_buf[TFTP_BLOCK_SIZE + 4]; // 数据块+协议头(最大516字节)
  int recv_len;
  char filename[64];

  // 等待网络就绪
  while (netif_default->ip_addr.addr == 0) {
    osDelay(100);
  }
  printf("=== TFTP Server Start (Port: %d) ===\r\n", TFTP_PORT);

  // 1. 创建UDP Socket
  udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
  if (udp_fd < 0) {
    printf("TFTP UDP Socket Create Failed! Err: %d\r\n", udp_fd);
    osThreadTerminate(myTask05Handle);
    return;
  }

  // 2. 绑定TFTP默认端口(69)
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(TFTP_PORT);
  if (bind(udp_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
    printf("TFTP Bind Port %d Failed! Err: %d\r\n", TFTP_PORT, errno);
    close(udp_fd);
    osThreadTerminate(myTask05Handle);
    return;
  }

  // 3. 循环处理TFTP请求
  while (1) {
    // 接收客户端数据包
    recv_len = recvfrom(udp_fd, recv_buf, sizeof(recv_buf)-1, 0,
                        (struct sockaddr *)&client_addr, &client_addr_len);
    if (recv_len < 2) {
      osDelay(100);
      continue; // 数据包过短,忽略
    }

    // 解析操作码
    switch (recv_buf[1]) {
      case TFTP_OP_WRQ: {
        // 处理写请求:客户端要上传文件
        if (tftp_parse_wrq(recv_buf, recv_len, filename, sizeof(filename)) != ERR_OK) {
          tftp_send_error(udp_fd, &client_addr, TFTP_ERR_NOT_DEFINED, "Invalid WRQ");
          break;
        }
//        char file_path[128];
//               snprintf(file_path, sizeof(file_path), "%s%s", TFTP_STORAGE_PATH, filename);
        printf("\n📢 TFTP WRQ Received: Client %s:%d Upload File -> %s\r\n",
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), filename);

//        f_res = f_open(&tftp_session.file, file_path, FA_CREATE_ALWAYS | FA_WRITE);
//               if (f_res != FR_OK) {
//                 printf("TFTP File Open Failed! Err: %d\r\n", f_res);
//                 tftp_send_error(udp_fd, &client_addr, TFTP_ERR_ACCESS_VIOLATION, "File Open Error");
//                 break;
//               }
//

        // 初始化会话:记录客户端地址、块号(从1开始)
        tftp_session.client_addr = client_addr;
        tftp_session.block_num = 1;
        tftp_session.is_active = 1;

        // 回复ACK 0(WRQ的确认)
        tftp_send_ack(udp_fd, &client_addr, 0);
        printf("✅ TFTP Ready to Receive File Content (Only Print, No Save)\r\n");
        break;
      }

      case TFTP_OP_DATA: {
        // 处理数据块:客户端发送的文件数据
        if (!tftp_session.is_active) {
          tftp_send_error(udp_fd, &client_addr, TFTP_ERR_NOT_DEFINED, "No Active Session");
          break;
        }

        // 验证块号(网络字节序转主机序)
        uint16_t recv_block = (recv_buf[2] << 8) | recv_buf[3];
        if (recv_block != tftp_session.block_num) {
          // 块号不匹配,重发上一个ACK
          tftp_send_ack(udp_fd, &tftp_session.client_addr, tftp_session.block_num - 1);
          break;
        }

        // 提取数据(跳过前4字节协议头)
        uint16_t data_len = recv_len - 4;
        if (data_len > 0) {
          // 核心修改:打印数据块内容(不存储)
          tftp_print_data_block(recv_block, &recv_buf[4], data_len);

          // 写入文件
//                    UINT bytes_written;
//                    f_res = f_write(&tftp_session.file, &recv_buf[4], data_len, &bytes_written);
//                    if (f_res != FR_OK || bytes_written != data_len)
//		      {
//                      tftp_send_error(udp_fd, &tftp_session.client_addr, TFTP_ERR_DISK_FULL, "File Write Error");
//                      f_close(&tftp_session.file);
//                      tftp_session.is_active = 0;
//                      break;
//		      }
//		      printf("TFTP Recv Block %d: %d Bytes\r\n", recv_block, data_len);
        }

        // 回复当前块号的ACK
        tftp_send_ack(udp_fd, &tftp_session.client_addr, recv_block);

        // 检查是否接收完成(数据块小于512字节)
        if (data_len < TFTP_BLOCK_SIZE) {
          printf("\n🎉 TFTP File Content Receive Complete! Total Blocks: %d\r\n", recv_block);
          tftp_session.is_active = 0;
        } else {
          // 块号递增,准备接收下一块
          tftp_session.block_num++;
        }
        break;
      }

      default: {
        // 不支持的操作码
        tftp_send_error(udp_fd, &client_addr, TFTP_ERR_UNSUPPORT_OP, "Only WRQ/DATA Supported");
        break;
      }
    }

    sys_check_timeouts();
    osDelay(10);
  }

  // 关闭Socket(理论上不会执行)
  close(udp_fd);

}

三、效果

stm32 做客户端,pc服务端测试效果如图:

PC做客户端:

UDP测试,需要配设置好ip地址进行测试。

测试TFTP文件传输功能效果如下:传输的仅为一个小文件,输出文件的传输内容。

相关推荐
不染尘.2 小时前
UDP客户服务器模型和UDP协议
服务器·网络·网络协议·计算机网络·udp
Macbethad3 小时前
Linux网关应用技术报告
网络
旺仔Sec3 小时前
2026年河北省职业院校技能大赛“网络系统管理”(高职组)网络构建样题
运维·服务器·网络
Neolnfra3 小时前
Xshell SSH 连接故障排查
运维·服务器·网络·ssh·xshell·运程连接
testpassportcn4 小时前
Cisco 300-540 SPCNI 認證考試介紹(CCNP Service Provider 專業考試)
网络·数据库
车载测试工程师4 小时前
CAPL学习-AVB交互层-功能函数-监听器函数函数
网络·学习·tcp/ip·capl·canoe
爬山算法4 小时前
Netty(21)Netty的SSL/TLS支持是如何实现的?
网络·网络协议·ssl
颹蕭蕭5 小时前
CRC的数学原理
网络
十五年专注C++开发5 小时前
ZeroMQ: 一款高性能、异步、轻量级的消息传输库
网络·c++·分布式·zeroqm