知识点1【TFTP的概述】
学习通信的基本:通信协议(具体发送上面样的报文)、通信流程(按照什么步骤发送)
1、TFTP的概述
tftp:简单文件传输协议,**基于UDP,**不进行用户有效性验证
基于UDP:编程架构是UDP的
必须创建UDP套接字
套接字分类:UDP套接字,TCP套接字,原始套接字
数据传输格式(模式选择):
octet:二进制模式
netascii:文本模式
2、TFTP的通信过程
决定了 编程流程
我们主要写客户端
注意:
服务器中,在69号端口后,是临时端口,临时端口是只要socket,不bind。因此需要创建一个新的线程,线程中执行socket()
TFTP通信过程总结
3、TFTP协议分析
读写请求是用户自己发送的,我们现在不考虑选项部分。
数据包516个字节,当小于516个字节的时候,表示下载 结束(最后一个报文),块编号中存储的是该数据包的编号。这里注意一定要对操作码进行判断,是3才可以确认是数据包。
收到数据包 我们需要回复一个ACK,ACK中的块编号需要和数据包的ACK相同
这里有一个技巧,客户端直接把收到的内容的前4个字节发送给服务器,只需要修改操作码的第二个字节即可
错误码操作码是5,差错码是不同的错误有不同的编号,差错信息说明错误。
OACK是比如我们设置了选项部分,需要设置的,相对复杂,我们下面介绍。
想一想
传输的数据的大小一定是 512Byte 吗?
由于网络的原因,一方收不到另一方的数据怎么办?
1、不一定需要512,选项修改
2、这里服务器发送数据包后,如果没有收到ACK,它会等待,并从发送数据包处开始计时,超出 超时时间后,才会发出错误码,提示重发。
而这里的数据包长度修改,超时时间的修改,都需要借助选项来完成
4、TFTP带选项的读写报文
选项和值成对出现。
OACK是在修改了选项后,服务器向客户端发送的一个报文,是起一个确认作用的。
我们结合上图,客户端一旦修改了选项部分,客户端不再是立即发送数据,而是发送OACK,让客户端确认是否要这样修改(通过ACK,块编号中:0表示同意,这也是为什么数据编号是从1开始的),客户端给予回应后,服务器才会执行发送数据的操作。
选项的种类
tsize 选项
当读操作时,tsize 选项的参数必须为“0”,服务器会返回待读取的文件的大小
当写操作时,tsize 选项参数应为待写入文件的大小,服务器会回显该选项
blksize 选项
修改传输文件时使用的数据块的大小(范围:8~65464)
timeout 选项
修改默认的数据传输超时时间(单位:秒)
注意:
1、选项没有顺序
2、在使用tsize 读操作的时候,tsize的默认参数是0(因为读时不知道文件的大小),收到OACK的时候,会返回实际文件的大小(nB说明是字符串需要转换)
总结
1、可以通过发送带选项的读/写请求发送给 server 2、如果 server 允许修改选项则发送选项修改确认包 3、server 发送的数据、选项修改确认包都是临时 port 4、server 通过 timeout 来对丢失数据包的重新发送
知识点2【TFTP案例】
需求:编写TFTP客户端(不带选项),从客户端下载文件
流程:
1、创建UDP套接字
2、构建读请求报文,并发送到服务器69号端口
3、while 循环接收,buf是516个字节,recvform的返回值是获取到数据的长度
这里需要创建一个文件描述符取接收
4、关闭文件描述符 和 套接字
构建 读写请求报文 的方式
使用sprintf组包,当sendto时,有一个内容长度,使用sprintf的返回值:组包的总长度,目的端口写69
代码演示
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(int argc, char const *argv[])
{if(argc != 3){printf("error\\n");printf("modo:./a.out 196.168.9.28 a.txt\\n");_exit(-1);}//创建套接字,目的:向服务器发送数据int fd_sock = socket(AF_INET,SOCK_DGRAM,0);if(fd_sock < 0){perror("socket");return 0;}//sprintf组包,返回值使用len_sprintf接收。格式: 0 1 文件名 0 模式 0 char arr_sprintf[64] = "";int len_sprintf = sprintf(arr_sprintf,"%c%c%s%c%s%c",0,1,argv[2],0,"octet",0);//发送sprintf组包(读写报文)struct sockaddr_in addr_rw;memset(&addr_rw,0,sizeof(addr_rw));addr_rw.sin_family = AF_INET;addr_rw.sin_port = htons(69);addr_rw.sin_addr.s_addr = inet_addr(argv[1]);sendto(fd_sock,arr_sprintf,len_sprintf,0,(struct sockaddr *)&addr_rw,sizeof(addr_rw));/* 接收服务器发出的 数据报文,报文的前四个字节:操作码(2),端口码(2),数据(512),存储在arr_recv中数据段操作码判断,下面是3的操作重复性判断,定义一个unsigned short num = 0,num+1与端口码比较数据写入文件,并将端口码赋值给numACK 回应服务器 arr_recv前四个字节进行处理即可*///操作码为5操作//关闭套接字 及 文件描述符 //输出错误原因unsigned short num = 0;int fd_w = open(argv[2],O_WRONLY | O_CREAT,0664);if(fd_w < 0){close(fd_sock);perror("open");return 0;}int len = 0;unsigned char arr_recv[516] = "";struct sockaddr_in addr_recv;int len_recv = sizeof(addr_recv);do{len = recvfrom(fd_sock,arr_recv,sizeof(arr_recv),0,(struct sockaddr *)&addr_recv,&len_recv);if(arr_recv[1] == 3){if((unsigned short)(num + 1) == ntohs(*(unsigned short *)(arr_recv +2))){write(fd_w,arr_recv + 4,len - 4);//ack应答num = ntohs(*(unsigned short *)(arr_recv +2));printf("%d\\n",num);}arr_recv[1] = 4;sendto(fd_sock,arr_recv,4,0,(struct sockaddr *)&addr_recv,sizeof(addr_recv));}else if(arr_recv[1] == 5){close(fd_sock);close(fd_w);unlink(argv[2]);printf("%s\\n",arr_recv+4);_exit(-1);}} while (len == 516);//关闭文件描述符,套接字,关闭文件close(fd_w);close(fd_sock);return 0;
}
代码运行结果
结束
代码重在练习!
代码重在练习!
代码重在练习!
今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏夹关注,谢谢大家!!!