"""
Created on 2022/7/25 21:22
"""
import os
import sys
import json
import tqdm
import struct
import socket
class ConnftpClient:
def __init__(self, host, port, buffer_size=102400, timeout=10):
"""
:param host: FTP登录地址
:param port: FTP登录端口
:param buffer_size: 缓冲区大小
:param timeout: FTP连接超时时间
"""
self.host = host
self.port = port
self.timeout = timeout
self.buffer = buffer_size
self.client = socket.socket()
self.client.settimeout(self.timeout)
def _connftp(self):
"""
:return: 返回FTP连接结果
"""
try:
self.client.connect((self.host, int(self.port)))
res = self.client.recv(self.buffer).decode()
if res.startswith("220"):
print("连接FTP地址 {}:{} 成功".format(self.host, self.port))
else:
print(res)
except Exception as e:
print("连接FTP地址 {}:{} 异常, 请检查FTP服务端网络地址是否正常".format(self.host, self.port))
exit()
def _login_user(self):
"""
:return: 返回用户名访问结果
"""
username = input("请输入FTP登录用户: ")
login_username = "USER " + username + "\n"
self.client.sendall(login_username.encode())
rsp = self.client.recv(self.buffer).decode()
if rsp.startswith("331"):
print("{}用户访问FTP地址 {}:{} 成功".format(username, self.host, self.port))
else:
print("访问FTP地址 {}:{}失败,请检查FTP登录用户名{}是否正常".format(self.host, self.port, username))
exit()
def _login_pass(self):
"""
:return: 访问FTP密码认证结果
"""
password = input("请输入FTP登录密码: ")
login_password = "PASS " + password + "\n"
try:
self.client.sendall(login_password.encode())
rsp = self.client.recv(self.buffer).decode()
if rsp.startswith("5"):
print("认证FTP地址 {}:{}失败,请检查FTP登录用户密码是否正常".format(self.host, self.port))
exit()
else:
print("认证FTP地址 {}:{} 成功".format(self.host, self.port, password))
except Exception as e:
print("访问FTP地址 {}:{} 异常, 请检查用户密码是否正常".format(self.host, self.port))
exit()
def _login_mode(self, mode="PASV"):
"""
:param mode: 默认为被动模式
:return: 返回Data Port响应体
"""
mode = mode + "\n"
self.client.sendall(mode.encode())
try:
for i in range(3):
rsp = self.client.recv(self.buffer).decode()
print(rsp.strip("\n"))
if rsp.startswith("227"):
return rsp.split(",")
else:
continue
except Exception as e:
pass
def _login_ftp(self):
"""
:return: 连接 -> 访问 -> 认证,返回最终结果
"""
self._login_user()
self._login_pass()
def help(self):
"""
:return: FTP支持CMD指令
"""
help_msg = """
· ls - Displays an abbreviated list of a remote directory's files and subdirectories
· help - Displays descriptions for ftp commands
· put - Copies a single local file to the remote computer
· get - Copies a single remote file to the local computer
· delete - Deletes a single file on a remote computer
· mkdir - Creates a remote directory
· rmdir - Deletes a remote directory
· cd - Changes the working directory on the remote computer
· quit - Ends the FTP session with the remote computer and exits ftp (same as "bye")
"""
print(help_msg)
def list_files(self, path=None):
"""
:param path: 查询目录路径
:return: 当前工作路径下文件目录列表信息
"""
if path is None or path == "":
cmd = "LIST" + "\n"
else:
cmd = "LIST " + path + "\n"
self.client.sendall(cmd.encode())
def get_path(self):
"""
:return: 当前工作路径
"""
cmd = "PWD" + "\n"
self.client.sendall((cmd.encode()))
try:
for i in range(2):
rsp = self.client.recv(self.buffer).decode()
if rsp.startswith("257"):
fmt_rsp = rsp.strip("\n")
return fmt_rsp
except Exception as e:
pass
def cwd(self, path=None):
"""
:param path: 需要进行切换的目录
:return: 切换后的工作路径
"""
if path is None or path == "":
print("需要指明切换路径")
else:
path = path
cmd = "CWD " + path + "\n"
self.client.sendall(cmd.encode())
try:
for i in range(2):
rsp = self.client.recv(self.buffer).decode()
if rsp.startswith("250"):
fmt_rsp = rsp.strip("\n")
return fmt_rsp
except Exception as e:
pass
def mkdir(self, path=None):
"""
:param path: 创建目录路径,不可递归
:return: 在当前工作路径下创建目录
"""
if path is None or path == "":
print("需要指明创建目录;Usage: mkdir [path]")
else:
path = path
cmd = "MKD " + path + "\n"
self.client.sendall(cmd.encode())
try:
for i in range(2):
res = self.client.recv(self.buffer).decode()
if res.startswith("257"):
print(res.strip("\r\n"))
break
elif res.startswith("550"):
print(res.strip("\r\n"))
break
except Exception as e:
pass
def rmdir(self, path=None):
"""
:param path: 删除目录路径,不可递归
:return: 在当前工作路径下删除目录
"""
if path is None or path == "":
print("需要指明删除目录;Usage: mkdir [path]")
else:
path = path
cmd = "RMD " + path + "\n"
self.client.sendall(cmd.encode())
try:
for i in range(2):
res = self.client.recv(self.buffer).decode()
if res.startswith("250"):
print(res.strip("\r\n"))
break
elif res.startswith("550"):
print(res.strip("\r\n"))
break
except Exception as e:
pass
def delete(self, filename=None):
"""
:param filename: 删除当前路径的某个文件
:return: 删除当前路径的某个文件
"""
if filename is None or filename == "":
print("需要指明删除文件名; Usage: delete [filename] ")
else:
filename = filename
cmd = "DELE " + filename + "\n"
self.client.sendall(cmd.encode())
try:
for i in range(2):
res = self.client.recv(self.buffer).decode()
if res.startswith("250"):
print(res.strip("\r\n"))
break
elif res.startswith("550"):
print(res.strip("\r\n"))
break
except Exception as e:
pass
def get_size(self, filename=None):
"""
:param filename: 指明需要操作的文件
:return: 获取ftp服务操作文件大小
"""
cmd = "SIZE " + filename + "\n"
self.client.sendall(cmd.encode())
try:
for i in range(3):
res = self.client.recv(self.buffer).decode()
if res.startswith("213"):
remote_size = res.strip("\r\n").split(" ")[1]
return remote_size
else:
pass
except Exception as e:
print(str(e))
def get_file(self, filename=None):
"""
:param filename: 指明需要下载的文件
:return: 下载文件数据流
"""
if filename is None or filename == "":
print("请指明需要下载的文件; Usage: get [filename]")
else:
filename = filename
cmd = "RETR " + filename + "\n"
self.client.sendall(cmd.encode())
def put_file(self, filename=None):
"""
:param filename: 指明需要上传的文件
:return: 上传文件数据流
"""
if filename is None or filename == "":
print("请指明需要上传的文件; Usage: put [filename]")
else:
filename = filename
cmd = "STOR " + filename + "\n"
self.client.sendall(cmd.encode())
def quit(self):
"""
:return: 关闭会话并退出ftp连接
"""
cmd = "QUIT" + "\n"
self.client.sendall(cmd.encode())
self.client.close()
class DataFtpClient():
def __init__(self, host, port, buffer_size=1024, timeout=30):
"""
:param host: 连接地址
:param port: 连接端口
:param buffer_size: 接收缓冲区大小,默认1024 Bytes
:param timeout: 连接超时,默认30 Seconds
"""
self.host = host
self.inc_port = port[-2]
self.num_port = port[-1].replace(').\r\n', '')
self.timeout = timeout
self.buffer = buffer_size
self.client = socket.socket()
self.client.settimeout(self.timeout)
def _connftp(self):
"""
:return: ftp连接对象
"""
data_trans = int(self.inc_port) * 256 + int(self.num_port)
self.client.connect((self.host, data_trans))
self.client.settimeout(self.timeout)
def _get_ls_data_trans(self):
"""
:return: 获取查询文件目录列表
"""
try:
recv_data = ''
while True:
data_rsp = self.client.recv(self.buffer).decode()
if len(data_rsp) > 0:
data_rsp = data_rsp
else:
break
recv_data += data_rsp
return recv_data
except Exception as e:
pass
def get_write_data_trans(self, filename=None, filesize=None):
"""
:param filename: 文件传输写入文件名
:param filesize: ftp服务端文件size大小
:return: 下载进度
"""
try:
progress = tqdm.tqdm(range(
int(filesize)), f"Sending {filename}", unit="B", unit_scale=True, unit_divisor=1024)
recv_data = b''
while True:
data_rsp = self.client.recv(self.buffer).decode()
if len(data_rsp) > 0:
data_rsp = data_rsp.encode()
else:
break
recv_data += data_rsp
localOpt.write_local_file(filename, recv_data)
progress.update(len(data_rsp))
except Exception as e:
print(str(e))
def put_write_data_trans(self, filename=None, filesize=None):
"""
:param filename: 文件传输写入文件名
:param filesize: 计算机本地文件size大小
:return: 下载进度
"""
try:
progress = tqdm.tqdm(range(
int(filesize)), f"Sending {filename}", unit="B", unit_scale=True, unit_divisor=1024)
with open(filename, "r", encoding="utf8") as r_f:
while True:
put_data = r_f.read(self.buffer)
if len(put_data) > 0:
put_data = put_data.encode()
else:
break
self.client.sendall(put_data)
progress.update(len(put_data))
except Exception as e:
print (str(e))
def quit(self):
"""
:return: exit current session connection
"""
self.client.close()
class localOpt():
def local_file_exists(filename):
"""
:param filename: 判断本地文件是否存在
:return: 存在 True or 不存在 False
"""
if filename is None:
print("请指明一个文件")
return False
else:
return True if os.path.exists(filename) else False
def write_local_file(filename, data):
"""
:param filename: 指明需要下载的文件
:param data: ftp服务端数据文件内容
:return: 回写数据
"""
with open(filename, "wb") as w_f:
w_f.write(data)
def get_local_file_size(filename):
"""
:param filename: 获取计算机本地文件size大小
:return: 本地文件大小
"""
local_file_size = os.path.getsize(filename)
return local_file_size
def compare(local_szie, remote_size, filename):
"""
:param local_szie: 获取计算机本地文件size大小
:param remote_size: ftp服务端文件size大小
:param filename: 操作文件名
:return: 上传 or 下载文件是否正常
"""
if int(local_szie) == int(remote_size):
print("{}文件传输正常,大小为{}字节".format(filename, local_szie))
else:
print("{}文件传输异常,大小差异为{}字节".format(filename, int(remote_size) - int(local_szie)))
def main():
ftp_host = input("请输入FTP登录地址: ")
ftp_port = input("请输入FTP登录端口: ")
ConnClient = ConnftpClient(ftp_host, ftp_port)
ConnClient._connftp()
ConnClient._login_ftp()
while True:
cmd = input("ftp> ")
if cmd.startswith("ls"):
if len(cmd.split(" ")) == 1:
path = None
elif len(cmd.split(" ")) == 2:
path = cmd.split(" ")[1]
else:
print("请指定一个目录进行查询; Usage: ls [path]")
continue
trans_port_rsp = ConnClient._login_mode()
ConnClient.list_files(path)
DataClient = DataFtpClient(ftp_host, trans_port_rsp)
DataClient._connftp()
try:
for i in range(3):
if i == 0:
res = DataClient._get_ls_data_trans()
else:
ConnClient.client.recv(1024).decode()
except Exception as e:
pass
finally:
print(res)
DataClient.quit()
elif cmd.startswith("get"):
if len(cmd.split(" ")) == 1:
filename = None
elif len(cmd.split(" ")) == 2:
filename = cmd.split(" ")[1]
else:
print("请指定需要下载的文件; Usage: get [filename]")
continue
remote_size = ConnClient.get_size(filename)
if remote_size is None or remote_size == "":
print("请确认文件{}是否存在".format(filename))
continue
else:
trans_port_rsp = ConnClient._login_mode()
ConnClient.get_file(filename)
DataClient = DataFtpClient(ftp_host, trans_port_rsp)
DataClient._connftp()
try:
for i in range(3):
if i == 0:
DataClient.get_write_data_trans(filename, remote_size)
else:
ConnClient.client.recv(1024).decode()
except Exception as e:
pass
finally:
local_file_size = localOpt.get_local_file_size(filename)
localOpt.compare(remote_size, local_file_size, filename)
DataClient.quit()
elif cmd.startswith("put"):
if len(cmd.split(" ")) == 1:
filename = None
elif len(cmd.split(" ")) == 2:
filename = cmd.split(" ")[1]
else:
print("请指定需要上传的文件; Usage: put [filename]")
continue
if localOpt.local_file_exists(filename):
local_size = localOpt.get_local_file_size(filename)
trans_port_rsp = ConnClient._login_mode()
ConnClient.put_file(filename)
DataClient = DataFtpClient(ftp_host, trans_port_rsp)
DataClient._connftp()
DataClient.put_write_data_trans(filename, local_size)
DataClient.quit()
ConnClient._login_mode()
remote_size = ConnClient.get_size(filename)
local_size = localOpt.get_local_file_size(filename)
localOpt.compare(local_size, remote_size, filename)
elif cmd == "pwd":
res = ConnClient.get_path()
print(res)
elif cmd == "help":
ConnClient.help()
elif cmd.startswith("cd"):
if len(cmd.split(" ")) == 2:
path = cmd.split(" ")[1]
res = ConnClient.cwd(path)
print(res)
else:
print("请指定一个目录进行切换")
continue
elif cmd.startswith("mkdir"):
if len(cmd.split(" ")) == 2:
path = cmd.split(" ")[1]
ConnClient.mkdir(path)
else:
print("需要指明创建路径; Usage: mkdir [path]")
elif cmd.startswith("rmdir"):
if len(cmd.split(" ")) == 2:
path = cmd.split(" ")[1]
ConnClient.rmdir(path)
else:
print("需要指明删除目录; Usage: rmdir [path]")
elif cmd.startswith("delete"):
if len(cmd.split(" ")) == 2:
filename = cmd.split(" ")[1]
ConnClient.delete(filename)
else:
print("需要指明删除文件名; Usage: delete [filename]")
elif cmd == "quit" or cmd == "bye":
ConnClient.quit()
print("221 Goodbye.")
break
else:
print("无效命令,请输入help进行查看")
continue
main()