socketserver实现FTP

功能: 
1、用户加密认证
2、允许同时多用户登录
3、每个用户有自己的家目录 ,且只能访问自己的家目录
4、对用户进行磁盘配额,每个用户的可用空间不同
5、允许用户在ftp server上随意切换目录
6、允许用户查看当前目录下文件
7、允许上传和下载文件,保证文件一致性
8、文件传输过程中显示进度条
附加功能:支持文件的断点续传 环境
  python 3.5 特性:
  用socketserver实现FTP 主要功能实现:
  1、用户加密认证
    ConfigParser 是Python自带的模块, 用来读写配置文件,将用户信息以下边的格式存入account.py文件,读出后进行判断
[DEFAULT]

[alex]
Password = 123456
Quotation = 100 [jack]
Password = 123456
Quotation = 100
    2、允许同时多用户登录
      从scokerserver 继承 socketserver.ThreadingTCPServer即可实现
    3、每个用户有自己的家目录 ,且只能访问自己的家目录
      将所有路径封装在home目录下
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

USER_HOME = '%s\home' % BASE_DIR
LOG_DIR = '%s\log' % BASE_DIR
LOG_LEVEL = 'DEBUG'
    4、允许用户在ftp server上随意切换目录
      用os模块改变工作目录
os.chdir(func_val)
    5、允许用户查看当前目录下文件
      用os模块的os.listdir,下为server端代码
    def _ls(self,*args,**kwargs):
'''显示当前目录下的所有文件'''
if os.getcwd() == '%s\\bin'% settings.BASE_DIR:
user_home_dir = "%s\%s" % (settings.USER_HOME, self.user["Username"])
self.request.send(json.dumps(os.listdir(user_home_dir)).encode())
else:self.request.send(json.dumps(os.listdir()).encode())
    6、允许上传和下载文件,保证文件一致性
      判断上传和下载的文件在传输前后的大小,用以判断文件是否保持一致
 #server端
