You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TFTP客户端发送RRQ后陷入无限循环无响应问题求助

解决TFTP客户端无限循环无法接收文件的问题

看起来你的TFTP客户端卡在接收循环里出不来,主要是因为TFTP协议格式错误、UDP发送方式不对,还缺了必要的协议交互逻辑。我来帮你一步步排查和修复:

核心问题分析

1. UDP发送未指定目标地址

你在gettftp里新建了UDP套接字,但直接用send()发数据——对于未连接的UDP套接字,send()根本不知道要发给谁,必须用sendto()并明确指定服务器的地址信息。

2. RRQ请求格式不完整

TFTP的读请求(RRQ)必须包含三个核心部分:

  • 操作码(2字节,RRQ固定为1)
  • 文件名(以\0结尾的字符串)
  • 传输模式(以\0结尾的字符串,比如"octet"表示二进制传输)

你的代码只加了文件名,完全没加传输模式,服务器根本识别不了这个无效请求,自然不会给你回复数据。

3. 未处理TFTP的ACK交互逻辑

TFTP是停等协议:客户端收到一个DATA包后,必须回复对应的ACK包,服务器才会发下一个包。你现在的代码只是傻等接收,不发ACK,服务器会以为客户端没收到数据,就一直不继续发,导致客户端无限循环等待。

4. 其他小问题

  • main函数里char* server, file;声明错误,file应该是char*类型,不是char;
  • 硬修改argv[1]和argv[2]的做法不规范,要么用命令行参数传,要么直接用变量赋值;
  • 接收循环没判断数据包类型(比如ERROR包),就算服务器返回错误,你也不知道,只会一直等。

修复后的完整代码

我把关键修改点都标出来了,你可以直接用:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <arpa/tftp.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int gettftp(const char *fileName, const char *serverIp)
{
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0) {
        perror("socket creation failed");
        return -1;
    }

    struct sockaddr_in server_address;
    memset(&server_address, 0, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(69);
    if (inet_pton(AF_INET, serverIp, &server_address.sin_addr) <= 0) {
        perror("invalid server IP");
        close(sock);
        return -1;
    }

    // 构造完整RRQ:opcode + 文件名 + \0 + 模式 + \0
    size_t rrq_len = sizeof(struct tftphdr) + strlen(fileName) + 1 + strlen("octet") + 1;
    struct tftphdr *read_request = (struct tftphdr *)malloc(rrq_len);
    if (!read_request) {
        perror("malloc failed");
        close(sock);
        return -1;
    }

    read_request->th_opcode = htons(RRQ);
    strcpy(read_request->th_stuff, fileName);
    // 在文件名后拼接传输模式
    strcpy(read_request->th_stuff + strlen(fileName) + 1, "octet");

    // 用sendto指定目标地址发送RRQ
    ssize_t send_len = sendto(sock, read_request, rrq_len, 0, 
                             (struct sockaddr*)&server_address, sizeof(server_address));
    if (send_len < 0) {
        perror("send RRQ failed");
        free(read_request);
        close(sock);
        return -1;
    }
    free(read_request);

    FILE *fp = fopen(fileName, "wb");
    if (!fp) {
        perror("open file failed");
        close(sock);
        return -1;
    }

    struct sockaddr_in peer_addr;
    socklen_t peer_addr_len = sizeof(peer_addr);
    uint16_t expected_block = 1;

    while (1) {
        char buffer[516]; // TFTP DATA包最大512字节数据 + 4字节头部(opcode+block)
        ssize_t bytes_received = recvfrom(sock, buffer, sizeof(buffer), 0,
                                         (struct sockaddr*)&peer_addr, &peer_addr_len);
        if (bytes_received < 0) {
            perror("recv failed");
            break;
        }

        struct tftphdr *resp = (struct tftphdr*)buffer;
        uint16_t opcode = ntohs(resp->th_opcode);

        // 处理服务器返回的ERROR包
        if (opcode == ERROR) {
            fprintf(stderr, "TFTP Error: %s\n", resp->th_stuff);
            break;
        }

        // 处理DATA包
        if (opcode == DATA) {
            uint16_t block_num = ntohs(resp->th_block);
            // 校验块编号是否符合预期
            if (block_num != expected_block) {
                fprintf(stderr, "Unexpected block: %d (expected %d)\n", block_num, expected_block);
                continue;
            }

            // 写入文件(跳过4字节的TFTP头部)
            size_t data_len = bytes_received - 4;
            fwrite(buffer + 4, 1, data_len, fp);

            // 数据长度小于512,说明是最后一个包
            if (data_len < 512) {
                printf("File transfer completed!\n");
                break;
            }

            // 构造ACK包回复服务器,确认收到当前块
            struct tftphdr ack_pkt;
            ack_pkt.th_opcode = htons(ACK);
            ack_pkt.th_block = htons(block_num);
            sendto(sock, &ack_pkt, sizeof(ack_pkt), 0,
                   (struct sockaddr*)&peer_addr, peer_addr_len);

            expected_block++;
        }
    }

    fclose(fp);
    close(sock);
    return 0;
}

int main(int argc, char* argv[])
{
    // 建议通过命令行参数传入,这里暂时用固定值方便测试
    const char* serverIp = "10.10.32.11";
    const char* fileName = "alt256";

    printf("Requesting file: \033[0;31m%s\033[0m from server: \033[0;31m%s\033[0m\n", fileName, serverIp);

    int result = gettftp(fileName, serverIp);
    if (result != 0) {
        fprintf(stderr, "File download failed\n");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

关键修改说明

  • 替换send()为sendto():明确告诉套接字要把RRQ发给服务器的69端口;
  • 补全RRQ传输模式:添加了"octet"模式,符合TFTP协议规范,服务器才能识别请求;
  • 添加ACK交互逻辑:收到DATA包后回复ACK,让服务器继续发送下一个块;
  • 区分数据包类型:处理ERROR包和DATA包,避免无限等待;
  • 完善错误处理:给套接字、文件操作添加错误检查,避免程序崩溃;
  • 简化main函数:去掉了不必要的域名解析逻辑(如果直接用IP的话,不需要getaddrinfo)。

现在运行这个代码,应该就能正常从TFTP服务器下载文件了。

内容的提问来源于stack exchange,提问作者othmane Cherai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 10:35:42