python实现ftp(客户端)

该文档为用python3实现ftp上传下载等功能。

 1 import optparse
 2 import socket
 3 import json,os
 4 import shelve
 5 
 6 class FtpClient(object):
 7     """ftp客户端"""
 8     MSG_SIZE = 1024  # 消息最长1024
 9 
10     def __init__(self):
11         self.username = None
12         self.terminal_display = None
13         self.shelve_obj = shelve.open(".luffy_db")
14         self.current_dir = None
15 
16         parser = optparse.OptionParser()#创建parser这个对象,optparse这个模块是个类
17         parser.add_option("-s","--server", dest="server", help="ftp server ip_addr")
18         parser.add_option("-P","--port",type="int", dest="port", help="ftp server port")
19         parser.add_option("-u","--username", dest="username", help="username info")
20         parser.add_option("-p","--password", dest="password", help="password info")
21         self.options , self.args = parser.parse_args()
22 
23         #print(self.options,self.args,type(self.options),self.options.server)
24         self.argv_verification()#调用检查参数合法性函数
25 
26         self.make_connection()#调用建立socket连接函数

上面这段首先创建一个类。该客户端代码均在这个类中实现。

定义一个接收值的变量,然后定义初始化函数,定义几个静态属性,这几个属性有些是后面函数需要的,所以提前定义。

之后用OptionParser这个函数生成一个命令行声明。这个函数具体相关可以从网络中查到。

再之后调用两个函数,一个是检查参数合法性的,另一个是建立socket连接。

 

 1     def argv_verification(self):
 2         """检查参数合法性"""
 3         if not self.options.server or not self.options.port:
 4             #如果options的server参数或port参数不为真则exit()关闭并打印括号内内容。
 5             exit("Error: must supply server and port parameters")
 6 
 7 
 8     def make_connection(self):
 9         """建立socket链接"""
10         self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
11         self.sock.connect((self.options.server,self.options.port))
12 
13     def get_response(self):#接收返回值
14         """获取服务器端返回"""
15         data = self.sock.recv(self.MSG_SIZE)#接收1024个
16         return json.loads(data.decode())#返回decode之后内容

上面代码为三个函数,分别是检查合法性、创建连接、接收返回值。

 1     def auth(self):
 2         """用户认证"""
 3         count = 0
 4         while count < 3:#输入最多3次
 5             username = input("username:").strip()
 6             if not username:continue#如果输入不为真则继续
 7             password = input("password:").strip()
 8 
 9             cmd = {
10                 'action_type':'auth',#action_type定义功能关键词,将关键词传入值
11                 'username':username,#用户名
12                 'password':password,#密码
13             }
14             #定义一个字典,该字典将为以后客户端与服务端通讯作为模板。
15             self.sock.send(json.dumps(cmd).encode("utf-8"))#将字典序列化然后encode,发送
16             response = self.get_response()#调用get_response函数,并赋值。该函数获取服务器端返回内容,并反序列化
17             print("response:",response)#打印得到的内容
18             if response.get('status_code') == 200:#pass auth#如果得到的内容是状态码200
19                 self.username = username
20                 # 定义一个数据属性,将__init__里的None赋值掉,为了以后调用方便
21                 self.terminal_display = "[%s]>>:" % self.username#将用户名赋值给显示
22                 self.current_dir = "\\"#设置当前目录路径,将init函数重写
23                 return True
24             else:
25                 print(response.get("status_msg"))#如果验证不成功打印状态码
26             count += 1