#
def _put(self,*args,**kwargs):
'''上传文件命令'''
data = args[0] response = self.get_response()
if response["status_code"] == 257: # ready to receive
self.request.send(b'') # send confirmation to server
user_home_dir = "%s\%s" % (settings.USER_HOME, self.user["Username"])
base_filename = "%s\%s" % (user_home_dir, data.get('filename'))
received_size = 0
file_obj = open(base_filename, 'wb')
if data.get('md5'):
md5_obj = hashlib.md5()
while received_size < response['file_size']:
line = self.request.recv(4096)
received_size += len(line)
file_obj.write(line)
md5_obj.update(line)
else:
file_obj.close()
md5_val = md5_obj.hexdigest()
self.send_response(258, {'md5': md5_val})
print("-----file rece done-----") else:
while received_size < response['file_size']:
data = self.request.recv(4096)
received_size += len(data)
file_obj.write(data)
else:
print("-----file rece done-----")
file_obj.close()
else:
print(STATUS_CODE[response["status_code"]]) def _get(self,*args,**kwargs):
'''下载文件命令'''
data = args[0]
if data.get('filename') is None:
self.send_response(255)
user_home_dir = "%s\%s" %(settings.USER_HOME,self.user["Username"])
file_abs_path = "%s\%s" %(user_home_dir,data.get('filename'))
print("file abs path",file_abs_path) if os.path.isfile(file_abs_path):
file_obj = open(file_abs_path,'rb')
file_size = os.path.getsize(file_abs_path)
self.send_response(257,data = {'file_size':file_size})
self.request.recv(1) #等待客户端确认,防粘包 if data.get('md5'):
md5_obj = hashlib.md5()
for line in file_obj:
self.request.send(line)
md5_obj.update(line)
else:
file_obj.close()
md5_val = md5_obj.hexdigest()
self.send_response(258,{'md5':md5_val})
print("send file done...")
else:
for line in file_obj:
self.request.send(line)
else:
file_obj.close()
print("send file done...")
else:
self.send_response(256) # client端
#
def _get(self,cmd_list):
'''下载文件'''
print("get--",cmd_list)
if len(cmd_list) == 1:
print("no filename follows...")
return
data_header = {
'action':'get',
'filename':cmd_list[1]
}
if self.__md5_required(cmd_list):
data_header['md5'] = True self.sock.send(json.dumps(data_header).encode())
response = self.get_response()
print(response)
try:
if response["status_code"] == 257:#ready to receive
self.sock.send(b'') #send confirmation to server
base_filename = cmd_list[1].split('/')[-1]
received_size = 0
file_obj = open(base_filename,'wb')
if self.__md5_required(cmd_list):
md5_obj = hashlib.md5()
progress = self.show_progress(response['file_size']) #generator
progress.__next__()
while received_size < response['file_size']:
data = self.sock.recv(4096)
received_size += len(data)
try:
progress.send(len(data))
except StopIteration as e:
print("100%")
file_obj.write(data)
md5_obj.update(data)
else:
print("-----file rece done-----")
file_obj.close()
md5_val = md5_obj.hexdigest()
md5_from_server = self.get_response()
if md5_from_server['status_code'] == 258:
if md5_from_server['md5'] == md5_val:
print("%s 文件一致性校验成功!"% base_filename)
print(md5_val,md5_from_server) else:
progress = self.show_progress(response['file_size']) # generator
progress.__next__()
while received_size < response['file_size']:
data = self.sock.recv(4096)
received_size += len(data)
file_obj.write(data)
try:
progress.send(len(data))
except StopIteration as e:
print("100%")
else:
print("-----file rece done-----")
file_obj.close()
else:
print(STATUS_CODE[response["status_code"]])
except Exception as e:
base_file_size = os.path.getsize(base_filename)
with open('data\\breakpoint', 'wb') as br_po:
data_header['action'] = 'breakpoint'
data_header['breakpoint'] = base_file_size
br_po.write(json.dumps(data_header).encode()) def _put(self,cmd_list):
'''上传文件'''
print("put--", cmd_list)
if len(cmd_list) == 1:
print("no filename follows...")
return
data_header = {
'action': 'put',
'filename': cmd_list[1]
}
if self.__md5_required(cmd_list):
data_header['md5'] = True self.sock.send(json.dumps(data_header).encode())
if os.path.isfile(cmd_list[1]):
file_obj = open(cmd_list[1], 'rb')
file_size = os.path.getsize(cmd_list[1])
self.send_response(257, data={'file_size': file_size})
self.sock.recv(1) # 等待服务器端确认
if self.__md5_required(cmd_list):
md5_obj = hashlib.md5()
progress = self.show_progress(file_size) # generator
progress.__next__()
for line in file_obj:
self.sock.send(line)
try:
progress.send(len(line))
except StopIteration as e:
print("100%")
md5_obj.update(line)
else:
file_obj.close()
md5_val = md5_obj.hexdigest()
md5_from_server = self.get_response()
if md5_from_server['md5'] == md5_val:
print("%s 文件一致性校验成功!"% cmd_list[1])
self.send_response(258, {'md5': md5_val})
print("send file done...")
else:
progress = self.show_progress(file_size) # generator
progress.__next__()
for line in file_obj:
self.sock.send(line)
try:
progress.send(len(line))
except StopIteration as e:
print("100%")
else:
file_obj.close()
print("send file done...")
else:
print(256)

get and put

    7、文件传输过程中显示进度条
      根据已传文件大小和总文件大小比对判断,输出符号
     def show_progress(self,total):
'''显示进度条
total: 文件大小'''
received_size = 0
current_percent = 0
while received_size < total:
if int((received_size / total) * 100) > current_percent:
print(">",end='',flush=True)
current_percent = int((received_size / total) * 100)
new_size = yield
received_size += new_size

进度条

    附加功能:支持文件的断点续传
      将上次断点信息记录下来,也就是记录已传文件大小,再次启用时,将目标文件光标移到上次断点处,然后进行续传
     def _breakpoint(self,*args,**kwargs):
'''断点续传'''
data = args[0]
if data.get('filename') is None:
self.send_response(255)
user_home_dir = "%s\%s" % (settings.USER_HOME, self.user["Username"])
file_abs_path = "%s\%s" % (user_home_dir, data.get('filename'))
print("file abs path", file_abs_path)
print(data.get('breakpoint')) if os.path.isfile(file_abs_path):
file_obj = open(file_abs_path, 'rb')
file_obj.seek(data.get('breakpoint'))
file_size = os.path.getsize(file_abs_path)
self.send_response(257, data={'file_size': file_size})
self.request.recv(1) # 等待客户端确认 if data.get('md5'):
md5_obj = hashlib.md5()
for line in file_obj:
self.request.send(line)
md5_obj.update(line)
else:
file_obj.close()
md5_val = md5_obj.hexdigest()
self.send_response(258, {'md5': md5_val})
print("send file done...")
else:
for line in file_obj:
self.request.send(line)
else:
file_obj.close()
print("send file done...")
else:
self.send_response(256)
pass

