网络编程作业3

1.基于UDP的TFTP文件传输

tftp协议概述

简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输

特点:

是应用层协议

基于UDP协议实现

数据传输模式

octet:二进制模式(常用)

mail:已经不再支持

代码:

#include <myhead.h>

#define TFTP_PORT 69
#define BLOCK_SIZE 512
#define OPCODE_RRQ 1
#define OPCODE_WRQ 2
#define OPCODE_DATA 3
#define OPCODE_ACK 4
#define OPCODE_ERROR 5
#define MAX_RETRIES 5

void error(const char *msg) 
{
    perror(msg);
    exit(1);
}

void send_rrq(const char *filename, const char *server_ip) 
{
    int sockfd;
    struct sockaddr_in server_addr;
    unsigned char buffer[516]; // 512 + 4 比特
    int n;
    unsigned int block_number = 1;

    // 创建UDP套接字
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
    {
        error("Socket creation failed");
    }

    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(TFTP_PORT);
    inet_pton(AF_INET, server_ip, &server_addr.sin_addr);

    // 发送读取请求
    buffer[0] = 0; // 高字节
    buffer[1] = OPCODE_RRQ; // 操作码

    // 复制文件名和模式到缓冲区
    size_t filename_len = strlen(filename);
    size_t mode_len = strlen("octet");

    if (filename_len + mode_len + 4 > sizeof(buffer)) 
    {
        fprintf(stderr, "Filename and mode too long\n");
        close(sockfd);
        return;
    }

    memcpy(buffer + 2, filename, filename_len);
    buffer[2 + filename_len] = 0; // 文件名结束
    memcpy(buffer + 3 + filename_len, "octet", mode_len);
    buffer[3 + filename_len + mode_len] = 0; // 模式结束

    // 发送请求
    if (sendto(sockfd, buffer, 4 + filename_len + mode_len, 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) 
    {
        error("Send RRQ failed");
    }

    // 接收数据
    while (1) 
    {
        socklen_t addr_len = sizeof(server_addr);
        n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&server_addr, &addr_len);
        if (n < 0) 
        {
            error("Receive failed");
        }

        // 处理数据包
        unsigned short opcode = (buffer[0] << 8) | buffer[1];
        if (opcode == OPCODE_DATA) 
        {
            unsigned short received_block_number = (buffer[2] << 8) | buffer[3];
            if (received_block_number == block_number) 
            {
                // 写入文件
                FILE *file = fopen(filename, "ab");
                if (file == NULL) 
                {
                    error("File open failed");
                }
                fwrite(buffer + 4, 1, n - 4, file);
                fclose(file);

                // 发送ACK
                buffer[0] = 0; // 高字节
                buffer[1] = OPCODE_ACK; // 操作码
                buffer[2] = (block_number >> 8) & 0xFF; // 发送ACK的块号
                buffer[3] = block_number & 0xFF;

                if (sendto(sockfd, buffer, 4, 0, (struct sockaddr *)&server_addr, addr_len) < 0) 
                {
                    error("Send ACK failed");
                }

                // 如果接收到的数据块小于512字节,表示传输结束
                if (n < 4 + BLOCK_SIZE) {
                    printf("File transfer complete.\n");
                    break;
                }

                block_number++;
            }
        } else if (opcode == OPCODE_ERROR) 
        {
            printf("下载错误: %s\n", buffer + 4);
            break;
        }
    }

    close(sockfd);
}

