TFTP协议理解

TFTP协议

RFC1350http://www.ietf.org/rfc/rfc1350.txt

为什么要引入TFTP

在项目中udp环境下需要进行文件传输,一种高效的文件传输。文件传输一般会想到FTP TFTP,但考虑限制条件,所以采用TFTP

TFTP是什么?

TFTP是一种简单的文件传输协议。目标是在UDP之上上建立一个类似于FTP的但仅支持文件上传和下载功能的传输协议,所以它不包含FTP协议中的目录操作和用户权限等内容。

FTP相似,TFTP传输过程中也有传输模式之分,模式的意思是如何解释数据包里的内容,比如是字符串还是二进制等。目前有三种模式:

· l netascii型:一种修改的8bit ascii

· l octet型:即binary普通的二进制型

· l mail型:过时,不再使用

另外,通讯双方也可以自定义所需的传输模式

TFTP的通信流程

TFTP共定义了五种类型的包格式,格式的区分由包数据前两个字节的Opcode字段区分,分别是:

· l 读文件请求包:Read request,简写为RRQ,对应Opcode字段值为1

· l 写文件请求包:Write requst,简写为WRQ,对应Opcode字段值为2

· l 文件数据包:Data,简写为DATA,对应Opcode字段值为3

· l 回应包:Acknowledgement,简写为ACK,对应Opcode字段值为4

· l 错误信息包:Error,简写为ERROR,对应Opcode字段值为5

1、由客户端发起读文件/写文件的请求,同时可以进行请求连接

2、服务器监听到请求,打开连接,并向客户端发送文件,以每个定长512字节的块进行发送。每个数据包包含一个数据块,在发送下一个包的时候必须被客户端确认回应一个回应包。

3、当发现某个数据包小于512个字节,说明传输终止

4、如果出现网络丢包,收件方(客户端)会超时,并且重传最后的接收包(可以是数据包或者回应包),因此这将可以让发送者重新发送丢失包。当之前的包已经接受成功之后,发送者只需要保持一个重传包。注意 发送者 要做的事情:发送数据,接受 回应包;接受者要做的:发送回应包,接受数据。如下图所示:

读请求写请求的回应是不一样的下图是读请求



有许多的错误会造成连接中断.

1、发送错误的包,这个包没有回应,也没有重发,(例如,TFTP服务器或者客户端会因为收到错误的信息终止)这样导致对方接受不到。所以引入了超时机制。通常错误由3种情况导致:

 (1)、不能满足请求的内容(例如:文件找不到,不允许接入,或者用户不存在)
2)、由于网络延迟或者重置导致正在接收的包无法解析(例如包的格式不正确)
 (3)、必须访问资源丢失(例如磁盘满了,或者传输过程中拒绝访问)

TFTP意识到仅有一个错误条件是不会导致中断的,那就是接收包的源端口不正确。在这种情况下会向原始的服务器发一个错误包。

为了简单的实现,TFTP协议非常的有限。例如:固定大小的块可以使得分配空间更直接,在锁定等待回应包可以方便控制流并且不需要重排接到的数据包

TFTP的具体包结构

 因为TFTp是设计在udp上层的协议,并且是报文是基于网络协议,属于数据包将拥有一个网络包头报文头,和TFTP

另外数据包有可能还有其他的头(例如LNI,ARPA头等等)来允许他们通过本地的传输媒体。下面的表表示了一个包包含的内容: local medium header, if used, Internet header,

   Datagram header, TFTP header, followed by the remainder of the TFTP

   Packet.

TFTP不需要制定任何的网络头的值。另外报文头数据源和目标端口字段用于TFTP,长度字段但应tftp包的大小。用于TFTP的传输id(tid)会传到报文层当作端口使用,因此必须是0~6553516位,2个字节)

          ---------------------------------------------------

         |  Local Medium  |  Internet  |  Datagram  |  TFTP  |

          ---------------------------------------------------

                      Figure 3-1: Order of Headers

TFTP支持5中操作类型的数据:

            opcode  operation

            1     Read request (RRQ) 读文件请求

            2     Write request (WRQ) 写文件请求

            3     Data (DATA) 数据

            4     Acknowledgment (ACK) 确认包

            5     Error (ERROR) 错误

TFTP请求包。如下图


            2 bytes     string    1 byte     string   1 byte

            ------------------------------------------------

           | Opcode |  Filename  |   0  |    Mode    |   0  |

            ------------------------------------------------

                      Figure 5-1: RRQ/WRQ packet