breakpoint

   主要知识点:

    类的继承
os模块的应用
json模块的应用
类方法的运用
md5加密方法
scoket链接
scoketserver链接
反射
异常处理 小结:
这个程序主要用socketserver实现了一台服务器链接多个客户端,并且进行信息交互,并且实现了几个简单的功能,如:get 文件下载、put 上传文件、cd 切换目录、ls 查看文件、breakpoint断点续传。 主要代码:
 #server端
# import os,sys
import hashlib
import socket
import socketserver
import json
import configparser
from conf import settings STATUS_CODE = {
250 : "Invalid cmd format,e.g:{'action':'get','filename':'test.py','size':344",
251 : "Invalid cmd",
252 : "Invalid auth data",
253 : "Wrong username or password..",
254 : "Passed authentication",
255 : "Filename doesn't provided",
256 : "File doesn't exist on server",
257 : "ready to send file",
258 : "md5 verification",
259 : "Directory has been switched"
} class FTPHandler(socketserver.BaseRequestHandler):
'''定义request handler类,从BaseRequestHandler类继承'''
def handle(self):
'''
获取服务器端的信息
如果传来cd命令,改变工作目录
''' while True:
self.data = self.request.recv(1024).strip()
if not self.data:
print('Client closed..')
break
data = json.loads(self.data.decode())
if data.get('action') is not None:
if hasattr(self,'_%s'%data.get('action')):
func =getattr(self,'_%s'%data.get('action'))
func_val = func(data)
if data.get('action') == 'cd': #cd 命令,改变工作目录
os.chdir(func_val)
else:pass
else:
print('Invalid cmd')
self.send_response(251)
else:
print('Invalid cmd format')
self.send_response(250) def send_response(self,status_code,data=None):
'''向客户端返回数据'''
response = {'status_code': status_code, 'status_msg':STATUS_CODE[status_code]}
if data:
response.update(data)
self.request.send(json.dumps(response).encode()) def _auth(self,*args,**kwargs):
'''判断用户是否输入完整的用户名和密码
验证用户名和密码是否合法'''
data = args[0]
if data.get('username') is None or data.get('password') is None:
self.send_response(252) user = self.authenticate(data.get('username'),data.get('password'))
if user is None:
self.send_response(253)
else:
print('passed authentication',user)
self.user = user
self.send_response(254) def authenticate(self,username,password): #
'''验证用户合法性,合法返回用户数据'''
config = configparser.ConfigParser()
config.read(settings.ACCOUNT_FILE)
if username in config.sections():
_password = config[username]['Password']
if _password == password:
print('pass auth..',username)
config[username]["Username"] = username
return config[username] def get_response(self):
'''接收客户端回复结果'''
data = self.request.recv(1024)
data = json.loads(data.decode())
return data def show_progress(self,total):
'''显示进度条
total: 文件大小'''
received_size = 0
current_percent = 0
while received_size < total:
if int((received_size / total) * 100) > current_percent:
print(">",end='',flush=True)
current_percent = int((received_size / total) * 100)
new_size = yield
received_size += new_size def _put(self,*args,**kwargs):
'''上传文件命令'''
data = args[0] response = self.get_response()
if response["status_code"] == 257: # ready to receive
self.request.send(b'') # send confirmation to server
user_home_dir = "%s\%s" % (settings.USER_HOME, self.user["Username"])
base_filename = "%s\%s" % (user_home_dir, data.get('filename'))
received_size = 0
file_obj = open(base_filename, 'wb')
if data.get('md5'):
md5_obj = hashlib.md5()
while received_size < response['file_size']:
line = self.request.recv(4096)
received_size += len(line)
file_obj.write(line)
md5_obj.update(line)
else:
file_obj.close()
md5_val = md5_obj.hexdigest()
self.send_response(258, {'md5': md5_val})
print("-----file rece done-----") else:
while received_size < response['file_size']:
data = self.request.recv(4096)
received_size += len(data)
file_obj.write(data)
else:
print("-----file rece done-----")
file_obj.close()
else:
print(STATUS_CODE[response["status_code"]]) def _get(self,*args,**kwargs):
'''下载文件命令'''
data = args[0]
if data.get('filename') is None:
self.send_response(255)
user_home_dir = "%s\%s" %(settings.USER_HOME,self.user["Username"])
file_abs_path = "%s\%s" %(user_home_dir,data.get('filename'))
print("file abs path",file_abs_path) if os.path.isfile(file_abs_path):
file_obj = open(file_abs_path,'rb')
file_size = os.path.getsize(file_abs_path)
self.send_response(257,data = {'file_size':file_size})
self.request.recv(1) #等待客户端确认,防粘包 if data.get('md5'):
md5_obj = hashlib.md5()
for line in file_obj:
self.request.send(line)
md5_obj.update(line)
else:
file_obj.close()
md5_val = md5_obj.hexdigest()
self.send_response(258,{'md5':md5_val})
print("send file done...")
else:
for line in file_obj:
self.request.send(line)
else:
file_obj.close()
print("send file done...")
else:
self.send_response(256) def _ls(self,*args,**kwargs):
'''显示当前目录下的所有文件'''
if os.getcwd() == '%s\\bin'% settings.BASE_DIR:
user_home_dir = "%s\%s" % (settings.USER_HOME, self.user["Username"])
self.request.send(json.dumps(os.listdir(user_home_dir)).encode())
else:self.request.send(json.dumps(os.listdir()).encode()) def _cd(self,*args,**kwargs):
'''改变工作目录'''
data = args[0]
user_home_dir = "%s\%s" % (settings.USER_HOME, self.user["Username"])
file_abs_path = "%s\%s" %(user_home_dir,data.get('path'))
try:
os.listdir(file_abs_path)
except FileNotFoundError as e:
self.request.send(json.dumps(e).encode())
return
self.request.send(json.dumps(259).encode())
os.chdir(file_abs_path)
return file_abs_path def _breakpoint(self,*args,**kwargs):
'''断点续传'''
data = args[0]
if data.get('filename') is None:
self.send_response(255)
user_home_dir = "%s\%s" % (settings.USER_HOME, self.user["Username"])
file_abs_path = "%s\%s" % (user_home_dir, data.get('filename'))
print("file abs path", file_abs_path)
print(data.get('breakpoint')) if os.path.isfile(file_abs_path):
file_obj = open(file_abs_path, 'rb')
file_obj.seek(data.get('breakpoint'))
file_size = os.path.getsize(file_abs_path)
self.send_response(257, data={'file_size': file_size})
self.request.recv(1) # 等待客户端确认 if data.get('md5'):
md5_obj = hashlib.md5()
for line in file_obj:
self.request.send(line)
md5_obj.update(line)
else:
file_obj.close()
md5_val = md5_obj.hexdigest()
self.send_response(258, {'md5': md5_val})
print("send file done...")
else:
for line in file_obj:
self.request.send(line)
else:
file_obj.close()
print("send file done...")
else:
self.send_response(256)
pass

