作者 | 无量测试之道 编辑 | 小 晴
今日分享主题:Python 如何实现TFTP文件服务器。
一、定义
TFTP 是一个传输文件的简单协议,它基于UDP协议而实现。 TFTP (Trivial File Transfer Protocol):简称文件传输协议。 TFTP 是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议,传输不复杂、开销不大的文件。端口号固定为69。
二、TFTP支持五种类型的包
opcode operation
- Read request (RRQ)
- Write request (WRQ)
- Data (DATA)
- Acknowledgment (ACK)
- Error (ERROR)
三、TFTP 文件下载过程,如下所示及相应的解释(重点关注)
1、读写请求--当操作码的取值为1时,表示RD 读请求;当操作码的取值为2时,表示WE 写请求 操作码 文件名 0 模式 0 2Bytes String 1Byte String 1Byte
2、数据包,所以数据包的大小为516Bytes--数据包操作码值为3 操作码 块编码 数据 2Bytes 2Bytes 512Bytes
3、ACK--ACK 操作码值为4 操作码 块编码 2Bytes 2Bytes
4、ERROR--ERROR 操作码值为5 操作码 差错码 差错信息 0 2Bytes 2Bytes String 1Byte
注意: 1、当客户端接收到的数据小于516字节时,表示服务器发送数据完成! 2、块编码从0开始,每次加1,它的范围是[0, 65535]。
四、下载过程
第一步:客户端给服务器发送下载请求,数据格式为(操作码1+文件名+0+模式+0)。
第二步:服务器接收到请求之后,回复客户端消息,数据格式为元组类型。如下所示:(操作码3+块编码0+数据, (IP号, 端口号))。
第三步:客户端每接受一次数据,都要回复服务器一次ACK信号。
第四步:直到客户端接收到的数据小于516个字节,才说明服务器发送完毕。
五、上传过程
第一步:客户端给服务器发送上传请求,数据格式为(操作码2+文件名+0+模式+0)。
第二步:服务器接收到请求之后,回复客户端ACK消息,数据格式为元组类型。如下所示:(操作码4+块编码0, (IP号, 端口号))。
第三步:客户端每发送一次数据,服务器都要回复一次ACK信号。
第四步:直到客户端发送完数据才结束。
六、struct 模块的使用说明
1、 struct.pack struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型,可以把这里的字符串理解为字节流,或字节数组)。
其函数原型为:struct.pack(fmt, v1, v2, …),参数fmt是格式字符串,关于格式字符串的相关信息在下面有所介绍。v1, v2, …表示要转换的python值。
2、 struct.unpack struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt, string),该函数返回一个元组。
七、特殊说明
格式符的使用说明: b-- signed char-- python里面的类型integer --大小为1 H-- unsigned short--python里面的类型integer --大小为2 s -- char[]--python里面的类型string --大小为1
!H%dsb5sb 解析如下: H=1表示读请求,它是两个字节,所以用H来表示,如果是一个字节则用b来表示 %ds表示%len(filename) b表示为0 5s表示为octet b表示为0 操作码 文件名 0 模式 0 2Bytes String 1Byte String 1Byte 这是对上面内容的诠释
八、代码实现
代码语言:javascript复制 1from threading import Thread
2from socket import *
3import struct
4
5#定义一个登录认证的方法
6def login(username,password):
7 if(username=="admin" and password=="123456"):
8 return True
9 else:
10 return False
11
12#定义一个上传文件的方法
13def tftp_upload(filename, user_ip, user_port):
14 filenum = 0 #表示接收文件的序号
15 fileHander = open(filename, 'ab')#创建一个文件句柄
16 socket_up = socket(AF_INET, SOCK_DGRAM)#创建udp套接字
17 send_data_1 = struct.pack("!HH", 4, filenum)#打包
18 socket_up.sendto(send_data_1, (user_ip, user_port)) # 第一次发送请求,服务器用随机端口发送
19
20 while True:
21 #接收客户端发送的数据
22 recv_data, user_info = socket_up.recvfrom(1024) # 第二次客户端返回响应,连接本服务器的随机端口
23 operator_code,ack_num = struct.unpack('!HH', recv_data[:4]) #解包,获取操作码 and ack确认码
24 print(operator_code, ack_num, filenum)#打印
25 if int(operator_code) == 3 and ack_num == filenum: #判断如果操作码=3 并且确认号=0就开始上传文件
26 fileHander.write(recv_data[4:])#写文件内容到服务器
27 send_data = struct.pack("!HH", 4, filenum)#打包
28 socket_up.sendto(send_data, (user_ip, user_port)) # 第二次发送请求,服务器用随机端口发
29 filenum = filenum 1#文件序号 1
30 if len(recv_data) < 516:#当文件的长度小于516,表示文件传输完成
31 print(user_ip '上传文件' filename ':完成')#打印输出
32 fileHander.close()#关闭文件
33 socket_up.close()#关闭socket
34 exit()#退出
35
36#定义一个下载文件的方法
37def tftp_download(filename, user_ip, user_port):
38 socket_down = socket(AF_INET, SOCK_DGRAM)#创建一个udp 的socket套接字
39 filenum = 0 #定义一个文件序号
40 try:
41 filehander = open(filename, 'rb')#打开一个文件句柄
42 except:
43 error_data = struct.pack('!HHHb', 5, 5, 5, filenum)#打包
44 socket_down.sendto(error_data, (user_ip, user_port)) # 文件不存在时发送
45 exit() # 只会退出此线程
46 while True:#进行无限循环中
47 read_data = filehander.read(1024)#读文件操作
48 send_data = struct.pack('!HH', 3, filenum) read_data#打包
49 socket_down.sendto(send_data, (user_ip, user_port)) # 向客户端进行数据第一次发送
50 if len(read_data) < 512:#当文件小于512时,表示文件下载完成
51 print('传输完成, 对方下载成功')
52 exit()#退出
53 recv_data = socket_down.recvfrom(1024) # 第二次接收客户端的数据
54 print(recv_data) #(b'x00x04x00x00', ('127.0.0.1', 61202))
55 operator_code, ack_num = struct.unpack("!HH", recv_data[0])#解包获取操作码和ack确认号
56 filenum = 1 #文件序号 1
57 if int(operator_code) != 4 or int(ack_num) != filenum - 1:#如果操作码不是4 或者 ack确认号不等于文件序号减1
58 exit()#程序退出
59 filehander.close()#文件关闭
60 socket_down.close()#关闭socket
61
62def main():
63 sockets = socket(AF_INET, SOCK_DGRAM)
64 sockets.bind(('', 69))
65 while 1:
66 recv_data, (user_ip, user_port) = sockets.recvfrom(1024) # 第一次客户连接69端口
67 print(recv_data, user_ip, user_port)
68 if struct.unpack('!b5sb', recv_data[-7:]) == (0, b'octet', 0):#解包判断
69 operator_code = struct.unpack('!H', recv_data[:2])#解包获取操作码
70 filename = recv_data[2:-7].decode('gb2312') #获取文件名
71 if operator_code[0] == 1:#操作码为1 表示下载
72 print('对方想下载数据', filename)
73 t = Thread(target=tftp_download, args=(filename, user_ip, user_port))#多线程处理
74 t.start()#线程启动
75 elif operator_code[0] == 2:#操作码为2 表示上传
76 print('对方想上传数据', filename)
77 t = Thread(target=tftp_upload, args=(filename, user_ip, user_port))#多线程处理
78 t.start()#线程启动
79
80def login_tftp():
81 udp_socket = socket(AF_INET, SOCK_DGRAM)#建立udp socker连接
82 udp_socket.bind(('127.0.0.1', 69))#服务端绑定ip and port
83 recv_data = udp_socket.recvfrom(1024)#收数据等待
84 print('接收的内容:', recv_data[0].decode('utf-8'))#显示收到的信息
85 print('发送人的地址:', recv_data[1])#显示收到的信息
86
87 msg=recv_data[0].decode('utf-8')
88 msg_length=len(msg.split(" "))
89 if(msg_length==2):
90 username=msg.split(" ")[0]
91 password=msg.split(" ")[1]
92 if (login(username, password)):
93 print("登录成功!!!")
94 data = "登录成功,可以开始上传下载文件了!!!"
95 send_msg(data)
96 else:
97 print("登录失败,请检查登录账号!!!")
98 data = "操作失败,请检查登录账号!!!"
99 send_msg(data)
100
101def send_msg(data):
102 client_address = ('127.0.0.1', 8000) # 定义了本机的ip and port
103 server_address = ('127.0.0.1', 70) # 定义了接收消息机器的ip and port
104 udp_sockets = socket(AF_INET, SOCK_DGRAM) # 建立udp socker连接
105 udp_sockets.bind(client_address) # 服务端绑定ip and port
106 udp_sockets.sendto(str(data).encode("utf-8"), server_address) # 向接收消息机器发送消息
107 udp_sockets.close()
108
109if __name__ == '__main__':
110 print("tftp 服务正在提供服务...")
111 #服务器端第一步校验登录
112 login_tftp()
113 #提供上传下载服务
114 main()