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

Linux 环境下实现简单的标准TFTP服务器

最近需要做一个在嵌入式Linux上运行的TFTP服务器程序,准备在x86环境下先试一试。

/*
TFTP 服务器
支持 RFC 1350 标准的 TFTP 协议,实现读取文件功能运行方法(gcc):
gcc -o tftp_server tftp_server.c
sudo ./tftp_server  # 需要 root 权限绑定 69 端口客户端测试:
tftp $IP
tftp> get update.bin
tftp> quitbusybox tftp 客户端
tftp -g -l target.bin -r source.bin $IP
*/#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h> // 定义TFTP服务端口,标准TFTP端口为69
#define TFTP_PORT 69// 定义缓冲区大小,TFTP数据包最大为516字节(512字节数据+4字节头部)
#define BUF_SIZE 516 // 定义要提供的文件路径
#define FILE_PATH "/data/tftp/update.bin"// 定义超时时间(秒),用于等待客户端响应
#define TIMEOUT 5     // 定义最大重试次数,防止无限重试
#define MAX_RETRIES 3 // TFTP操作码定义
#define OP_RRQ 1   // 读取请求(Read Request)
#define OP_WRQ 2   // 写入请求(Write Request)
#define OP_DATA 3  // 数据包(Data Packet)
#define OP_ACK 4   // 确认包(Acknowledgment)
#define OP_ERROR 5 // 错误包(Error Packet)// 全局变量,用于控制服务器运行状态
int running = 1;/*** 信号处理函数,用于优雅退出* 当接收到SIGINT(Ctrl+C)或SIGTERM信号时,设置running为0,使服务器退出循环* @param sig 信号编号*/
void signal_handler(int sig)
{running = 0;printf("\nShutting down TFTP server...\n");
}/*** 发送错误包给客户端* @param sock 套接字描述符* @param client_addr 客户端地址信息* @param error_code 错误代码* @param msg 错误消息*/
void send_error_packet(int sock, struct sockaddr_in *client_addr, uint16_t error_code, const char *msg)
{// 创建错误包缓冲区char err_pkt[BUF_SIZE];memset(err_pkt, 0, sizeof(err_pkt));// 构造TFTP错误包格式// 前两个字节为操作码(00 05表示ERROR)err_pkt[0] = 0;err_pkt[1] = OP_ERROR;// 接下来两个字节为错误代码(网络字节序)err_pkt[2] = (error_code >> 8) & 0xFF;  // 高字节err_pkt[3] = error_code & 0xFF;         // 低字节// 从第5个字节开始是错误消息,以NULL结尾strncpy(err_pkt + 4, msg, BUF_SIZE - 5);// 发送错误包到客户端sendto(sock, err_pkt, 4 + strlen(msg) + 1, 0,(struct sockaddr *)client_addr, sizeof(struct sockaddr_in));
}/*** 主函数,TFTP服务器入口点*/
int main()
{// 主监听套接字和数据传输套接字int sock;// 服务器地址和客户端地址结构体struct sockaddr_in server_addr, client_addr;// 客户端地址结构体长度socklen_t client_len = sizeof(client_addr);// 数据缓冲区,用于接收和发送TFTP数据包char buffer[BUF_SIZE];// 存储客户端请求的文件名和传输模式char filename[256];char mode[16];// 设置信号处理函数,用于优雅退出signal(SIGINT, signal_handler);   // Ctrl+C 信号signal(SIGTERM, signal_handler);  // 终止信号// 创建UDP套接字,用于监听TFTP请求if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0){perror("socket creation failed");exit(EXIT_FAILURE);}// 设置套接字接收超时,防止recvfrom无限等待struct timeval timeout;timeout.tv_sec = TIMEOUT;    // 秒timeout.tv_usec = 0;         // 微秒setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));// 初始化服务器地址结构体memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;           // IPv4地址族server_addr.sin_addr.s_addr = INADDR_ANY;   // 监听所有网络接口server_addr.sin_port = htons(TFTP_PORT);    // 端口69,使用网络字节序// 绑定套接字到服务器地址if (bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){perror("bind failed");close(sock);exit(EXIT_FAILURE);}printf("TFTP server listening on port %d\n", TFTP_PORT);printf("Serving file: %s\n", FILE_PATH);printf("Press Ctrl+C to stop the server\n");// 主循环,持续监听和处理TFTP请求while (running){// 接收客户端数据包ssize_t recv_len = recvfrom(sock, buffer, BUF_SIZE, 0,(struct sockaddr *)&client_addr, &client_len);// 如果接收到的数据包小于4字节(操作码2字节+至少2字节数据),则无效if (recv_len < 4)continue;// 解析TFTP操作码(前两个字节,网络字节序)uint16_t opcode = (buffer[0] << 8) | buffer[1];// 只处理读取请求(RRQ),忽略其他类型的请求if (opcode != OP_RRQ){ continue;}// 解析TFTP RRQ请求中的文件名和模式// TFTP协议中,文件名和模式是以NULL字符分隔的字符串char *ptr = buffer + 2; // 跳过操作码(2字节)// 获取文件名长度并复制到filename变量int filename_len = strlen(ptr);if (filename_len >= sizeof(filename)){filename_len = sizeof(filename) - 1;}strncpy(filename, ptr, filename_len);filename[filename_len] = '\0';// 移动指针到模式字符串位置(跳过文件名和结尾的NULL)ptr += filename_len + 1;// 获取模式字符串长度并复制到mode变量int mode_len = strlen(ptr);if (mode_len >= sizeof(mode)){mode_len = sizeof(mode) - 1;}strncpy(mode, ptr, mode_len);mode[mode_len] = '\0';// 打印客户端请求信息printf("RRQ from %s:%d for file: '%s' (mode: %s)\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port),filename, mode);// 检查请求的文件名是否为允许的文件(update.bin)if (strcmp(filename, "update.bin") != 0){printf("File not found: %s\n", filename);send_error_packet(sock, &client_addr, 1, "File not found");continue;}// 打开要传输的文件int fd = open(FILE_PATH, O_RDONLY);if (fd < 0){perror("open file failed");send_error_packet(sock, &client_addr, 1, "File not found");continue;}// 获取文件状态信息,包括文件大小struct stat st;fstat(fd, &st);off_t file_size = st.st_size;printf("Sending file size: %ld bytes\n", file_size);// 为数据传输创建新的UDP套接字// 这样可以实现TFTP协议要求的:主连接(端口69)接收请求,数据连接(随机端口)传输数据int data_sock = socket(AF_INET, SOCK_DGRAM, 0);if (data_sock < 0){perror("data socket creation failed");close(fd);continue;}// 为数据传输套接字设置相同的超时时间setsockopt(data_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));// 初始化数据传输套接字地址结构体struct sockaddr_in data_addr;memset(&data_addr, 0, sizeof(data_addr));data_addr.sin_family = AF_INET;data_addr.sin_addr.s_addr = INADDR_ANY;data_addr.sin_port = htons(0); // 端口设为0,让系统自动分配可用端口// 绑定数据传输套接字到自动分配的端口if (bind(data_sock, (struct sockaddr *)&data_addr, sizeof(data_addr)) < 0){perror("bind data socket failed");close(fd);close(data_sock);continue;}// 获取系统分配的数据传输端口号socklen_t addr_len = sizeof(data_addr);getsockname(data_sock, (struct sockaddr *)&data_addr, &addr_len);printf("Using data port: %d\n", ntohs(data_addr.sin_port));// 开始发送数据块,从块号1开始uint16_t block_num = 1;ssize_t bytes_read;  // 每次读取的字节数int retries = 0;     // 重试计数器// 循环发送文件数据,直到文件传输完成while (running && (bytes_read = read(fd, buffer + 4, 512)) >= 0){// 构造DATA数据包头部// 前两个字节为操作码(00 03表示DATA)buffer[0] = 0;buffer[1] = OP_DATA;// 接下来两个字节为块号(网络字节序)buffer[2] = (block_num >> 8) & 0xFF;  // 块号高字节buffer[3] = block_num & 0xFF;         // 块号低字节// 发送数据包到客户端if (sendto(data_sock, buffer, bytes_read + 4, 0,(struct sockaddr *)&client_addr, client_len) < 0){perror("sendto failed");break;}// 等待客户端的ACK确认包struct sockaddr_in ack_addr;socklen_t ack_len = sizeof(ack_addr);ssize_t ack_len_received = recvfrom(data_sock, buffer, BUF_SIZE, 0,(struct sockaddr *)&ack_addr, &ack_len);// 如果接收到的数据包长度小于4字节,说明无效if (ack_len_received < 4){// 检查是否是超时错误if (errno == EAGAIN || errno == EWOULDBLOCK){retries++;// 如果重试次数超过最大限制,则放弃传输if (retries >= MAX_RETRIES){printf("Max retries exceeded for block %d\n", block_num);break;}printf("Timeout for block %d, retrying...\n", block_num);continue; // 重新发送当前数据块}break;}// 解析接收到的数据包操作码和块号uint16_t ack_opcode = (buffer[0] << 8) | buffer[1];uint16_t ack_block = (buffer[2] << 8) | buffer[3];// 如果接收到错误包if (ack_opcode == OP_ERROR){printf("Received ERROR packet, code: %d\n", ack_block);break;}// 如果不是ACK确认包if (ack_opcode != OP_ACK){printf("Expected ACK, got opcode %d\n", ack_opcode);// 如果收到的是DATA包(可能是重传),则忽略并继续等待ACKif (ack_opcode == OP_DATA){printf("Received DATA instead of ACK, ignoring...\n");continue;}break;}// 检查ACK包中的块号是否正确if (ack_block != block_num){printf("Expected ACK for block %d, got ACK for block %d\n",block_num, ack_block);// 如果是重复的ACK(上一个块的确认),则继续发送下一个块if (ack_block == block_num - 1){printf("Duplicate ACK received, continuing...\n");continue;}break;}// 成功发送并收到确认,打印传输信息printf("Sent block %d (%ld bytes)\n", block_num, bytes_read);// 增加块号,重置重试计数器block_num++;retries = 0;// 如果读取的字节数小于512,说明这是最后一个数据块if (bytes_read < 512){printf("Last block sent, transfer completed.\n");break; // 退出传输循环}}// 关闭文件和数据传输套接字close(fd);close(data_sock);printf("Transfer session ended.\n\n");}// 关闭主监听套接字,服务器退出close(sock);printf("TFTP server stopped.\n");return 0;
}