server端

 #client端
# import socket
import hashlib
import os,json
import optparse
import socketserver STATUS_CODE = {
250 : "Invalid cmd format,e.g:{'action':'get','filename':'test.py','size':344",
251 : "Invalid cmd",
252 : "Invalid auth data",
253 : "Wrong username or password..",
254 : "Passed authentication",
255 : "Filename doesn't provided",
256 : "File doesn't exist on server",
257 : "ready to send file",
258 : "md5 verification",
259 : "Directory has been switched"
} class FTPClient(object):
'''客户端'''
def __init__(self):
'''用户信息输入格式化
变量定义'''
parser = optparse.OptionParser()
parser.add_option('-s', '--server', dest='server', help ='ftp server ip_addr')
parser.add_option('-p','--port', type='int', dest='port', help='ftp server port')
parser.add_option('-U', '--username', dest='username', help='username')
parser.add_option('-P', '--Password', dest='password', help='password')
self.options, self.args = parser.parse_args()
self.verify_args(self.options,self.args)
self.make_connection() def make_connection(self):
'''连接服务器'''
self.sock = socket.socket()
self.sock.connect((self.options.server,self.options.port)) def verify_args(self,options,args):
'''校验参数合法性'''
if options.username and options.password:
pass
elif options.username is None and options.password is None:
pass
else:
if options.username is None or options.password is None:
print('Error:username and password must be provided together..')
if options.server and options.port:
if options.port > 0 and options.port < 65535:
return True
else:
exit('Err:host port must in 0-65535') def authenticate(self):
'''用户验证'''
if self.options.username:
return self.get_auth_result(self.options.username, self.options.password)
else:
retry_count = 0
while retry_count < 3:
username = input("username:").strip()
password = input("password:").strip()
return self.get_auth_result(username,password) def get_auth_result(self,user,password):
'''用户认证'''
data = {'action':'auth',
'username':user,
'password':password} self.sock.send(json.dumps(data).encode())
response = self.get_response()
if response.get('status_code') == 254:
print("Passed authentication!")
self.user = user
return True
else:
print(data.get('status_msg'))
print('response:',data) def __md5_required(self,cmd_list):
'''检测命令是否需要进行MD5验证'''
if '--md5' in cmd_list:
return True def show_progress(self,total):
'''进度条'''
received_size = 0
current_percent = 0
while received_size < total:
if int((received_size / total) * 100) > current_percent:
print(">",end='',flush=True)
current_percent = int((received_size / total) * 100)
new_size = yield
received_size += new_size def send_response(self,status_code,data=None):
'''向服务器端返回数据'''
response = {'status_code': status_code, 'status_msg':STATUS_CODE[status_code]}
if data:
response.update(data)
self.sock.send(json.dumps(response).encode()) def get_response(self):
'''得到服务器端回复结果'''
data = self.sock.recv(1024)
data = json.loads(data.decode())
return data def _get(self,cmd_list):
'''下载文件'''
print("get--",cmd_list)
if len(cmd_list) == 1:
print("no filename follows...")
return
data_header = {
'action':'get',
'filename':cmd_list[1]
}
if self.__md5_required(cmd_list):
data_header['md5'] = True self.sock.send(json.dumps(data_header).encode())
response = self.get_response()
print(response)
try:
if response["status_code"] == 257:#ready to receive
self.sock.send(b'') #send confirmation to server
base_filename = cmd_list[1].split('/')[-1]
received_size = 0
file_obj = open(base_filename,'wb')
if self.__md5_required(cmd_list):
md5_obj = hashlib.md5()
progress = self.show_progress(response['file_size']) #generator
progress.__next__()
while received_size < response['file_size']:
data = self.sock.recv(4096)
received_size += len(data)
try:
progress.send(len(data))
except StopIteration as e:
print("100%")
file_obj.write(data)
md5_obj.update(data)
else:
print("-----file rece done-----")
file_obj.close()
md5_val = md5_obj.hexdigest()
md5_from_server = self.get_response()
if md5_from_server['status_code'] == 258:
if md5_from_server['md5'] == md5_val:
print("%s 文件一致性校验成功!"% base_filename)
print(md5_val,md5_from_server) else:
progress = self.show_progress(response['file_size']) # generator
progress.__next__()
while received_size < response['file_size']:
data = self.sock.recv(4096)
received_size += len(data)
file_obj.write(data)
try:
progress.send(len(data))
except StopIteration as e:
print("100%")
else:
print("-----file rece done-----")
file_obj.close()
else:
print(STATUS_CODE[response["status_code"]])
except Exception as e:
base_file_size = os.path.getsize(base_filename)
with open('data\\breakpoint', 'wb') as br_po:
data_header['action'] = 'breakpoint'
data_header['breakpoint'] = base_file_size
br_po.write(json.dumps(data_header).encode()) def _put(self,cmd_list):
'''上传文件'''
print("put--", cmd_list)
if len(cmd_list) == 1:
print("no filename follows...")
return
data_header = {
'action': 'put',
'filename': cmd_list[1]
}
if self.__md5_required(cmd_list):
data_header['md5'] = True self.sock.send(json.dumps(data_header).encode())
if os.path.isfile(cmd_list[1]):
file_obj = open(cmd_list[1], 'rb')
file_size = os.path.getsize(cmd_list[1])
self.send_response(257, data={'file_size': file_size})
self.sock.recv(1) # 等待服务器端确认
if self.__md5_required(cmd_list):
md5_obj = hashlib.md5()
progress = self.show_progress(file_size) # generator
progress.__next__()
for line in file_obj:
self.sock.send(line)
try:
progress.send(len(line))
except StopIteration as e:
print("100%")
md5_obj.update(line)
else:
file_obj.close()
md5_val = md5_obj.hexdigest()
md5_from_server = self.get_response()
if md5_from_server['md5'] == md5_val:
print("%s 文件一致性校验成功!"% cmd_list[1])
self.send_response(258, {'md5': md5_val})
print("send file done...")
else:
progress = self.show_progress(file_size) # generator
progress.__next__()
for line in file_obj:
self.sock.send(line)
try:
progress.send(len(line))
except StopIteration as e:
print("100%")
else:
file_obj.close()
print("send file done...")
else:
print(256) def _ls(self,*args,**kwargs):
'''获取当前目录下的所有文件'''
print('ls'.center(30,'-'))
data_header = {
'action': 'ls',
}
self.sock.send(json.dumps(data_header).encode())
ls_val = self.sock.recv(1024)
ls_val = json.loads(ls_val.decode())
for obj in ls_val:
print(obj) def _cd(self,cmd_list):
'''改变工作目录'''
print("cd--", cmd_list)
if len(cmd_list) == 1:
cmd_list.append('')
data_header = {
'action': 'cd',
'path': cmd_list[1]
}
self.sock.send(json.dumps(data_header).encode())
server_path = self.sock.recv(1024)
server_path = json.loads(server_path.decode())
if server_path == 259 :
print("%s >" % cmd_list[1],end='') def _breakpoint(self,*args,**kwargs):
'''断点续传'''
with open('data\\breakpoint', 'rb') as br_po:
data_header = json.loads(br_po.read().decode())
br_po.close()
if data_header:
print(data_header)
self.sock.send(json.dumps(data_header).encode())
response = self.get_response()
try:
if response["status_code"] == 257: # ready to receive
self.sock.send(b'') # send confirmation to server
base_filename = data_header['filename'].split('/')[-1]
received_size = data_header['breakpoint']
file_obj = open(base_filename, 'ab')
file_obj.seek(data_header['breakpoint'])
if self.__md5_required(data_header):
md5_obj = hashlib.md5()
progress = self.show_progress(response['file_size']) # generator
progress.__next__()
while received_size < response['file_size']:
data = self.sock.recv(4096)
received_size += len(data)
try:
progress.send(len(data))
except StopIteration as e:
print("100%")
file_obj.write(data)
md5_obj.update(data)
else:
print("-----file rece done-----")
file_obj.close()
md5_val = md5_obj.hexdigest()
md5_from_server = self.get_response()
if md5_from_server['status_code'] == 258:
if md5_from_server['md5'] == md5_val:
print("%s 文件一致性校验成功!" % base_filename)
with open('data\\breakpoint', 'wb') as br_po:
br_po.write()
print(md5_val, md5_from_server) else:
progress = self.show_progress(response['file_size']) # generator
progress.__next__()
while received_size < response['file_size']:
data = self.sock.recv(4096)
received_size += len(data)
file_obj.write(data)
try:
progress.send(len(data))
except StopIteration as e:
print("100%")
else:
print("-----file rece done-----")
file_obj.close()
with open('data\\breakpoint', 'wb') as br_po:
br_po.write()
except Exception as e:
base_file_size = os.path.getsize(base_filename)
with open('data\\breakpoint', 'wb') as br_po:
data_header['breakpoint'] = base_file_size
br_po.write(json.dumps(data_header).encode())
else:print('There is no need to transfer files..') def _helps(self,*args,**kwargs):
'''帮助信息'''
helps = '''
get 文件名 #下载文件
put 文件名 #上传文件
ls #获取当前目录下的所有文件
cd #改变工作目录
breakpoint #断点续传'''
print(helps) def interactive(self):
'''根据命令分配函数'''
if self.authenticate():
print('---start interactive iwth u...(helps 帮助信息)')
while True:
choice = input("[%s]:" % self.user).strip()
if len(choice) == 0: continue
cmd_list = choice.split()
if hasattr(self, "_%s" % cmd_list[0]):
func = getattr(self, "_%s" % cmd_list[0])
func(cmd_list)
else:
print("Invalid cmd") if __name__ == '__main__':
ftp = FTPClient()
ftp.interactive()

client端

上一篇:scala中的抽象类


下一篇:智能合约 helloworld