auth函数主要负责登录认证功能。注释写的蛮详细了,我就不赘述了。

 1     def unfinished_file_check(self):
 2         """检查shelve db ,把为正常传完的文件列表打印,按用户的指令决定是否重传"""
 3         if list(self.shelve_obj.keys()):#如果shelve列表为真
 4             print("-------Unfinished file list -------------")
 5             for index,abs_file in enumerate(self.shelve_obj.keys()):
 6                 #enumerate函数将一个可迭代的容器列出它的索引
 7                 received_file_size = os.path.getsize(self.shelve_obj[abs_file][1])
 8                 #接收到的大小=getsize大小
 9                 print("%s. %s    %s    %s   %s" %(index,abs_file,
10                                                   self.shelve_obj[abs_file][0],
11                                                   received_file_size,
12                                                   received_file_size/self.shelve_obj[abs_file][0]*100
13                                                   ))
14 
15             while True:
16                 choice = input("[select file index to re-download]").strip()
17                 if not choice:continue
18                 if choice == 'back':break
19                 if choice.isdigit():
20                     choice = int(choice)
21                     if choice >= 0 and choice <= index:
22                         selected_file = list(self.shelve_obj.keys())[choice]
23                         already_received_size = os.path.getsize(self.shelve_obj[selected_file][1])
24 
25                         print("tell server to resend file ", selected_file)
26                         #abs_filename + size +received_size
27                         self.send_msg('re_get', file_size=self.shelve_obj[selected_file][0],
28                                       received_size=already_received_size,
29                                       abs_filename=selected_file)
30 
31                         response = self.get_response()
32                         if response.get('status_code') == 401:#"File exist ,ready to re-send !",
33                             local_filename = self.shelve_obj[selected_file][1]
34 
35 
36 
37                             f = open(local_filename,'ab')
38                             total_size = self.shelve_obj[selected_file][0]
39                             recv_size = already_received_size
40                             current_percent = int(recv_size /total_size *100)
41                             progress_generator = self.progress_bar(total_size,current_percent,current_percent)
42                             progress_generator.__next__()
43                             while recv_size < total_size:
44                                 if total_size - recv_size < 8192:  # last recv
45                                     data = self.sock.recv(total_size - recv_size)
46                                 else:
47                                     data = self.sock.recv(8192)
48                                 recv_size += len(data)
49                                 f.write(data)
50                                 progress_generator.send(recv_size)
51                                 #progress_generator.send(received_size)
52                                 #print(recv_size,total_size)
53                             else:
54                                 print("file re-get done")
55                         else:
56                             print(response.get("status_msg"))
断点续传

该函数为断点续传功能,改天写注释。该函数大量使用shelve。

 1     def interactive(self):
 2         """处理与Ftpserver的所有交互"""
 3         if self.auth():
 4             self.unfinished_file_check()
 5 
 6             while True:
 7                 user_input  = input(self.terminal_display).strip()#输入的时候显示名字后来变成路径
 8                 if not user_input:continue
 9 
10                 cmd_list = user_input.split()#登录之后,将输入的内容转化成列表
11                 if hasattr(self,"_%s"%cmd_list[0]):#判断输入的内容列表索引0 这个类里是否有这个方法
12                     func = getattr(self,"_%s"%cmd_list[0])#获取这个方法。
13                     func(cmd_list[1:])#执行这个函数,将input后面所有的内容作为列表传进去
14                     #get fil1 --md5

interactive函数负责与服务端的交互。首先调用登陆验证,然后就是下载未完成文件的检查,之后是负责交互。

 1     def parameter_check(self,args,min_args=None,max_args=None,exact_args=None):
 2         """参数个数合法性检查
 3         # args这个传进来的参数是一个列表,min_args默认值看函数的定义。
 4         下面的判断如果符合一条则报错,没有符合的则正常。"""
 5         if min_args:
 6             if len(args) < min_args:
 7                 print("must provide at least %s parameters but %s received." %(min_args,len(args)))
 8                 return False
 9         if max_args:
10             if len(args) > max_args:
11                 print("need at most %s parameters but %s received." %(max_args,len(args)))
12                 return False
13 
14         if exact_args:
15             if len(args) != exact_args:
16                 print("need exactly %s parameters but %s received." % (exact_args, len(args)))
17                 return False
18 
19         return True
parameter_check

