TFTP(Trivial File Transfer Protocol,简单文件传输协议)
是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议
操作码 | 功能 |
---|---|
1 | 读请求,即下载 |
2 | 写请求,即上传 |
3 | 表示数据报,即data |
4 | 确认码,即ACK |
5 | 错误 |
from socket import *
import struct
#服务器端的socket
s = socket(AF_INET,SOCK_DGRAM)
#绑定IP和端口号
s.bind(('',69)) #注意:此处需要将IP地址和端口号先封装成一个元组
def download(filename,client_ip,client_port):
#创建一个新的socket,负责发送包含文件内容的数据报给客户端
new_socket = socket(AF_INET,SOCK_DGRAM)
#文件内容数据报的计数器,起始就是发送数据包块的编号
num = 1
#定义服务客户端退出的标签
flag = True
try:
f = open(filename,'rb')
except: #捕获所有异常
#H表示Python的inte转成C的无符号的short
#5s表示把Python中包含5个字符的字符串转成C中的字符数组
error_package = struct.pack('!HH5sb',5,5,'error'.encode('utf-8'),0)
new_socket.sendto(error_package,(client_ip,client_port)) #错误数据包发给客户端
exit() #当前线程结束,当前客户端退出服务器
flag = False
#如果文件存在,那么需要把文件的内容切成一个个的数据报发给客户端,一个个数据报包含的数据内容为512字节
while True:
#从文件内容中读取512字节
read_data = f.read(512) #字节数据
#创建一个数据报
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)
break
#服务器接收ACK的确认数据
recv_ack = new_socket.recv(1024) #里面有数据报的内容,还有客户端的IP和端口号
operator_code,ack_num = struct.unpack('!HH',recv_ack)
print('客户端:%s,的确认信息是'%client_ip,ack_num)
num += 1
#保护性代码
if int(operator_code) != 4 or int(ack_num) < 1: #不正常的ACK确认信息
exit()
if f:
f.close()
new_socket.close() #客户端真正退出了
def server():
while True:#服务器端不需要关闭socket,其他socket可能还会访问客户端
#服务器等待客户端发送数据并接收
recv_data,(client_ip,client_port) = s.recvfrom(1024)
#服务器收到数据后开始打印
print(recv_data,client_ip,client_port)
# 判断数据报是否为客户端请求的数据报 b'octet'表示"octet"为字节数据
if struct.unpack('!b5sb',recv_data[-7:]) == (0,b'octet',0):#解包:后面7个字符
#得到操作码值(元组)
opeaator_code = struct.unpack('!H',recv_data[:2])
#得到文件名
file_name = recv_data[2:-7].decode('utf-8')
# print(opeaator_code,file_name)
if opeaator_code[0] == 1: #如果等于1就是下载的请求数据包
print('客户端下载文件:'"%s"%file_name)
download(file_name,client_ip,client_port)
if __name__ == '__main__':
server()
from socket import *
import struct #负责Python数据结构和Python数据结构之间的转换
file_name = input('请输入上传的文件名:')
#客户端的socket
s = socket(AF_INET,SOCK_DGRAM)
#定义目标服务端的地址和接口号
host_port = ('127.0.0.1',69)
# "!H%dsb5sb"代表格式:!开头的数据报
data_package = struct.pack('!H%dsb5sb'%len(file_name),1,file_name.encode('utf-8'),0,'octet'.encode('utf-8'),0)
#把数据报发送到目标服务器
s.sendto(data_package,host_port)
#客户端首先创建一个空文件
f = open('client_'+file_name,'ab')
while True:
#客户端接收服务器发过来的数据,数据只有两种:1.下载文件内容数据报 2.error数据报
recv_data,(server_ip,server_port) = s.recvfrom(1024)
operator_code,num = struct.unpack('!HH',recv_data[:4]) #把前四个字节的数据解包出来
if int(operator_code) == 5: #判断数据报是否是error信息
print('服务器返回:你要下载的文件不存在')
break
#如果是文件内容的数据,需要保存文件内容
f.write(recv_data[4:])
if len(recv_data) < 516: #数据内容512+操作码2+块编号2
#意味着服务器传输过来的文件已经接收完成了
print('客户端下载成功')
break
#客户端接收到数据后,还需要发送一个确认ACK给服务器
ack_package = struct.pack('!HH',4,num)
s.sendto(ack_package,(server_ip,server_port))
#释放资源
f.close()
s.close()