TFTP下载文件

TFTP概述

TFTP:简单文件传送协议

最初用于引导无盘系统,被设计用来传输小文件

特点:

基于UDP协议实现

不进行用户有效性认证

数据传输模式:

octet:二进制模式

netascii:文本模式

mail:已经不再支持

TFTP通信过程

TFTP通信过程总结(无选项)

1、服务器在69号端口等待客户端的请求

2、服务器若批准此请求,则使用临时端口与客户端进行通信

3、每个数据包的编号都有变化(从1开始)

4、每个数据包都要得到ACK的确认如果出现超时,则需要重新发送最后的包(数据或ACK)

5、数据的长度以512Byte传输

6、小于512Byte的数据意味着传输结束

TFTP协议分析

错误码:

0 未定义,参见错误信息

1 File not found.

2 Access violation.

3 Disk full or allocation exceeded.

4 illegal TFTP operation.

5 Unknown transfer ID.

6 File already exists.

7 No such user.

8 Unsupported option(s) requested.

1、先创建一个套接字描述符用来和服务器端进行连接,
2、开始填充服务器端网络信息结构体,ip地址是主机地址,端口号为69,
3、开始进行下载操作,用循环可以多次进行下载,
4、先给端口初始化,因为第一次是69端口到第二次通信的时候是和临时端口进行通信的
5、然后就输入一个文件名,告诉服务器端下载的是哪个文件
6、然后客户端给服务器端发送TFPT协议,按照格式发送,
      nbytes=sprintf(buf,"%c%c%s%c%s%c",0,1,file,0,"octet",0);
     把请求协议封装好写入buf中,使用sprintf函数获取写入的长度,用nbytes来接
7、把buf中的内容写入到sockfd中发送给服务端,此时是第一次与服务端通信,所以端口号是69
8、服务端接受到请求数据包后给客户端发一个数据包,一端一端的发送,每一段是512字节,此时就需要一个循环来接收服务器端发来的数据包,
9、开始接收服务器端发来的数据包,但是需要注意一点的是以后和服务端进行通信就不再是69端口,而是临时端口,所以刚好使用recvfrom函数来获取服务器端的地址和端口号
10、服务器端发来的数据端4前个字节是操作码和块编号,是网络字节序,所以先把网络字节序转换为主机字节序来进行打印和赋值
11、当操作码为3时是有内容的数据包,收到数据包后将数据内容存储在文件中,然后创建文件,只需要在第一次写入的时候创建,以后不需要,不然会清空前面的数据
12、组装ACK发送应答给服务器端  3bytes操作码  2bytes块编号,此时就需要把主机字节序转换成网络字节序发送给服务器端
13、将数据包的文本段写入fd中
14、当内容小于512 的时候就退出循环重新输入文件名字
 

#include<my_head.h>

#define N 128

int main(int argc,const char *argv[]){

if(argc!=3)
{
    printf("error\n");
    exit(-1);
}
int sockfd=0;
sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd==-1)
{
    PRINT_ERR("error");
}
printf("%d\n",sockfd);

struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
socklen_t serverlen=sizeof(serveraddr);

serveraddr.sin_addr.s_addr=inet_addr(argv[1]);
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(argv[2]);

char buf[N]={0};
char file[N]={0};
int nbytes;
short code;//操作码
short block_num;//块编码
int fd=0;

while(1)
{
    //重新给端口初始化
    serveraddr.sin_port=htons(argv[2]);
    serveraddr.sin_addr.s_addr=inet_addr(argv[1]);

    printf("请输入要下载的文件名:");
    fgets(file,sizeof(file),stdin);
    file[strlen(file)-1]='\0';

    //拼接请求数据包
    nbytes=sprintf(buf,"%c%c%s%c%s%c",0,1,file,0,"octet",0);
    //将buf中的数据写入sockfd中,发送给服务器
    if(-1==(sendto(sockfd,buf,nbytes,0,(struct sockaddr*)&serveraddr,serverlen)))//这时候还是69,初始化后的可以再一次下载文件
    PRINT_ERR("error");
     //接收服务器发来的的数据内容读到buf中,循环读入
    while(1)
    {
           //此时需要把保存临时端口的信息,以后的数据包是和临时端口进行通信的
        if(-1==(nbytes==(recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&serveraddr,serverlen))))
        
        PRINT_ERR("error");

        code=ntohs(*(short *)buf);
        block_num=ntohs((*(short*)(buf+2)));
        printf("code=%d\n",code);
        printf("block_num=%d\n",block_num);
        printf("data=%s\n",buf+4);
        //操作码是3时是数据包
        if(code==3)
        {
            //收到数据包,存储在文件中
            if(block_num==1)//收到第一块数据包的时候创建,其他不需要再创建
            {
                if((fd=open(file,O_CREAT|O_TRUNC|O_WRONLY,0664))==-1)
                PRINT_ERR("error");
            }
            //组装ACK发送应答给服务器端  3bytes操作码  2bytes块编号,此时就需要把主机字节序转换成网络字节序发送给服务器端
           *((short *)buf)=htons(4);
           *((short*)(buf+2))=htons(block_num);
            if(sendto(sockfd,buf,4,0,(struct sockaddr*)&serveraddr,serverlen)==-1)//把数据发到临时端口
            PRINT_ERR("error");
            write(fd,buf+4,nbytes-4);//将数据包的文本段写入fd中
            if((nbytes-4)<512)//当内容小于512 的时候就退出循环重新输入文件名字
            {
                break;
            }

        }
        else if(code==5)
        {
             break;
        }

    }
//拆解数据包
}
close(sockfd);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值