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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