上述协议中

Opcode 操作码

Filename:netascii的文件字节序号,0表示终止

Mode:类型主要 可以是 "netascii", "octet", or "mail" (这几个单词的字母不区分大小写)

数据包格式:


                   2 bytes     2 bytes      n bytes

                   ----------------------------------

                  | Opcode |   Block #  |   Data     |

                   ----------------------------------

                        Figure 5-2: DATA packet

Block# 1开始,每一个新包自加。此限制,允许程序使用一个单一的数字来区分的新数据包之间的重复。每一个块的定长为512 2^9,如果出现某一块长度部位512,那就表示数据传输完成,终止传输。

除了ACK和用于中断的包外,其它的包均得到确认。发出新的数据包等于确认上次的包。WRQDATA包由ACKERROR数据包确认,而RRQ数据包由DATAERROR数据包确认。

ACK

                         2 bytes     2 bytes

                         ---------------------

                        | Opcode |   Block #  |

                         ---------------------

                         Figure 5-3: ACK packet

错误包结构


               2 bytes     2 bytes      string    1 byte

               -----------------------------------------

              | Opcode |  ErrorCode |   ErrMsg   |   0  |

               -----------------------------------------

                        Figure 5-4: ERROR packet

附录:

Order of Headers(头顺序)

                                                  2 bytes

    ----------------------------------------------------------

   |  Local Medium  |  Internet  |  Datagram  |  TFTP Opcode  |

    ----------------------------------------------------------

TFTP Formats(TFTP格式)

   Type   Op #     Format without header

          2 bytes    string   1 byte     string   1 byte

          -----------------------------------------------

   RRQ/  | 01/02 |  Filename  |   0  |    Mode    |   0  |

   WRQ    -----------------------------------------------

          2 bytes    2 bytes       n bytes

          ---------------------------------

   DATA  | 03    |   Block #  |    Data    |

          ---------------------------------

          2 bytes    2 bytes

          -------------------

   ACK   | 04    |   Block #  |

          --------------------

          2 bytes  2 bytes        string    1 byte

          ----------------------------------------

   ERROR | 05    |  ErrorCode |   ErrMsg   |   0  |

          ----------------------------------------

初始化一个读文件的连接

初始化连接协议

一旦发送请求(WRQ 写文件请求或者 RRQ读文件请求),传输就已经建立,并且收到写的回应包,或读取第一个数据,的肯定答复。一般说回应包会包含已经回应的数据包的块序号。每一个数据包都和他的块序号关联,块序号从1开始,顺序增加。在特殊的情况下块序号是0,即当写请求的相应包是一个确认包的时候(通常,只要确认包是确认的数据包,确认包会包含被确认数据的块序号)。如果回应是一个错误包,请求会被拒绝。

为了建立连接,连接的每一段都会为自己在传输期间选则传输idtid)。连接的tid是随机生成的,所以在一个过程中同时出现两个相同的tid的概率是非常少的。每一个包都包含两个tid,一个是源tid,一个是目标tid。这两个tid是用来支持udp(或者其他报文协议)作为源端口和目标端口。在响应请求的,正常情况操作下,服务器使用自己的TID作为源TID,而选择上一条请求消息的Tid作为目标TID。这两个TId作为接下来的传输。

例如:

1主机A向主机B发出WRQ,其中端口为69

2. B机向A机发出ACK,块号为0,包括BATID

错误码

   值          表示意思

   0         未定义Not defined, see error message (if any).
   1         文件找不到File not found.
   2         拒绝访问Access violation.
   3         磁盘满了或者超出了可分配空间Disk full or allocation exceeded.
   4         非法TFTP操作Illegal TFTP operation.
   5         传输ID未知Unknown transfer ID.
   6         文件不存在File already exists.
   7         没有该用户No such user.

TFTP总结

整体上来说,TFTP的一个重要特点就是简单及易于实现,这也是设计TFTP协议的一个初衷。

优点:

· l 每个数据包大小固定,这样在内存分配处理的时候比较直接

· l 实现简单

· l 每个数据包都有确认机制,可以实现一定程度的可靠性

缺点:

· l 传输效率不高

· l 滑动窗口机制太简单,并且该窗口仅有一个包的大小