这个程序使用本地模型写的注释,效果还挺不错的。

运行起来出现一些问题。本地小模型确实难以解决。还是需要靠云端的[笑哭]

http://www.dtcms.com/a/525881.html

相关文章:

  • const和explicit关键字
  • 建设植绒衣架网站wordpress discuz论坛模板
  • MapAnything: 通用前馈式度量3D重建
  • (springboot+vue前后端分离部署)阿里云windows服务器部署
  • 优质聊城做网站费用杭州 app开发公司
  • springboot——@Scheduled为什么顺序执行
  • 做一个网站需要多少人域名查询网中国万网
  • 【Java面向对象编程(OOP)的三大基本特性】
  • 潍坊网站商品网站怎么做的
  • 响应式网站页面设计福彩网站开发
  • 专做婚纱店设计网站网站设计软件开发
  • 上海网站建设口碑最好的公司低成本门户网站开发
  • Watch and Learn: Semi-Supervised Learning of Object Detectors from Videos
  • 北京网站开发报价到那个网站做翻译接单
  • 云蛇吞路懂车赛-游戏程序系统方案
  • 自己做网站不想买空间 自己电脑可以做服务器吗?yu网站建设
  • 网站建设费能入长期待摊吗网站建设及网页设计教案
  • h5网站欣赏wordpress搜索调用
  • DepthAI V3.1.0 正式版发布!
  • 网络课程网站开发过程东莞长安网站设计公司
  • UVa 11183 Teen Girl Squad
  • 医疗教育的网站建设山西公司网站建设
  • CompositionLocal 用法
  • 怎样设计一个网站平台免费seo课程
  • EFM8开发系列
  • 哈尔滨网站优化咨询wordpress哪个模版好用
  • 网站策划ppt自己申请网站空间
  • 阿里云国际站GPU:阿里云GPU怎么释放实例?
  • Linux网络UDP(10)
  • 网站培训制度wap网站开发流程