TFTP客户端开源代码分析

本文件详细分析TFTP客户端的实现,包括数据结构、层次关系和工作流程。

1. 文件结构

  • client.c - 客户端主应用逻辑文件
  • client_comm.c - 通信模块的实现文件
  • client_comm.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 (client_comm.c)

struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
inet_pton(AF_INET, server_ip, &(server.sin_addr.s_addr));
  • sin_family: 地址族,固定为AF_INET(IPv4)
  • sin_port: 端口号,使用htons()转换为网络字节序
  • sin_addr: IP地址,使用inet_pton()转换为二进制格式

3. 层次关系

+-------------------+
|    client.c       |
+-------------------+
|  业务逻辑层       |
|  - 命令解析       |
|  - 流程控制       |
+-------------------+
          |
          v
+-------------------+
|  client_comm.c    |
+-------------------+
|  通信层           |
|  - 套接字操作     |
|  - 数据包发送     |
|  - 数据包接收     |
+-------------------+

3.1 层次说明

  • client.c: 业务逻辑层,负责命令解析和流程控制
  • client_comm.c: 通信层,实现底层套接字操作,向上提供封装好的接口

4. 初始化流程

4.1 命令行参数解析 (client.c:main)

if(argc < 3){
    printf("Usage: %s <server> <port> <get|put> <file>\n", argv[0]);
    return 0;
}

const char *server_ip = argv[1];
unsigned short port = atoi(argv[2]);
const char *cmd = argv[3];
const char *file = argv[4];

4.2 套接字初始化 (client_comm.c:client_comm_init)

int client_comm_init(const char *server_ip, unsigned short port) {
    addr_len = sizeof(struct sockaddr_in);
    sender_len = sizeof(struct sockaddr_in);
    memset(&sender, 0, sizeof(struct sockaddr_in));
    
    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);
    inet_pton(AF_INET, server_ip, &(server.sin_addr.s_addr));
    
    return 0;
}

4.3 流程分支 (client.c:main)

if(strcmp(cmd, "get") == 0){
    do_get(server_ip, port, file);
} else if(strcmp(cmd, "put") == 0){
    do_put(server_ip, port, file);
} else {
    printf("Unknown command: %s\n", cmd);
    return 0;
}

5. 发送文件流程(PUT操作)

5.1 准备WRQ数据包 (client.c:do_put)

struct tftpx_packet snd_packet;
snd_packet.cmd = htons(CMD_WRQ);
sprintf(snd_packet.data, "%s\0%s\0", file, "octet");

5.2 发送WRQ数据包 (client.c:do_put)

client_comm_send(&snd_packet, sizeof(struct tftpx_packet));

5.3 等待ACK响应 (client.c:do_put)

struct tftpx_packet rcv_packet;
int r_size = client_comm_recvfrom(&rcv_packet, sizeof(struct tftpx_packet));
if(r_size >= 4 && rcv_packet.cmd == htons(CMD_ACK)){
    printf("Received ACK for block 0\n");
}

5.4 分块发送文件数据 (client.c:do_put)

FILE *fp = fopen(file, "rb");
if(fp == NULL){
    printf("Can't open file: %s\n", file);
    return;
}

char buffer[512];
int block = 1;
while(!feof(fp)){
    int bytes_read = fread(buffer, 1, 512, fp);
    struct tftpx_packet data_packet;
    data_packet.cmd = htons(CMD_DATA);
    data_packet.block = htons(block);
    memcpy(data_packet.data, buffer, bytes_read);
    
    if(client_comm_send(&data_packet, bytes_read + 4) == -1){
        printf("Error sending block %d\n", block);
        break;
    }
    
    // 等待ACK
    int ack_received = 0;
    while(!ack_received){
        struct tftpx_packet ack_packet;
        int r_size = client_comm_recvfrom(&ack_packet, sizeof(struct tftpx_packet));
        if(r_size >= 4 && ack_packet.cmd == htons(CMD_ACK) && ack_packet.block == htons(block)){
            ack_received = 1;
        }
    }
    
    block++;
}

fclose(fp);

6. 接收文件流程(GET操作)

6.1 准备RRQ数据包 (client.c:do_get)

struct tftpx_packet snd_packet;
snd_packet.cmd = htons(CMD_RRQ);
sprintf(snd_packet.data, "%s\0%s\0", file, "octet");

6.2 发送RRQ数据包 (client.c:do_get)

client_comm_send(&snd_packet, sizeof(struct tftpx_packet));

6.3 接收DATA数据包 (client.c:do_get)

FILE *fp = fopen(file, "wb");
if(fp == NULL){
    printf("Can't create file: %s\n", file);
    return;
}

struct tftpx_packet rcv_packet;
int block = 1;
while(1){
    int r_size = client_comm_recvfrom(&rcv_packet, sizeof(struct tftpx_packet));
    if(r_size < 4){
        printf("Error receiving data\n");
        break;
    }
    
    if(rcv_packet.cmd == htons(CMD_DATA) && ntohs(rcv_packet.block) == block){
        // 写入文件
        fwrite(rcv_packet.data, 1, r_size - 4, fp);
        
        // 发送ACK
        struct tftpx_packet ack_packet;
        ack_packet.cmd = htons(CMD_ACK);
        ack_packet.block = rcv_packet.block;
        client_comm_send_to(&ack_packet, sizeof(struct tftpx_packet));
        
        // 检查是否为最后一个块
        if(r_size - 4 < 512){
            printf("Received final block\n");
            break;
        }
        
        block++;
    }
}

fclose(fp);

7. client_comm.c中的关键函数

  • 7.1 client_comm_init - 初始化套接字通信
  • 7.2 client_comm_send - 向服务器发送数据
  • 7.3 client_comm_send_to - 向特定地址发送数据
  • 7.4 client_comm_recvfrom - 从服务器接收数据
  • 7.5 client_comm_close - 关闭套接字连接
  • 7.6 client_comm_get_sock - 获取套接字描述符
  • 7.7 client_comm_get_server - 获取服务器地址
  • 7.8 client_comm_get_addr_len - 获取地址长度

8. client.c中的关键函数

  • 8.1 main - 主入口点,解析参数并启动操作
  • 8.2 do_get - 处理文件下载(RRQ)
  • 8.3 do_put - 处理文件上传(WRQ)
  • 8.4 parse_opts - 解析命令行选项

9. TFTP协议细节

  • 9.1 使用UDP协议进行通信
  • 9.2 默认端口:69
  • 9.3 支持RRQ(读请求)和WRQ(写请求)
  • 9.4 数据以512字节的块传输
  • 9.5 每个块由接收方确认
  • 9.6 最后一个块的大小小于512字节

10. 错误处理

  • 10.1 数据包传输的超时处理
  • 10.2 丢失数据包的重传机制
  • 10.3 处理服务器返回的错误数据包
  • 10.4 向用户报告连接错误

11. 完整源码

下载链接:https://download.csdn.net/download/wxmtwfx/9272566

Logo

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

更多推荐