1.TFTP基础
TFTP(Trival File Transfer Protocol,简单文件传输协议)是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议。
特点:简单、占用资源小、适合传输小文件、适合在局域网内传递、默认端口号是69、基于UDP实现。
建立连接过程:
①客户端发送下载或上传请求,将请求发送给69端口;
②服务器允许请求的情况下,服务器开启一个新端口负责文件的传输;
③服务器用新端口与客户端发送文件,客户端接受成功后发送一个ACK应答。
当文件比较大时,服务器会将文件分为几个数据包,且会多发送2个字节的数据用来存放序号,并且放在数据的前面,序号从1开始。
若需要下载的文件不存在时,服务器就会发送一个错误的信息过来,为区分发过来的是文件内容还是错误的信息,又用2个字节来表示这个数据包的功能(称为操作码),并且在序号的前面。
操作码 | 功能 |
1 | 读请求,即下载 |
2 | 写请求,即上传 |
3 | 表示数据包,即DATA |
4 | 确认码,即ACK |
5 | 错误 |
规定,当客户端接收到的数据小于516字节(2字节操作码+2字节序号+512字节数据)时,就意味着服务器发送完毕了。TFTP数据包格式:
上图中的“模式”指传输数据的格式,一般采用C语言的解释器,用octet模式。
2.服务端与客户端简单数据传输代码
服务器端:
from socket import *
socket_server = socket(AF_INET,SOCK_DGRAM) #创建套接字
socket_server.bind(('',69)) #绑定IP和端口号
def server():
while True:
recv_data,(client_ip,client_port) = socket_server.recvfrom(1024)
print(recv_data,client_ip,client_port) #将收到的数据打印
#定义main函数
if __name__ == '__main__':
server()
客户端:
from socket import *
import struct
#struct负责Python数据结构与C数据结构之间的转换
# 将数据变为C语言解释器能识别的字节流
file_name = input('请输入文件名:')
client_socket = socket(AF_INET,SOCK_DGRAM)
#定义目标服务器的地址和端口
host_port = ('192.168.43.145',69)
#按照数据包的格式给出数据
data_package = struct.pack('!H%dsb5sb'%len(file_name),1,file_name.encode('utf-8'),0,'octet'.encode('utf-8'),0)
#!H%dsb5sb表示格式,以!开头,如b是将Python中的int变为C中的char
client_socket.sendto(data_package,host_port)
在客户端输入文件名“JS.png”之后,服务器端输出:
上述代码中,Python中的struct是用来处理C结构数据的,主要的格式化字符如下:
Format | C Type | Python |
x | pad byte | no value |
c | char | string of length 1 |
b | signed char | integer |
B | unsigned char | integer |
? | _Bool | bool |
h | short | integer |
H | unsigned short | integer |
i | int | integer |
I | unsigned int | integer or long |
L | unsigned long | long |
q | longlong | long |
Q | unsigned longlong | long |
f | float | float |
d | double | float |
s | char[] | string |
p | char[] | string |
3.从服务器下载文件的代码
服务器端:
from socket import *
import struct
socket_server = socket(AF_INET,SOCK_DGRAM) #创建套接字
socket_server.bind(('',69)) #绑定IP和端口号
def download(filename,client_ip,client_port):
#服务器需要开辟新端口即创建新socket,将文件内容发给客户端
new_socket = socket(AF_INET,SOCK_DGRAM)
num = 1 #文件内容数据包个数计数器
try:
f = open(filename,'rb')
except:
#当打开文件出现错误时,返回ERROR数据包
error_package = struct.pack('!HH5sb',5,5,'error'.encode('utf-8'),0)
new_socket.sendto(error_package,(client_ip,client_port))
exit() #发送错误信息之后,当前客户端退出服务器
#文件存在时,将内容切成一个个数据包发给客户端,每个包512B
while True:
read_data = f.read(512)
#文件的内容已经是字节数据,因此不需要放入pack()中
data_package = struct.pack('!HH',3,num) + read_data
new_socket.sendto(data_package,(client_ip,client_port))
if len(read_data) < 512: #文件内容刚好读完
print("客户端:%s,文件下载完成"%client_ip)
exit()
#服务器接受ACK确认数据包
recv_ack = new_socket.recvfrom(1024)
operator_code,ack_num = struct.unpack('!HH',recv_ack[0])
print("客户端:%s,的确认信息是"%client_ip,ack_num)
num += 1
if int(operator_code) != 4 or int(ack_num) < 1:
exit()
if f:
f.close()
def server():
while True:
recv_data,(client_ip,client_port) = socket_server.recvfrom(1024)
print(recv_data,client_ip,client_port) #将收到的数据打印
#函数unpack()将数据包解开,判断后七个字符即可得知是不是读写请求数据包
#b指的是字节数据
if struct.unpack('!b5sb',recv_data[-7:]) == (0,b'octet',0):
#解出操作码
operator_code = struct.unpack('!H',recv_data[:2])
#得到文件名,并将字节变为字符串
file_name = recv_data[2:-7].decode('utf-8')
if operator_code[0] == 1:
#等于1就是下载请求
print("下载文件:%s"%file_name)
download(file_name,client_ip,client_port)
#定义main函数
if __name__ == '__main__':
server()
客户端:
from socket import *
import struct
#struct负责Python数据结构与C数据结构之间的转换
# 将数据变为C语言解释器能识别的字节流
file_name = input('请输入文件名:')
client_socket = socket(AF_INET,SOCK_DGRAM)
#定义目标服务器的地址和端口
host_port = ('10.175.193.126',69)
#按照数据包的格式给出数据
data_package = struct.pack('!H%dsb5sb'%len(file_name),1,file_name.encode('utf-8'),0,'octet'.encode('utf-8'),0)
#!H%dsb5sb表示格式,以!开头,如b是将Python中的int变为C中的char
client_socket.sendto(data_package,host_port)
#客户端首先创建一个空白文件
f = open('client_'+file_name,'ab')
while True:
#客户端接收服务器发来的数据,数据包或error数据包
recv_data,(server_ip,server_port) = client_socket.recvfrom(1024)
#解包,判断属于哪种数据包
operate_code,num = struct.unpack('!HH',recv_data[:4])
if int(operate_code) == 5:
print("你要下载的文件不存在!")
break
#是文件内容数据包时,将其保存
f.write(recv_data[4:])
if len(recv_data) < 516:
print("文件下载成功!")
break
#客户端每收到一个数据包,都要发送ack应答
ack_package = struct.pack('!HH',4,num)
client_socket.sendto(ack_package,(server_ip,server_port))
f.close()
client_socket.close()
运行结果:
分为18个包将文件下载完成。