目录
FTP编程实现文件上传下载(基于Python3.7和PyQt5)
FTP(File Transfer Protocol,文件传输协议) 是 TCP/IP 协议组中的协议之一。FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于FTP服务器上的资源。在开发网站的时候,通常利用FTP协议把网页或程序传到Web服务器上。此外,由于FTP传输效率非常高,在网络上传输大的文件时,一般也采用该协议。
默认情况下FTP协议使用TCP端口中的 20和21这两个端口,其中20用于传输数据,21用于传输控制信息。但是,是否使用20作为传输数据的端口与FTP使用的传输模式有关,如果采用主动模式,那么数据传输端口就是20;如果采用被动模式,则具体最终使用哪个端口要服务器端和客户端协商决定。
——来自《百度百科》
老规矩,先上图:
一、实验目的
- 了解FTP协议的用途,掌握FTP编程的基本方法,理解其工作流程
- 巩固PyQt5的使用,扩展学习了新的组件ListView和打开文件选择框
- 巩固多线程的用法
二、实验内容
编制 FTP 客户端程序,实现如下功能:登陆 FTP 服务器,显 示登录客户目录下的文件和目录名,能从该目录中(包括各级子目录)选择下载 服务器端的文件,也能向服务器上传文件。应用程序基于对话框,程序界面如下图:
三、实验步骤
实验步骤我主要讲代码,前面的界面设计和代码生成我的前一篇socket网络程序设计实验三文章中讲的比较详细了,不清楚的可以看一下或者百度吧。
(一)服务器端
首先我们得自己简单搞个服务器端出来,服务器端需要用到库:pyftpdlib
没有的需要先pip install一波:
pip install pyftpdlib
Ok,然后新建个ftp_server.py文件,敲:
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
# 获得实例
handler = FTPHandler
# 将用户名,密码,指定目录,权限 添加到里面
handler.authorizer.add_user("mike", "123456", "D:/Work/", perm="elradfmw")
# 开启服务器
server = FTPServer(("127.0.0.1", 21), handler)
server.serve_forever()
Ok,这样服务器就开始运行了,打开浏览器,输入:
ftp://127.0.0.1:21
看到下面这样的界面就说明服务器OK了
(二)客户端
【1】 界面设计
打开Qt Designer,新建一个Dialog Without Button,然后我们就自己开始设计,注意最大的文本框不是TextBrowser,而是ListView,因为我们需要将文件列表显示在其中,ListView刚好适合我们的需求.设计完后改名保存就好了。
【2】 生成布局代码
在pycharm中找到刚才保存的.ui文件,右键External Tools -> Py UIC,就自动生成了.py文件
在代码的开头加上:
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.Qt import *
from ftplib import FTP
import logging
import threading
在代码的最后加上:
if __name__ == '__main__':
app = QApplication(sys.argv)
MainWindow = QMainWindow()
ui = Ui_FtpFile()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
至此,运行程序可以显示界面了
【3】 功能实现
首先在dialog类中初始化一些我们需要的变量:
def __init__(self):
self.slm = QStringListModel() # listView的模型
self.ftp = FTP() # 实例化FTP
self.select_file = "" # listView中选择的文件名
self.file_list = [] # 存放查询FTP返回的当前目录所有文件列表
1.连接并登录FTP,返回文件列表(查询按钮)
要注意用户名和密码要和我们的服务器端一致
def connect_button(self):
host = self.lineEdit.text() # 获取IP地址框内容
port = int(self.lineEdit_4.text()) # 获取端口号,注意要转换为int
usr = self.lineEdit_2.text() # 获取用户名
pwd = self.lineEdit_3.text() # 获取密码
try:
self.ftp.connect(host,port,timeout=10) # 连接FTP
except:
logging.warning('network connect time out') # 打印日志信息
try:
self.ftp.login(usr,pwd) # 登录FTP
except:
logging.warning("username or password error") # 打印日志信息
self.file_list = self.ftp.nlst() # 查询当前目录的所有文件列表
self.slm.setStringList(self.file_list) # 将文件列表存入listView模型
再在retranslateUi下加上:
self.pushButton.clicked.connect(self.connect_button)
2.单击listView获取选中的item
def select_item(self, qModelIndex):
self.select_file = self.file_list[qModelIndex.row()]
# print(self.select_file)
if "." in self.select_file: # 如果是文件,则可下载
self.pushButton_3.setEnabled(True)
else: # 否则是文件夹,不能下载
self.pushButton_3.setEnabled(False)
再在retranslateUi下加上:
self.listView.setModel(self.slm)
self.listView.clicked.connect(self.select_item)
3.双击listView进入选中的item文件夹
def cd_button(self):
if '.' in self.select_file: # 是文件则不能进入
pass
else: # 是文件夹则可以进入
self.ftp.cwd(self.select_file)
self.file_list = self.ftp.nlst() # 刷新当前目录的所有文件列表
self.slm.setStringList(self.file_list)
再在retranslateUi下加上:
self.listView.doubleClicked.connect(self.cd_button)
4.返回上一级目录(上一层按钮)
def back_button(self):
self.ftp.cwd("..")
self.file_list = self.ftp.nlst() # 刷新当前目录的所有文件列表
self.slm.setStringList(self.file_list)
再在retranslateUi下加上:
self.pushButton_1.clicked.connect(self.back_button)
5.上传文件(上传按钮)
def upload_button(self):
t = threading.Thread(target=self.t,args=())
t.start()
def t(self):
file_path, filer_ = QFileDialog.getOpenFileName()
print(file_path)
file_name = os.path.split(file_path)[1]
print(file_name)
self.ftp.storbinary('stor '+file_name, open(file_path, 'rb'))
再在retranslateUi下加上:
self.pushButton_2.clicked.connect(self.upload_button)
6.下载文件(下载按钮)
def download_button(self):
self.ftp.retrbinary('retr '+self.select_file, open(self.select_file, 'wb').write)
print("download succeed")
再在retranslateUi下加上:
self.pushButton_3.clicked.connect(self.download_button)
7.断开连接(退出按钮)
def quit_button(self):
self.ftp.quit()
self.slm.setStringList([])
再在retranslateUi下加上:
self.pushButton_4.clicked.connect(self.quit_button)
至此,我们预想的功能都已实现了,也在过程中接入了一些异常捕获来减少bug,但是还不够全面,下面来看看有什么需要改进的地方。
【4】 改进程序
现在程序一打开是这样的:
我们会发现如果这个时候点“上一层”或者“上传”“退出”,肯定是不行的,因为都还没有连接FTP服务器,所以我们应该先将这三个按钮disable一下
在retranslateUi下加入:
self.pushButton_1.setEnabled(False)
self.pushButton_2.setEnabled(False)
self.pushButton_4.setEnabled(False)
在点击查询按钮连接后再将它们设置为True
在connect_button方法下加上:
self.pushButton_1.setEnabled(True)
self.pushButton_2.setEnabled(True)
self.pushButton_4.setEnabled(True)
还有退出连接后,也要改回来
在quit_button方法下加上:
self.pushButton_1.setEnabled(False)
self.pushButton_2.setEnabled(False)
self.pushButton_4.setEnabled(False)
能想到的别的Bug但是不影响程序使用,例如:
- 连接后,再点查询(重复连接)是不会出错的,如果不在顶层目录就相当于返回顶层目录
- 点击上传后没有选择文件夹关闭了,因为我是启用新线程执行这一操作,所以不影响主程序
- 已经在顶层目录还点“上一层”按钮,也不会出错
服务器端也可以一对多服务的,可以增加多个用户组:
handler.authorizer.add_user("mike", "123456", "D:/Work/", perm="elradfmw")
handler.authorizer.add_user("LiePy", "654321", "D:/Work/", perm="elradfmw")
handler.authorizer.add_user("python", "abcdefg", "D:/Work/", perm="elradfmw")
也可以直接不设用户名密码限制只需要在服务器端中加入:
handler.authorizer.add_anonymous("D:/Work/")
当然,你可以在远程主机上传输文件,只要改一下ip地址即可,不只限于localhost噢
好吧,这篇文章就到这里
源代码已上传至我的github:https://github.com/LiePy/socket_test.git
pass~