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
相关产品推荐
相关产品推荐

