TFTP服务端开源代码分析
·
TFTP服务端开源代码分析
本文件详细分析TFTP服务端的实现,包括数据结构、层次关系和工作流程。
1. 文件结构
server.c- 服务端主应用逻辑server_comm.c- 套接字操作的通信模块server_comm.h- 服务端通信模块的头文件work_thread.c- 工作线程处理模块work_thread.h- 工作线程处理模块的头文件tftpx.h- 通用TFTP协议定义
2. 数据结构
2.1 struct tftpx_packet (tftpx.h)
struct tftpx_packet {
uint16_t cmd; // 操作码
uint16_t block; // 块编号
char data[512]; // 数据内容
};
- cmd: 操作码,取值为RRQ(1)、WRQ(2)、DATA(3)、ACK(4)、ERROR(5)
- block: 数据块编号,从1开始递增
- data: 数据内容,最大512字节
2.2 struct sockaddr_in (server_comm.c)
struct sockaddr_in server;
- sin_family: 地址族,通常为AF_INET
- sin_port: 端口号,使用htons()转换为网络字节序
- sin_addr: IP地址,使用inet_pton()转换为二进制格式
2.3 struct tftpx_request (work_thread.h)
struct tftpx_request {
struct tftpx_packet packet;
struct sockaddr_in client;
int size;
};
- packet: TFTP数据包
- client: 客户端地址信息
- size: 数据包大小
3. 层次关系
+-------------------+ +-------------------+
| server.c | | work_thread.c |
+-------------------+ +-------------------+
| 主服务层 | -> | 业务逻辑层 |
| - 初始化 | | - 请求处理 |
| - 监听请求 | | - 文件传输 |
+-------------------+ | - 线程管理 |
+-------------------+
|
v
+-------------------+
| server_comm.c |
+-------------------+
| 通信层 |
| - 套接字操作 |
| - 数据包发送 |
| - 数据包接收 |
+-------------------+
3.1 层次说明
- server.c: 主服务层,负责初始化服务器和监听客户端请求,调用server_comm.c进行通信
- work_thread.c: 业务逻辑层,处理具体的TFTP请求(RRQ、WRQ、LIST),调用server_comm.c进行通信
- server_comm.c: 通信层,实现底层套接字操作,向上提供封装好的接口,被server.c和work_thread.c调用
- tftpx.h: 协议定义层,定义TFTP协议的通用常量和结构,被所有模块引用
4. 初始化流程
4.1 命令行参数解析 (server.c:main)
// 解析命令行端口参数
if(argc > 1){
port = (unsigned short)atoi(argv[1]);
}
- 解析命令行参数,获取服务器端口号
- 默认端口为10220
4.2 服务器初始化 (server_comm.c:server_comm_init)
/**
* @brief 初始化服务器套接字通信
*
* 创建UDP监听套接字并绑定到指定端口
*
* @param port 服务器监听端口号
* @return 成功返回0,失败返回-1
*/
int server_comm_init(unsigned short port) {
struct sockaddr_in server;
/* 创建UDP监听套接字 */
if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0){
printf("Server socket could not be created.\n");
return -1;
}
/* 初始化服务器地址结构,绑定到任意可用地址 */
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = INADDR_ANY;
/* 绑定套接字到指定端口 */
if(bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0){
printf("Bind failed.\n");
return -1;
}
return 0;
}
- 创建UDP套接字
- 绑定服务器地址和端口
- 开始监听客户端请求
5. 请求处理流程
5.1 接收客户端请求 (server.c:main)
// 从客户端接收数据包
request->size = server_comm_recvfrom(
&(request->packet), MAX_REQUEST_SIZE,
(struct sockaddr *) &(request->client),
&addr_len, 0);
// 转换网络字节序
request->packet.cmd = ntohs(request->packet.cmd);
printf("Receive request.\n");
// 创建工作线程处理请求
if(pthread_create(&t_id, NULL, work_thread, request) != 0){
printf("Can't create thread.\n");
}
- 接收客户端发送的TFTP请求
- 创建工作线程处理请求
5.2 工作线程初始化 (work_thread.c:work_thread)
/* 初始化工作线程通信套接字 */
if(server_comm_connect_init() < 0){
free(request);
return NULL;
}
/* 连接到客户端 */
if(server_comm_connect_client((struct sockaddr*)&(request->client), addr_len) < 0){
free(request);
server_comm_connect_close();
return NULL;
}
- 初始化客户端连接套接字
- 连接到客户端
5.3 请求分发 (work_thread.c:work_thread)
/* 根据请求类型选择对应的处理函数 */
switch(request->packet.cmd){
case CMD_RRQ:
printf("handle_rrq called.\n");
handle_rrq(request);
break;
case CMD_WRQ:
printf("handle_wrq called.\n");
handle_wrq(request);
break;
case CMD_LIST:
printf("handle_list called.\n");
handle_list(request);
break;
default:
printf("Illegal TFTP operation.\n");
break;
}
- 根据请求类型分发到不同的处理函数
- 支持RRQ(读请求)、WRQ(写请求)和LIST(列表请求)
6. 文件下载流程(RRQ操作)
6.1 处理读请求 (work_thread.c:handle_rrq)
/**
* @brief 处理读文件请求(RRQ)
*
* 读取请求的文件并以数据包形式发送给客户端
*
* @param request 指向 tftpx_request 结构体的指针,包含客户端请求信息
*/
void handle_rrq(struct tftpx_request *request){
struct tftpx_packet snd_packet;
char fullpath[256];
char *r_path = request->packet.filename; /* 请求文件 */
char *mode = r_path + strlen(r_path) + 1;
char *blocksize_str = mode + strlen(mode) + 1;
int blocksize = atoi(blocksize_str);
/* 验证并设置块大小 */
if(blocksize <= 0 || blocksize > DATA_SIZE){
blocksize = DATA_SIZE;
}
/* 检查路径长度是否超出限制 */
if(strlen(r_path) + strlen(conf_document_root) > sizeof(fullpath) - 1){
printf("Request path too long. %d\n", strlen(r_path) + strlen(conf_document_root));
return;
}
/* 构建完整路径 */
memset(fullpath, 0, sizeof(fullpath));
strcpy(fullpath, conf_document_root);
if(r_path[0] != '/'){
strcat(fullpath, "/");
}
strcat(fullpath, r_path);
printf("rrq: \"%s\", blocksize=%d\n", fullpath, blocksize);
FILE *fp = fopen(fullpath, "r");
if(fp == NULL){
printf("File not exists!\n");
return;
}
int s_size = 0;
ushort block = 1;
snd_packet.cmd = htons(CMD_DATA);
do{
memset(snd_packet.data, 0, sizeof(snd_packet.data));
snd_packet.block = htons(block);
s_size = fread(snd_packet.data, 1, blocksize, fp);
if(server_comm_connect_send_packet(&snd_packet, s_size + 4) == -1){
fprintf(stderr, "Error occurs when sending packet.block = %d.\n", block);
goto rrq_error;
}
block ++;
}while(s_size == blocksize);
printf("\nSend file end.\n");
rrq_error:
fclose(fp);
return;
}
- 打开客户端请求的文件
- 分块读取文件内容
- 发送数据块到客户端
- 等待客户端的ACK响应
6.2 发送数据块 (work_thread.c:handle_rrq)
if(server_comm_connect_send_packet(&snd_packet, s_size + 4) == -1){
fprintf(stderr, "Error occurs when sending packet.block = %d.\n", block);
goto rrq_error;
}
- 使用server_comm_connect_send_packet函数发送数据块
- 等待客户端的ACK响应
- 超时重传机制
7. 文件上传流程(WRQ操作)
7.1 处理写请求 (work_thread.c:handle_wrq)
/**
* @brief 处理写文件请求(WRQ)
*
* 接收客户端上传的数据并写入文件
*
* @param request 指向 tftpx_request 结构体的指针,包含客户端请求信息
*/
void handle_wrq(struct tftpx_request *request){
struct tftpx_packet ack_packet, rcv_packet;
char fullpath[256];
char *r_path = request->packet.filename; /* 请求文件 */
char *mode = r_path + strlen(r_path) + 1;
char *blocksize_str = mode + strlen(mode) + 1;
int blocksize = atoi(blocksize_str);
/* 验证并设置块大小 */
if(blocksize <= 0 || blocksize > DATA_SIZE){
blocksize = DATA_SIZE;
}
/* 检查路径长度是否超出限制 */
if(strlen(r_path) + strlen(conf_document_root) > sizeof(fullpath) - 1){
printf("Request path too long. %d\n", strlen(r_path) + strlen(conf_document_root));
return;
}
/* 构建完整路径 */
memset(fullpath, 0, sizeof(fullpath));
strcpy(fullpath, conf_document_root);
if(r_path[0] != '/'){
strcat(fullpath, "/");
}
strcat(fullpath, r_path);
printf("wrq: \"%s\", blocksize=%d\n", fullpath, blocksize);
/* 检查文件是否已存在 */
FILE *fp = fopen(fullpath, "r");
if(fp != NULL){
/* 发送错误包 */
fclose(fp);
printf("File \"%s\" already exists.\n", fullpath);
return;
}
/* 创建文件 */
fp = fopen(fullpath, "w");
if(fp == NULL){
printf("File \"%s\" create error.\n", fullpath);
return;
}
/* 发送初始ACK(块号为0) */
ack_packet.cmd = htons(CMD_ACK);
ack_packet.block = htons(0);
if(server_comm_connect_send_ack(&ack_packet, 4) == -1){
fprintf(stderr, "Error occurs when sending ACK = %d.\n", 0);
goto wrq_error;
}
int s_size = 0;
int r_size = 0;
ushort block = 1;
int time_wait_data;
do{
/* 等待接收数据包 */
for(time_wait_data = 0; time_wait_data < PKT_RCV_TIMEOUT * PKT_MAX_RXMT; time_wait_data += 20000){
/* 尝试接收(非阻塞接收) */
r_size = server_comm_connect_recv(&rcv_packet, sizeof(struct tftpx_packet), MSG_DONTWAIT);
if(r_size > 0 && r_size < 4){
printf("Bad packet: r_size=%d, blocksize=%d\n", r_size, blocksize);
}
if(r_size >= 4 && rcv_packet.cmd == htons(CMD_DATA) && rcv_packet.block == htons(block)){
printf("DATA: block=%d, data_size=%d\n", ntohs(rcv_packet.block), r_size - 4);
/* 有效数据包,写入文件 */
fwrite(rcv_packet.data, 1, r_size - 4, fp);
break;
}
usleep(20000);
}
if(time_wait_data >= PKT_RCV_TIMEOUT * PKT_MAX_RXMT){
printf("Receive timeout.\n");
goto wrq_error;
break;
}
/* 发送ACK确认 */
ack_packet.block = htons(block);
if(server_comm_connect_send_ack(&ack_packet, 4) == -1){
fprintf(stderr, "Error occurs when sending ACK = %d.\n", block);
goto wrq_error;
}
printf("Send ACK=%d\n", block);
block ++;
}while(r_size == blocksize + 4);
printf("Receive file end.\n");
wrq_error:
fclose(fp);
return;
}
- 创建客户端请求的文件
- 发送ACK响应
- 接收客户端发送的数据块
- 将数据块写入文件
7.2 接收数据块 (work_thread.c:handle_wrq)
/* 等待接收数据包 */
for(time_wait_data = 0; time_wait_data < PKT_RCV_TIMEOUT * PKT_MAX_RXMT; time_wait_data += 20000){
/* 尝试接收(非阻塞接收) */
r_size = server_comm_connect_recv(&rcv_packet, sizeof(struct tftpx_packet), MSG_DONTWAIT);
if(r_size > 0 && r_size < 4){
printf("Bad packet: r_size=%d, blocksize=%d\n", r_size, blocksize);
}
if(r_size >= 4 && rcv_packet.cmd == htons(CMD_DATA) && rcv_packet.block == htons(block)){
printf("DATA: block=%d, data_size=%d\n", ntohs(rcv_packet.block), r_size - 4);
/* 有效数据包,写入文件 */
fwrite(rcv_packet.data, 1, r_size - 4, fp);
break;
}
usleep(20000);
}
- 使用server_comm_connect_recv函数接收数据块
- 发送ACK响应
- 超时重传机制
8. server_comm.c中的关键函数
8.1 server_comm_init
初始化服务器套接字通信
/**
* @brief 初始化服务器套接字通信
*
* 创建UDP监听套接字并绑定到指定端口
*
* @param port 服务器监听端口号
* @return 成功返回0,失败返回-1
*/
int server_comm_init(unsigned short port) {
struct sockaddr_in server;
/* 创建UDP监听套接字 */
if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0){
printf("Server socket could not be created.\n");
return -1;
}
/* 初始化服务器地址结构,绑定到任意可用地址 */
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = INADDR_ANY;
/* 绑定套接字到指定端口 */
if(bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0){
printf("Bind failed.\n");
return -1;
}
return 0;
}
8.2 server_comm_send_to
向特定地址发送数据
/**
* @brief 向指定地址发送数据
*
* @param data 待发送的数据缓冲区指针
* @param len 待发送的数据长度(字节)
* @param dest 目标地址结构指针
* @param dest_len 目标地址结构长度
* @return 成功返回实际发送的字节数,失败返回-1
*/
int server_comm_send_to(const void *data, size_t len, struct sockaddr *dest, socklen_t dest_len) {
return sendto(sock, data, len, 0, dest, dest_len);
}
8.3 server_comm_recvfrom
从客户端接收数据
/**
* @brief 从客户端接收数据
*
* 从客户端接收数据并记录发送方地址信息
*
* @param buf 接收数据的缓冲区指针
* @param len 缓冲区大小(字节)
* @param sender 发送方地址结构指针
* @param sender_len 发送方地址结构长度指针
* @param flags 接收标志
* @return 成功返回实际接收的字节数,失败返回-1
*/
int server_comm_recvfrom(void *buf, size_t len, struct sockaddr *sender, socklen_t *sender_len, int flags) {
return recvfrom(sock, buf, len, flags, sender, sender_len);
}
8.4 server_comm_connect_init
初始化客户端连接套接字
/**
* @brief 初始化工作线程套接字通信
*
* 创建用于与客户端通信的工作套接字
*
* @return 成功返回0,失败返回-1
*/
int server_comm_connect_init() {
struct sockaddr_in server;
/* 创建工作线程UDP套接字 */
if((connect_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0){
printf("work_thread socket could not be created.\n");
return -1;
}
/* 初始化服务器地址结构 */
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = 0; /* 让操作系统自动分配端口 */
/* 绑定套接字 */
if (bind(connect_sock, (struct sockaddr *)&server, sizeof(server)) < 0){
printf("work_thread bind failed.\n");
return -1;
}
return 0;
}
8.5 server_comm_connect_client
连接到客户端
/**
* @brief 连接到客户端
*
* 将工作套接字连接到指定的客户端地址
*
* @param client 客户端地址结构指针
* @param client_len 客户端地址结构长度
* @return 成功返回0,失败返回-1
*/
int server_comm_connect_client(struct sockaddr *client, socklen_t client_len) {
if(connect(connect_sock, client, client_len) < 0){
printf("Can't connect to client.\n");
return -1;
}
return 0;
}
8.6 server_comm_connect_send
向连接的客户端发送数据
/**
* @brief 向已连接的客户端发送数据
*
* @param data 待发送的数据缓冲区指针
* @param len 待发送的数据长度(字节)
* @return 成功返回实际发送的字节数,失败返回-1
*/
int server_comm_connect_send(const void *data, size_t len) {
return send(connect_sock, data, len, 0);
}
8.7 server_comm_connect_recv
从连接的客户端接收数据
/**
* @brief 从已连接的客户端接收数据
*
* @param buf 接收数据的缓冲区指针
* @param len 缓冲区大小(字节)
* @param flags 接收标志
* @return 成功返回实际接收的字节数,失败返回-1
*/
int server_comm_connect_recv(void *buf, size_t len, int flags) {
return recv(connect_sock, buf, len, flags);
}
8.8 server_comm_connect_send_ack
发送ACK数据包
/**
* @brief 向已连接的客户端发送ACK确认包
*
* @param data 待发送的ACK数据缓冲区指针
* @param len 待发送的数据长度(字节)
* @return 成功返回实际发送的字节数,失败返回-1
*/
int server_comm_connect_send_ack(const void *data, size_t len) {
return send(connect_sock, data, len, 0);
}
8.9 server_comm_connect_send_packet
发送数据包并等待ACK
/**
* @brief 发送数据并等待ACK确认
*
* 带重传机制的可靠数据传输实现
*
* @param data 待发送的数据缓冲区指针
* @param len 待发送的数据长度(字节)
* @return 成功返回发送的字节数,失败返回-1
*/
int server_comm_connect_send_packet(const void *data, size_t len) {
struct tftpx_packet rcv_packet;
int time_wait_ack = 0;
int rxmt = 0;
int r_size = 0;
/* 循环重传直到收到有效ACK或达到最大重传次数 */
for(rxmt = 0; rxmt < PKT_MAX_RXMT; rxmt ++){
printf("Send block=%d\n", ntohs(((struct tftpx_packet *)data)->block));
/* 发送数据包 */
if(send(connect_sock, data, len, 0) != len){
return -1;
}
/* 等待ACK超时检测 */
for(time_wait_ack = 0; time_wait_ack < PKT_RCV_TIMEOUT; time_wait_ack += 10000){
/* 尝试非阻塞接收 */
r_size = recv(connect_sock, &rcv_packet, sizeof(struct tftpx_packet), MSG_DONTWAIT);
/* 检查是否为有效的ACK响应 */
if(r_size >= 4 && rcv_packet.cmd == htons(CMD_ACK) && rcv_packet.block == ((struct tftpx_packet *)data)->block){
/* 收到有效ACK,退出等待 */
break;
}
usleep(10000);
}
/* 收到ACK,退出重传循环 */
if(time_wait_ack < PKT_RCV_TIMEOUT){
break;
}else{
/* 需要重传 */
continue;
}
}
/* 超过最大重传次数,发送超时 */
if(rxmt == PKT_MAX_RXMT){
/* 发送超时 */
printf("Sent packet exceeded PKT_MAX_RXMT.\n");
return -1;
}
return len;
}
8.10 server_comm_close
关闭服务器套接字
/**
* @brief 关闭服务器监听套接字
*
* 关闭并释放服务器监听套接字资源
*/
void server_comm_close() {
if(sock >= 0) {
close(sock);
sock = -1;
}
}
8.11 server_comm_connect_close
关闭客户端连接套接字
/**
* @brief 关闭工作线程套接字
*
* 关闭并释放工作线程套接字资源
*/
void server_comm_connect_close() {
if(connect_sock >= 0) {
close(connect_sock);
connect_sock = -1;
}
}
9. work_thread.c中的关键函数
9.1 work_thread
工作线程主函数
/**
* @brief 工作线程主函数
*
* 处理客户端请求的工作线程入口函数,根据请求类型分发到相应的处理函数
*
* @param arg 指向 tftpx_request 结构体的指针,包含客户端请求信息
* @return 总是返回 NULL
*/
void *work_thread(void *arg){
struct tftpx_request *request = (struct tftpx_request *)arg;
static socklen_t addr_len = sizeof(struct sockaddr_in);
/* 检查请求大小是否合法 */
if(request->size <= 0){
printf("Bad request.\n");
free(request);
return NULL;
}
printf("work_thread started.\n");
/* 初始化工作线程通信套接字 */
if(server_comm_connect_init() < 0){
free(request);
return NULL;
}
/* 连接到客户端 */
if(server_comm_connect_client((struct sockaddr*)&(request->client), addr_len) < 0){
free(request);
server_comm_connect_close();
return NULL;
}
/* 根据请求类型选择对应的处理函数 */
switch(request->packet.cmd){
case CMD_RRQ:
printf("handle_rrq called.\n");
handle_rrq(request);
break;
case CMD_WRQ:
printf("handle_wrq called.\n");
handle_wrq(request);
break;
case CMD_LIST:
printf("handle_list called.\n");
handle_list(request);
break;
default:
printf("Illegal TFTP operation.\n");
break;
}
/* test{
memset(request, 0, sizeof(struct tftpx_request));
}*/
free(request);
server_comm_connect_close();
return NULL;
}
9.2 handle_rrq
处理读请求(RRQ)
/**
* @brief 处理读文件请求(RRQ)
*
* 读取请求的文件并以数据包形式发送给客户端
*
* @param request 指向 tftpx_request 结构体的指针,包含客户端请求信息
*/
void handle_rrq(struct tftpx_request *request){
struct tftpx_packet snd_packet;
char fullpath[256];
char *r_path = request->packet.filename; /* 请求文件 */
char *mode = r_path + strlen(r_path) + 1;
char *blocksize_str = mode + strlen(mode) + 1;
int blocksize = atoi(blocksize_str);
/* 验证并设置块大小 */
if(blocksize <= 0 || blocksize > DATA_SIZE){
blocksize = DATA_SIZE;
}
/* 检查路径长度是否超出限制 */
if(strlen(r_path) + strlen(conf_document_root) > sizeof(fullpath) - 1){
printf("Request path too long. %d\n", strlen(r_path) + strlen(conf_document_root));
return;
}
/* 构建完整路径 */
memset(fullpath, 0, sizeof(fullpath));
strcpy(fullpath, conf_document_root);
if(r_path[0] != '/'){
strcat(fullpath, "/");
}
strcat(fullpath, r_path);
printf("rrq: \"%s\", blocksize=%d\n", fullpath, blocksize);
/*if(!strncasecmp(mode, "octet", 5) && !strncasecmp(mode, "netascii", 8)){
// 发送错误包
return;
}*/
FILE *fp = fopen(fullpath, "r");
if(fp == NULL){
printf("File not exists!\n");
/* 发送错误包
snd_packet.cmd = htons(CMD_ERROR);
snd_packet.code = htons(1);
strcpy(snd_packet.data, "File not found!");
send_ack(sock, &snd_packet, 4 + strlen(snd_packet.data) + 1);
*/
return;
}
int s_size = 0;
ushort block = 1;
snd_packet.cmd = htons(CMD_DATA);
do{
memset(snd_packet.data, 0, sizeof(snd_packet.data));
snd_packet.block = htons(block);
s_size = fread(snd_packet.data, 1, blocksize, fp);
if(server_comm_connect_send_packet(&snd_packet, s_size + 4) == -1){
fprintf(stderr, "Error occurs when sending packet.block = %d.\n", block);
goto rrq_error;
}
block ++;
}while(s_size == blocksize);
printf("\nSend file end.\n");
rrq_error:
fclose(fp);
return;
}
9.3 handle_wrq
处理写请求(WRQ)
/**
* @brief 处理写文件请求(WRQ)
*
* 接收客户端上传的数据并写入文件
*
* @param request 指向 tftpx_request 结构体的指针,包含客户端请求信息
*/
void handle_wrq(struct tftpx_request *request){
struct tftpx_packet ack_packet, rcv_packet;
char fullpath[256];
char *r_path = request->packet.filename; /* 请求文件 */
char *mode = r_path + strlen(r_path) + 1;
char *blocksize_str = mode + strlen(mode) + 1;
int blocksize = atoi(blocksize_str);
/* 验证并设置块大小 */
if(blocksize <= 0 || blocksize > DATA_SIZE){
blocksize = DATA_SIZE;
}
/* 检查路径长度是否超出限制 */
if(strlen(r_path) + strlen(conf_document_root) > sizeof(fullpath) - 1){
printf("Request path too long. %d\n", strlen(r_path) + strlen(conf_document_root));
return;
}
/* 构建完整路径 */
memset(fullpath, 0, sizeof(fullpath));
strcpy(fullpath, conf_document_root);
if(r_path[0] != '/'){
strcat(fullpath, "/");
}
strcat(fullpath, r_path);
printf("wrq: \"%s\", blocksize=%d\n", fullpath, blocksize);
/*if(!strncasecmp(mode, "octet", 5) && !strncasecmp(mode, "netascii", 8)){
// 发送错误包
return;
}*/
/* 检查文件是否已存在 */
FILE *fp = fopen(fullpath, "r");
if(fp != NULL){
/* 发送错误包 */
fclose(fp);
printf("File \"%s\" already exists.\n", fullpath);
return;
}
/* 创建文件 */
fp = fopen(fullpath, "w");
if(fp == NULL){
printf("File \"%s\" create error.\n", fullpath);
return;
}
/* 发送初始ACK(块号为0) */
ack_packet.cmd = htons(CMD_ACK);
ack_packet.block = htons(0);
if(server_comm_connect_send_ack(&ack_packet, 4) == -1){
fprintf(stderr, "Error occurs when sending ACK = %d.\n", 0);
goto wrq_error;
}
int s_size = 0;
int r_size = 0;
ushort block = 1;
int time_wait_data;
do{
/* 等待接收数据包 */
for(time_wait_data = 0; time_wait_data < PKT_RCV_TIMEOUT * PKT_MAX_RXMT; time_wait_data += 20000){
/* 尝试接收(非阻塞接收) */
r_size = server_comm_connect_recv(&rcv_packet, sizeof(struct tftpx_packet), MSG_DONTWAIT);
if(r_size > 0 && r_size < 4){
printf("Bad packet: r_size=%d, blocksize=%d\n", r_size, blocksize);
}
if(r_size >= 4 && rcv_packet.cmd == htons(CMD_DATA) && rcv_packet.block == htons(block)){
printf("DATA: block=%d, data_size=%d\n", ntohs(rcv_packet.block), r_size - 4);
/* 有效数据包,写入文件 */
fwrite(rcv_packet.data, 1, r_size - 4, fp);
break;
}
usleep(20000);
}
if(time_wait_data >= PKT_RCV_TIMEOUT * PKT_MAX_RXMT){
printf("Receive timeout.\n");
goto wrq_error;
break;
}
/* 发送ACK确认 */
ack_packet.block = htons(block);
if(server_comm_connect_send_ack(&ack_packet, 4) == -1){
fprintf(stderr, "Error occurs when sending ACK = %d.\n", block);
goto wrq_error;
}
printf("Send ACK=%d\n", block);
block ++;
}while(r_size == blocksize + 4);
printf("Receive file end.\n");
wrq_error:
fclose(fp);
return;
}
9.4 handle_list
/**
* @brief 处理目录列表请求
*
* 读取指定目录的内容,并将文件列表以 DATA 包的形式发送给客户端
*
* @param request 指向 tftpx_request 结构体的指针,包含客户端请求信息
*/
void handle_list(struct tftpx_request *request){
char fullpath[256];
char data_buf[LIST_BUF_SIZE + 256 + 32];
char *r_path = request->packet.data; /* 请求路径 */
int data_size; /* data_buf[] 中的数据大小 */
struct tftpx_packet packet;
/* 检查路径长度是否超出限制 */
if(strlen(r_path) + strlen(conf_document_root) > sizeof(fullpath) - 1){
printf("Request path too long. %d\n", strlen(r_path) + strlen(conf_document_root));
return;
}
/* 构建完整路径 */
memset(fullpath, 0, sizeof(fullpath));
strcpy(fullpath, conf_document_root);
if(r_path[0] != '/'){
strcat(fullpath, "/");
}
strcat(fullpath, r_path);
printf("List %s\n", fullpath);
struct stat stat_buf;
struct dirent *dirent;
DIR *dp;
stat(fullpath, &stat_buf);
/* 检查是否为目录 */
if(!S_ISDIR(stat_buf.st_mode)){
printf("\"%s\" is not a directory.\n", fullpath);
packet.cmd = htons(CMD_ERROR);
packet.code = 0;
sprintf(packet.data, "\"%s\" is not a directory!\n", r_path);
server_comm_connect_send_packet(&packet, strlen(packet.data) + 4);
return;
}
char *ptr = fullpath + strlen(fullpath);
data_size = 0;
dp = opendir(fullpath);
/* 填充目录列表缓冲区 */
while((dirent = readdir(dp)) != NULL){
if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0){
continue;
}
ptr[0] = '/';
strcpy(ptr + 1, dirent->d_name);
*(ptr + 1 + strlen(dirent->d_name)) = '\0';
stat(fullpath, &stat_buf);
char mod = S_ISDIR(stat_buf.st_mode)? 'd' : 'f'; /* 文件类型标识 */
data_size += sprintf(data_buf + data_size,
"%c\t%d\t%s\t\n",
mod, (int)stat_buf.st_size, dirent->d_name);
if(data_size >= LIST_BUF_SIZE){
/* 目录文件太多! */
printf("SEND_BUF_SIZE too small!\n");
return;
}
}
/*printf("%s", data_buf);
return;*/
ushort block;
packet.cmd = htons(CMD_DATA);
for(block=1; block<data_size/DATA_SIZE + 1; block++){
/* 转换为网络字节序 */
packet.block = htons(block);
memcpy(packet.data, data_buf + DATA_SIZE * (block - 1), DATA_SIZE);
if(server_comm_connect_send_packet(&packet, DATA_SIZE + 4) == -1){
printf("Error occurs when sending Packet %d.", block);
return;
}
usleep(PKT_TIME_INTERVAL);
}
/* 发送最后一个数据包 */
packet.block = htons(block);
memcpy(packet.data, data_buf + DATA_SIZE * (block - 1), data_size - DATA_SIZE * (block - 1));
if(server_comm_connect_send_packet(&packet, data_size - DATA_SIZE * (block - 1) + 4) == -1){
printf("Error occurs when sending the last packet.");
return;
}
printf("Sent %d packet(s).\n", block);
}
处理列表请求(LIST)
10. server.c中的关键函数
10.1 main
/**
* TFTP服务器主函数
*
* 初始化并启动TFTP服务器,监听指定端口接收客户端请求,
* 为每个请求创建独立的工作线程进行处理。
*
* @param argc 命令行参数个数
* @param argv 命令行参数数组,argv[1]可选指定端口号,默认为10220
* @return 程序退出状态码,成功返回0
*/
int main (int argc, char **argv){
int done = 0; // Server exit.
socklen_t addr_len;
pthread_t t_id;
unsigned short port = SERVER_PORT;
// 打印使用说明
printf("Usage: %s [port]\n", argv[0]);
printf(" port - default 10220\n");
// 解析命令行端口参数
if(argc > 1){
port = (unsigned short)atoi(argv[1]);
}
config();
// 初始化服务器通信
if(server_comm_init(port) < 0){
return 0;
}
printf("Server started at 0.0.0.0:%d.\n", port);
struct tftpx_request *request;
addr_len = sizeof(struct sockaddr_in);
// 主服务器循环
while(!done){
// 分配请求结构体
request = (struct tftpx_request *)malloc(sizeof(struct tftpx_request));
memset(request, 0, sizeof(struct tftpx_request));
// 从客户端接收数据包
request->size = server_comm_recvfrom(
&(request->packet), MAX_REQUEST_SIZE,
(struct sockaddr *) &(request->client),
&addr_len, 0);
// 转换网络字节序
request->packet.cmd = ntohs(request->packet.cmd);
printf("Receive request.\n");
// 创建工作线程处理请求
if(pthread_create(&t_id, NULL, work_thread, request) != 0){
printf("Can't create thread.\n");
}
}
return 0;
}
主入口点,初始化服务器并监听请求
10.2 config
/**
* 配置服务器参数
*
* 设置TFTP服务器的文档根目录,默认为当前目录
*/
void config(){
conf_document_root = ".";//"/home/ideawu/books";
}
配置服务器参数
11. TFTP协议细节
- 11.1 使用UDP协议进行通信
- 11.2 默认端口:69
- 11.3 支持RRQ(读请求)和WRQ(写请求)
- 11.4 数据以512字节的块传输
- 11.5 每个块由接收方确认
- 11.6 最后一个块的大小小于512字节
12. 错误处理
- 12.1 数据包传输的超时处理
- 12.2 丢失数据包的重传机制
- 12.3 处理客户端返回的错误数据包
- 12.4 向用户报告连接错误
13. 完整源码
下载链接:https://download.csdn.net/download/wxmtwfx/92725660
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)