· l 超时处理机制并不完善,RFC1350并没有给出详细的处理机制说明



  • 8
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TFTP(Trivial File Transfer Protocol)是一种简单的文件传输协议,常用于将文件从服务器传输到客户端。下面是一个使用C语言实现TFTP协议的例子: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #define TFTP_OPCODE_RRQ 1 #define TFTP_OPCODE_DATA 3 #define TFTP_OPCODE_ACK 4 #define TFTP_OPCODE_ERROR 5 #define TFTP_ERROR_UNDEFINED 0 #define TFTP_ERROR_FILE_NOT_FOUND 1 #define TFTP_ERROR_ACCESS_VIOLATION 2 #define TFTP_ERROR_DISK_FULL 3 #define TFTP_ERROR_ILLEGAL_OPERATION 4 #define TFTP_ERROR_UNKNOWN_TID 5 #define TFTP_ERROR_FILE_EXISTS 6 #define TFTP_ERROR_NO_SUCH_USER 7 #define TFTP_BLOCK_SIZE 512 struct tftp_packet { unsigned short opcode; union { struct { char filename[1]; char mode[1]; } rrq; struct { unsigned short block_num; char data[TFTP_BLOCK_SIZE]; } data; struct { unsigned short block_num; } ack; struct { unsigned short error_code; char error_msg[1]; } error; } data; }; int main(int argc, char *argv[]) { if (argc != 4) { fprintf(stderr, "Usage: %s <ip> <port> <file>\n", argv[0]); exit(EXIT_FAILURE); } int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); if (sock_fd < 0) { perror("socket"); exit(EXIT_FAILURE); } struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(argv[1]); server_addr.sin_port = htons(atoi(argv[2])); char *filename = argv[3]; char mode[] = "octet"; int filename_len = strlen(filename); int mode_len = strlen(mode); int packet_len = 2 + filename_len + 1 + mode_len + 1; char *packet_buf = malloc(packet_len); if (!packet_buf) { perror("malloc"); exit(EXIT_FAILURE); } struct tftp_packet *packet = (struct tftp_packet *)packet_buf; packet->opcode = htons(TFTP_OPCODE_RRQ); memcpy(packet->data.rrq.filename, filename, filename_len); memcpy(packet->data.rrq.filename + filename_len, "\0", 1); memcpy(packet->data.rrq.mode, mode, mode_len); memcpy(packet->data.rrq.mode + mode_len, "\0", 1); int ret = sendto(sock_fd, packet_buf, packet_len, 0, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (ret < 0) { perror("sendto"); exit(EXIT_FAILURE); } int block_num = 1; FILE *file = fopen(filename, "wb"); if (!file) { perror("fopen"); exit(EXIT_FAILURE); } while (1) { char recv_buf[TFTP_BLOCK_SIZE + 4]; struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); ret = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&client_addr, &client_len); if (ret < 0) { perror("recvfrom"); exit(EXIT_FAILURE); } struct tftp_packet *recv_packet = (struct tftp_packet *)recv_buf; unsigned short opcode = ntohs(recv_packet->opcode); if (opcode == TFTP_OPCODE_DATA) { unsigned short recv_block_num = ntohs(recv_packet->data.data.block_num); if (recv_block_num == block_num) { int data_len = ret - 4; fwrite(recv_packet->data.data.data, 1, data_len, file); block_num++; char ack_buf[4]; struct tftp_packet *ack_packet = (struct tftp_packet *)ack_buf; ack_packet->opcode = htons(TFTP_OPCODE_ACK); ack_packet->data.ack.block_num = htons(recv_block_num); ret = sendto(sock_fd, ack_buf, sizeof(ack_buf), 0, (struct sockaddr *)&client_addr, client_len); if (ret < 0) { perror("sendto"); exit(EXIT_FAILURE); } if (data_len < TFTP_BLOCK_SIZE) { break; } } } else if (opcode == TFTP_OPCODE_ERROR) { unsigned short error_code = ntohs(recv_packet->data.error.error_code); fprintf(stderr, "TFTP error: %d\n", error_code); exit(EXIT_FAILURE); } else { fprintf(stderr, "Unexpected TFTP opcode: %d\n", opcode); exit(EXIT_FAILURE); } } fclose(file); close(sock_fd); free(packet_buf); return 0; } ``` 这个例子实现了TFTP的读取请求(RRQ)和数据(DATA)包的处理,以及确认(ACK)包的发送。它会将服务器上的文件传输到本地,并保存为和服务器上的文件名相同的文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值