parameter_check是参数的合法性检查,检查输入内容命令+参数后面的参数格式。

    def send_msg(self,action_type,**kwargs ):#action_type传进来的是get,**是文件名
        """打包消息并发送到远程"""
        msg_data = {
            'action_type': action_type,
            'fill':''
        }
        msg_data.update(kwargs)#将文件名这个字典作为key传到字典里,update本质是两个字典合并成一个字典。

        bytes_msg = json.dumps(msg_data).encode()#将字典序列化,目的是为了将字典转化为字符串,好算长度
        if self.MSG_SIZE > len(bytes_msg):#如果1024>这个序列化后的字典
            msg_data['fill'] = msg_data['fill'].zfill( self.MSG_SIZE - len(bytes_msg))
            bytes_msg = json.dumps(msg_data).encode()#将调整大小后的字典序列化

        self.sock.send(bytes_msg)#发送

send_msg负责具体向服务端发送消息的工作。

 1     def _ls(self,cmd_args):
 2         """
 3         display current dir's file list
 4         :param cmd_args:
 5         :return:
 6         """
 7         self.send_msg(action_type='ls')#发送消息,将ls命令通过字典发出去
 8         response = self.get_response() #1024#收消息
 9         print('ls recv',response)#打印
10         if response.get('status_code') == 302:#取出收到的字典中的状态码,判断是否为302
11             cmd_result_size = response.get('cmd_result_size')#收到信息获取返回值长度
12             print(cmd_result_size)#打印长度看一下
13             received_size = 0#定义一个初始值
14             cmd_result = b''#定义一个初始值
15             while received_size < cmd_result_size:#当收入大小小于实际大小
16                 if cmd_result_size - received_size < 8192:#last receive#实际大小-收入大小小于8192
17                     data = self.sock.recv( cmd_result_size -  received_size)#继续收入上面这个差值
18                 else:
19                     data = self.sock.recv(8192)#入过差值大过8192 那么索性直接收8192
20                 cmd_result += data#收入的文件+=recv的内容
21                 received_size += len(data)#收入的长度+=recv的长度
22             else:
23                 print(cmd_result.decode("gbk"))#当收入大小不小于实际大小,那么直接打印

_ls函数是负责ls命令的功能,调用send_msg发送命令并调用get_response接收返回值,期间解决了粘包的问题。

 1     def _cd(self,cmd_args):
 2         """change to target dir"""
 3         if self.parameter_check(cmd_args, exact_args=1):#参数合法性校验
 4             target_dir = cmd_args[0] #赋值
 5             self.send_msg('cd',target_dir=target_dir) #发送消息
 6             response = self.get_response()#接收消息
 7             print(response)#打印
 8             if response.get("status_code") == 350:#dir changed
 9                 self.terminal_display = "[/%s]" % response.get('current_dir')#修改输入端显示
10                 self.current_dir = response.get('current_dir')#将current_dir赋值

_cd函数负责cd命令的处理。

 1     def _get(self,cmd_args):
 2         """download file from ftp server
 3         1.拿到文件名
 4         2.发送到远程
 5         3.等待服务器返回消息
 6             3.1 如果文件存在, 拿到文件大小
 7                 3.1.1 循环接收
 8             3.2 文件如果不存在
 9                 print status_msg
10 
11         """
12         if self.parameter_check(cmd_args,min_args=1):
13             # cmd_args这个传进来的参数是一个列表,min_args默认是1,执行判断参数合法性函数,将列表传进去
14             filename = cmd_args[0]#将参数0赋值为文件名
15             self.send_msg(action_type='get',filename=filename)#运行发送函数,将action为get等内容字典传值。
16             response = self.get_response()#接收返回值
17             if response.get('status_code') == 301:# file exist ,ready to receive
18                 file_size = response.get('file_size')#取返回值里的文件大小
19                 received_size = 0#先定义为0
20 
21                 progress_generator = self.progress_bar(file_size)
22                 progress_generator.__next__()
23 
24                 #save to shelve db
25                 file_abs_path = os.path.join(self.current_dir,filename)
26                 self.shelve_obj[file_abs_path] = [file_size,"%s.download" %filename]
27 
28                 f = open("%s.download" %filename,"wb")#打开一个空文件
29                 while received_size < file_size:#如果收到的大小小于文件大小
30                     if file_size - received_size < 8192:#last recv如果文件大小-收到的大小之后小于8192
31                         data = self.sock.recv(  file_size - received_size )#继续接收文件大小没有收到的数量值
32                     else:
33                         data = self.sock.recv(8192)#如果大于8192则直接接收8192
34                     received_size += len(data)#没接收一次数据,将数据大小增加
35                     f.write(data)
36                     progress_generator.send(received_size)#打印文件大小和接收大小
37 
38                     #print(received_size,file_size)
39                 else:
40                     print('\n')
41                     print("---file [%s] recv done,received size [%s]----"%( filename,file_size))
42                     del self.shelve_obj[file_abs_path]
43                     f.close()#如果接收到的大小不小于文件大小,等于文件大小,那就传完了
44                     os.rename("%s.download"%filename,filename)
45 
46             else:
47                 print(response.get('status_msg'))#打印状态码,文件不存在