void send_wrq(const char *filename, const char *server_ip) 
{
    int sockfd;
    struct sockaddr_in server_addr;
    unsigned char buffer[516]; // 512 + 4 比特
    int n;
    unsigned int block_number = 0;

    // 创建UDP套接字
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
    {
        error("Socket creation failed");
    }

    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(TFTP_PORT);
    inet_pton(AF_INET, server_ip, &server_addr.sin_addr);

    // 发送写入请求
    buffer[0] = 0; // 高字节
    buffer[1] = OPCODE_WRQ; // 操作码

    // 复制文件名和模式到缓冲区
    size_t filename_len = strlen(filename);
    size_t mode_len = strlen("octet");

    if (filename_len + mode_len + 4 > sizeof(buffer)) 
    {
        fprintf(stderr, "Filename and mode too long\n");
        close(sockfd);
        return;
    }

    memcpy(buffer + 2, filename, filename_len);
    buffer[2 + filename_len] = 0; // 文件名结束
    memcpy(buffer + 3 + filename_len, "octet", mode_len);
    buffer[3 + filename_len + mode_len] = 0; // 模式结束

    // 发送请求
    if (sendto(sockfd, buffer, 4 + filename_len + mode_len, 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) 
    {
        error("Send WRQ failed");
    }

    // 等待ACK
    socklen_t addr_len = sizeof(server_addr);
    n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&server_addr, &addr_len);
    if (n < 0) 
    {
        error("Receive failed");
    }

    unsigned short opcode = (buffer[0] << 8) | buffer[1];
    if (opcode != OPCODE_ACK) 
    {
        printf("等待ACK错误: %s\n", buffer + 4);
        close(sockfd);
        return;
    }

    // 读取文件并发送数据
    FILE *file = fopen(filename, "rb");
    if (file == NULL) 
    {
        error("File open failed");
    }

    while (1) 
    {
        // 读取数据块
        n = fread(buffer + 4, 1, BLOCK_SIZE, file);
        if (n < 0) 
        {
            error("File read failed");
        }

        if (n == 0) 
        {
            // 如果读取到文件末尾,则结束传输
            printf("上传完成.\n");
            break;
        }

        block_number++;
        buffer[0] = 0; // 高字节
        buffer[1] = OPCODE_DATA; // 操作码
        buffer[2] = (block_number >> 8) & 0xFF; // 块号
        buffer[3] = block_number & 0xFF; // 块号

        int retries = 0;
        while (retries < MAX_RETRIES) 
        {
            // 发送数据块
            printf("Sending block %d, size: %d bytes\n", block_number, n);
            if (sendto(sockfd, buffer, n + 4, 0, (struct sockaddr *)&server_addr, addr_len) < 0) 
            {
                error("Send DATA failed");
            }

            // 等待ACK
            n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&server_addr, &addr_len);
            if (n < 0) 
            {
                printf("Receive failed, retrying...\n");
                retries++;
                continue; // 继续重试
            }

            opcode = (buffer[0] << 8) | buffer[1];
            if (opcode == OPCODE_ACK) 
            {
                printf("Received ACK for block %d\n", block_number);
                break; // 收到ACK,退出重试
            } else 
            {
                printf("Error received from server: %s\n", buffer + 4);
                break;
            }
        }

        if (retries == MAX_RETRIES) 
        {
            printf("Failed to receive ACK after %d retries. Aborting upload.\n", MAX_RETRIES);
            break;
        }
    }

    fclose(file);
    close(sockfd);
}

int main() 
{
    char server_ip[16];
    char filename[256];
    int choice;

    printf("请输入TFTP服务器IP地址: ");
    scanf("%s", server_ip);

    while (1) {
        printf("选择操作:\n1. 下载文件 (RRQ)\t2. 上传文件 (WRQ)\t3. 退出\n");
        scanf("%d", &choice);

        switch (choice) {
            case 1:
                printf("请输入要下载的文件名: ");
                scanf("%s", filename);
                send_rrq(filename, server_ip);
                break;
            case 2:
                printf("请输入要上传的文件名: ");
                scanf("%s", filename);
                send_wrq(filename, server_ip);
                break;
            case 3:
                printf("退出成功。\n");
                exit(0);
            default:
                printf("无效的选择。\n");
                break;
        }
    }

    return 0;
}

运行结果:

 

 2.思维导图

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值