基于UDP的TFTP文件传输

1. tftp协议概述

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

特点:

        是应用层协议

基于UDP协议实现

数据传输模式

        octet:二进制模式(常用)

        mail:已经不再支持

2. tftp下载模型

3. TFTP通信过程总结

  1. 服务器在69号端口等待客户端的请求
  2. 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
  3. 每个数据包的编号都有变化(从1开始)
  4. 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
  5. 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。

4. tftp协议分析

 5.代码

#include<myhead.h>
#define PORT 69
#define SER_IP "192.168.10.10"
int do_download(int sfd,struct sockaddr_in sin);
int do_upload(int sfd,struct sockaddr_in sin);
// 主函数
int main(int argc, char const *argv[])
{
    // 创建套接字
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sfd == -1)
    {
        perror("socket error");
        return -1;
    }

    // 初始化服务器地址结构
    struct sockaddr_in sin;
    socklen_t addrlen = sizeof(sin);
    sin.sin_family = AF_INET;       
    sin.sin_port = htons(PORT);     
    sin.sin_addr.s_addr = inet_addr(SER_IP); 

    int choose = 0;

    // 主循环
    while (1)
    {
        // 打印操作菜单
        printf("------------------------\n");
        printf("---------1. 下载--------\n");
        printf("---------2. 上传--------\n");
        printf("---------3. 退出--------\n");
        printf("------------------------\n");
        printf("------------------------\n");
        printf("请输入>>> ");

        // 获取用户选择
        choose = getchar();
        while (getchar() != 10);   // 清空输入缓冲区直到遇到换行符

        // 根据用户选择执行相应操作
        switch (choose)
        {
        case '1':
            // 执行下载操作
            do_download(sfd, sin);
            break;
        case '2':
            // 执行上传操作
            do_upload(sfd, sin);
            break;
        case '3':
            // 退出程序
            goto END;
            break;

        }
    }
END:
    // 关闭套接字文件描述符
    close(sfd);

    return 0; 
}

int do_download(int cfd, struct sockaddr_in sin)
{
    // 初始化
    char buf[516]="";
    char name[32]="";
    // 提示用户输入要下载的文件名
    printf("请输入要下载的文件名:");
    scanf("%s",name);
    // 设置操作码为下载操作
    unsigned short *p1 = (short *)buf;
    *p1 = htons(1);

    // 设置文件名
    char *p2 = buf+2;
    stpcpy(p2,name);

    // 设置文件名结束标志
    char *p3 = p2+strlen(p2);
    *p3 = 0;

    // 设置下载模式为二进制
    char *p4 = p3+1;
    strcpy(p4,"octet");

    // 计算发送数据的大小
    size_t size=2+strlen(p2)+1+strlen(p4)+1;

    // 发送下载请求给服务器
    if(sendto(cfd,buf,sizeof(buf),0,(struct sockaddr *)&sin,sizeof(sin)) == -1)
    {
        perror("send error");
        return -1;
    }
    
    // 初始化
    int fd = -1;
    socklen_t lent = sizeof(sin);
    size_t res =0;
    unsigned short num =0;

    // 循环接收数据直到文件下载完成
    while (1)
    {
        bzero(buf,sizeof(buf));
        res = recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr *)&sin,&lent);
        if(res < 0)
        {
            perror("recv error");
            return -1;
        }
        // 检查数据包类型,如果是数据包则处理
        if(buf[1] == 3)
        {
            // 检查数据包序列号是否正确
            if(*(unsigned short *)(buf+2) == htons(num+1))
            {
                num++;
                // 如果文件描述符未初始化,则打开文件
                if(fd == -1)
                {
                    fd = open(name,O_WRONLY|O_CREAT|O_TRUNC,0664);
                    if(fd <0)
                    {
                        perror("fd error");
                        return -1;
                    }
                }
                // 将接收到的数据写入文件
                if(write(fd,buf+4,res-4) <0)
                {
                    perror("write error");
                    return -1;
                }
                // 发送ACK确认数据包已接收
                buf[1] = 4;
                if(sendto(cfd,buf,4,0,(struct sockaddr *)&sin,sizeof(sin)) == -1)
                {
                    perror("send error");
                    return -1;
                }

                // 如果接收到的数据包大小小于缓冲区大小,表示文件传输完成
                if(res < 516)
                {
                    printf("文件下载完毕\n");
                    break;
                }
            }
        }
    }
    // 关闭文件描述符
    close(cfd);
    return 0;
}

int do_upload(int cfd, struct sockaddr_in sin)
{
    // 定义buf数组用于构造上传数据包
    char buf[516] = "";
    // 定义name数组用于存储上传文件名
    char name[32] = "";
    printf("请输入你要上传的文件名:");
    scanf("%s", name);
    // 打开文件,只读方式
    int fd = open(name, O_RDONLY);
    if (fd < 0)
    {
        perror("error open");
        return -1;
    }

    // 构造上传数据包,前两个字节为操作码,此处为上传操作
    unsigned short *p1 = (short *)buf;
    *p1 = htons(2);

    // 构造上传数据包,接下来为文件名长度
    char *p2 = buf + 2;
    strcpy(p2, name);

    // 文件名后添加字符串结束符
    char *p3 = p2 + strlen(p2);
    *p3 = 0;

    // 添加数据类型标识,此处为二进制文件
    char *p4 = p3 + 1;
    strcpy(p4, "octet");

    // 发送上传数据包给服务器
    if (sendto(cfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
        perror("error send");
        return -1;
    }
    // 定义接收服务器响应数据的变量
    ssize_t res = 0;
    unsigned short num = 0;
    socklen_t lent = sizeof(sin);
    // 循环接收服务器响应
    while (1)
    {
        // 清空接收缓冲区
        bzero(buf, sizeof(buf));
        // 接收服务器响应
        res = recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &lent);
        // 检查接收是否成功
        if (res < 0)
        {
            perror("error recv");
            return -1;
        }
        // 检查服务器响应的操作码是否为数据包
        if (4 == buf[1])
        {
            // 检查服务器返回的序号是否正确
            if (*(unsigned short *)(buf + 2) == htons(num))
            {
                // 修改操作码为发送数据
                buf[1] = 3;
                num++;
                *(unsigned short *)(buf + 2) = htons(num);
                // 读取文件数据到buf中
                res = read(fd, buf + 4, sizeof(buf) - 4);
                // 检查读取是否成功
                if (res < 0)
                {
                    perror("read error");
                    return -1;
                }
                else if (0 == res)
                {
                    // 若读取到文件末尾,打印上传成功信息并退出循环
                    printf("上传成功\n");
                    break;
                }
                // 发送数据包给服务器
                if (sendto(cfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, sizeof(sin)) == -1)
                {
                    perror("send error");
                    return -1;
                }
            }
        }
    }
    // 关闭文件
    close(fd);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值