_get函数负责下载相关,同样需处理粘包工作。

 1     def progress_bar(self,total_size,current_percent=0,last_percent=0):
 2 
 3         # current_percent = 0
 4         # last_percent = 0
 5         #received_size = 0
 6         while True:
 7             received_size = yield current_percent
 8             current_percent = int(received_size / total_size *100)
 9 
10             if current_percent > last_percent:
11                 print("#" * int(current_percent / 2) + "{percent}%".format(percent=current_percent), end='\r',
12                       flush=True)
13                 last_percent = current_percent  # 把本次循环的percent赋值给last

progress_bar。进度条功能。

 1     def _put(self,cmd_args):
 2         """上传本地文件到服务器
 3         1. 确保本地文件存在
 4         2. 拿到文件名+大小,放到消息头里发给远程
 5         3. 打开文件,发送内容
 6         """
 7         if self.parameter_check(cmd_args, exact_args=1):#参数合法性检查
 8             local_file = cmd_args[0]#取文件名
 9             if os.path.isfile(local_file):#判断文件存在
10                 total_size = os.path.getsize(local_file)#取文件大小
11                 self.send_msg('put',file_size=total_size,filename=local_file)#发送
12                 f = open(local_file,'rb')
13                 uploaded_size = 0
14                 #last_percent = 0
15 
16                 progress_generator = self.progress_bar(total_size)#进度条
17                 progress_generator.__next__()#next一下
18                 for line in f:
19                     self.sock.send(line)
20                     uploaded_size += len(line)
21                     # current_percent = int(uploaded_size / total_size * 100)
22                     # if current_percent > last_percent:
23                     #     print("#"* int(current_percent/2) + "{percent}%".format(percent=current_percent),end='\r',flush=True)
24                     #     last_percent = current_percent #把本次循环的percent赋值给last
25                     progress_generator.send(uploaded_size)
26 
27                 else:
28                     print('\n')
29                     print('file upload done'.center(50,'-'))
30                     f.close()

上传功能的函数。

1 if __name__ == "__main__":
2     client = FtpClient()
3     client.interactive() #交互

执行该py文件需使用cmd,格式为 filename.py -s IP -P port执行。

 

转载于:https://www.cnblogs.com/ArmoredTitan/p/7203408.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Python可以使用内置的ftplib模块来实现FTP客户端的设计和实现。 首先,我们需要导入ftplib模块: ```python import ftplib ``` 接下来,我们需要连接到FTP服务器。我们可以使用ftplib.FTP类的connect方法来建立连接: ```python ftp = ftplib.FTP() ftp.connect("ftp服务器地址", "ftp服务器端口") ``` 连接成功后,我们需要登录到FTP服务器。我们可以使用ftplib.FTP类的login方法来登录: ```python ftp.login("用户名", "密码") ``` 登录成功后,我们可以执行各种FTP操作。例如,我们可以列出FTP服务器上的文件列表: ```python files = ftp.nlst() for file in files: print(file) ``` 我们还可以下载文件: ```python ftp.retrbinary("RETR 文件名", open("本地保存路径", "wb").write) ``` 同时,我们还可以上传文件: ```python ftp.storbinary("STOR 文件名", open("本地文件路径", "rb")) ``` 最后,我们可以关闭与FTP服务器的连接: ```python ftp.quit() ``` 通过以上步骤,我们可以实现一个简单的FTP客户端。当然,我们还可以根据具体需求来进一步增加其他功能,例如创建文件夹、删除文件、重命名文件等操作。 需要注意的是,FTP服务器的地址、端口、用户名和密码等参数都需要根据实际情况进行设置,并且在处理错误和异常时需要进行适当的处理。 ### 回答2: Python可以使用ftplib库来实现FTP客户端的设计与实现。以下是一个基本的示例代码: ```python # 导入ftplib库 from ftplib import FTP # 创建FTP客户端对象 ftp = FTP() # 连接FTP服务器 ftp.connect('ftp.example.com', 21) # 登录FTP服务器 ftp.login('username', 'password') # 切换到指定目录 ftp.cwd('path/to/directory') # 列出当前目录下的文件和文件夹 file_list = ftp.nlst() for file in file_list: print(file) # 下载文件 ftp.retrbinary('RETR filename', open('local_filename', 'wb').write) # 上传文件 file = open('local_filename', 'rb') ftp.storbinary('STOR remote_filename', file) # 删除文件 ftp.delete('filename') # 关闭FTP连接 ftp.quit() ``` 以上代码演示了连接到FTP服务器、登录、切换目录、列出文件、下载、上传和删除文件的基本操作。你可以根据具体需求来进行修改和拓展。使用Python实现FTP客户端可以方便地与FTP服务器进行交互,并进行文件传输和管理操作。 ### 回答3: Python实现FTP客户端可以通过使用Python的内置模块`ftplib`来完成。`ftplib`模块提供了一些方法和函数来方便地连接FTP服务器、上传和下载文件。 首先,我们需要通过`ftplib.FTP`类来建立与FTP服务器的连接。我们可以使用`ftplib.FTP`类的`connect(host, port)`方法来指定FTP服务器的地址和端口。例如,我们可以使用以下代码连接到FTP服务器: ```python import ftplib ftp = ftplib.FTP() ftp.connect('ftp.example.com', 21) ``` 接下来,我们需要使用`login(user, passwd)`方法进行登录。我们需要提供用户名和密码。例如,我们可以使用以下代码登录FTP服务器: ```python ftp.login('username', 'password') ``` 登录成功后,我们可以使用`ftp.cwd(directory)`方法切换到目标目录。例如,我们可以使用以下代码进入目标目录: ```python ftp.cwd('directory') ``` 此时,我们可以使用`ftp.retrbinary(command, callback)`方法来下载文件。`retrbinary`方法需要提供FTP服务器的命令和一个回调函数来接收文件数据。例如,我们可以使用以下代码下载文件到本地: ```python def receive_data(data): # 处理接收到的数据,例如将数据写入本地文件 pass ftp.retrbinary('RETR filename', receive_data) ``` 类似地,我们可以使用`ftp.storbinary(command, file)`方法来上传文件。`storbinary`方法需要提供FTP服务器的命令和一个本地文件对象。例如,我们可以使用以下代码将本地文件上传到FTP服务器: ```python file = open('localfile', 'rb') ftp.storbinary('STOR remotefilename', file) file.close() ``` 最后,我们可以使用`ftp.quit()`方法来结束与FTP服务器的连接: ```python ftp.quit() ``` 综上所述,以上是使用Python实现FTP客户端的主要步骤和方法。通过使用`ftplib`模块的相关函数和方法,我们可以方便地连接FTP服务器、登录、切换目录、上传和下载文件等操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值