開發背景:
有時需要在局域網傳輸文件,總是要用U盤傳輸,要是多個人都需要同樣的文件,U盤就有點麻煩了,Windows上的文件共享配置步驟很少,但是經常因爲各種原因失敗,又要檢查來檢查去的。於是考慮到通過FTP協議來傳輸文件,但是出名的FTP服務器軟件類似Serv-U 這種,功能是很強大,但是配置也很多,我只是臨時傳輸下文件,希望可以免安裝,一鍵啓動FTP,一鍵關閉FTP。於是就想使用python實現FTP服務器,再打包成exe文件。
開發環境:
win 7 64位,Python 3.6.2,pyftpdlib,wxpython,pyinstaller 3.3.1
具體思路:
通過pyftpdlib庫實現FTP 功能ftpserver.py,使用wxpython實現GUI界面wxgui.py,在wxgui.py上生成配置文件config.ini,ftpserver.py再獲取config.ini中的信息啓動FTP,最後使用pyinstaller打包成exe可執行文件。
軟件截圖:
不過剛開發好的軟件,應該有挺多BUG的,以後使用過程中再慢慢改進。
打開軟件:
選擇FTP目錄:
啓動FTP:
關鍵步驟:
這次小項目中使用到了ini格式的配置文件,該配置文件提供給FTP服務啓動需要的配置信息(是否匿名,FTP端口等),還可以記錄FTP啓動時GUI界面狀態,使界面關閉後打開還是FTP啓動時的界面。
ini 文件是文本文件,中間的數據格式一般爲:
[Section1 Name]
KeyName1=value1
KeyName2=value2
...
python可以通過configparse內置模塊寫入讀取配置信息
該界面啓動就執行下面的Python語句:
import configparse
config=configparser.ConfigParser()
config.add_section('ftpd')#添加一個新的section
config.set('ftpd','anonymous',str(not check.GetValue()))#判斷複選框是否選中
config.set('ftpd','user',usertext.GetValue())#獲取用戶名
config.set('ftpd','password',passtext.GetValue())#獲取密碼
config.set('ftpd','port',porttext.GetValue())#獲取端口
config.set('ftpd','dir',dirtext.GetValue())#獲取目錄
#寫入到config.ini配置
with open('config.ini','w') as conf:
config.write(conf)
寫入的配置文件config.ini如下:
[ftpd]
anonymous = False
user = aaa
password = aaa
port = 21
dir = E:\
接着就是ftpserver.py如何獲取config.ini配置文件的信息了
config = configparser.ConfigParser()
config.read('config.ini')
config.get('ftpd',user)#獲取用戶名
config.get('ftpd',password)#獲取密碼
...
wxgui.py也可以通過config.ini配置文件來恢復程序界面關閉前的模樣
def getconfig(v):#pyserver.py中的函數
#獲取config.ini配置文件的信息
config = configparser.ConfigParser()
config.read('config.ini')
return config.get('ftpd',v)
if os.path.isfile('config.ini'):#config.ini文件存在的話,就獲取配置文件信息。
if ftpserver.getconfig('anonymous')=='False':
check.SetValue(1)#設置複選框爲選中狀態
usertext.SetEditable(1)#允許編輯
passtext.SetEditable(1)
usertext.SetValue(ftpserver.getconfig('user'))#設置用戶名輸入框的值
passtext.SetValue(ftpserver.getconfig('password'))
porttext.SetValue(ftpserver.getconfig('port'))
dirtext.SetValue(ftpserver.getconfig('dir'))
具體源代碼:
實現FTP功能代碼ftpserver.py:
# coding:utf-8
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
import logging
import configparser
import os
def ftpd():
authorizer = DummyAuthorizer()
if getconfig('anonymous')=='True':
#添加匿名用戶
authorizer.add_anonymous(getconfig('dir'),perm='elradfmwM')
else:
#添加用戶,需要賬號和密碼登錄
authorizer.add_user(getconfig('user'), getconfig('password'), getconfig('dir'), perm='elradfmwM')
handler = FTPHandler#初始化處理客戶端命令的類
handler.authorizer = authorizer#選擇登錄方式(是否匿名)
logging.basicConfig(filename='ftpd.log', level=logging.INFO) #日誌信息寫入ftpd.log
address = ('0.0.0.0', getconfig('port'))#設置服務器的監聽地址和端口
server = FTPServer(address, handler)
server.max_cons = 256 #給鏈接設置限制
server.max_cons_per_ip = 5
server.serve_forever() # 啓動FTP
def getconfig(v):
#獲取config.ini配置文件的信息
config = configparser.ConfigParser()
config.read('config.ini')
return config.get('ftpd',v)
if __name__ == '__main__':
ftpd()
實現GUI界面代碼wxgui.py:
# -*- coding:utf-8 -*-
import wx
import os
import sys
import configparser
import ftpserver
import time
import threading
import socket
def onclick(event): #選擇FTP目錄
dlg=wx.DirDialog(window,'選擇共享目錄')
if dlg.ShowModal() == wx.ID_OK:
dirtext.SetValue(dlg.GetPath())
def onclose(event): #退出事件
if startbutton.GetLabel()=='關閉FTP':
dlg1 = wx.MessageDialog(None, "FTP正在運行,確認退出嗎?", "退出", wx.YES_NO | wx.ICON_EXCLAMATION)
if dlg1.ShowModal()==wx.ID_YES:
sys.exit()
else:
sys.exit()
def startftp(event): #點擊 啓動FTP 按鈕事件
global t
if startbutton.GetLabel()=='啓動FTP':
startbutton.SetLabel('關閉FTP')
#把FTP啓動信息寫入config.ini配置文件中
config=configparser.ConfigParser()
config.add_section('ftpd')
config.set('ftpd','anonymous',str(not check.GetValue()))
config.set('ftpd','user',usertext.GetValue())
config.set('ftpd','password',passtext.GetValue())
config.set('ftpd','port',porttext.GetValue())
config.set('ftpd','dir',dirtext.GetValue())
with open('config.ini','w') as conf:
config.write(conf)
time.sleep(1)
#創建線程啓動FTP
t=threading.Thread(target=ftpserver.ftpd)
t.setDaemon(True)
t.start()
iplist=socket.gethostbyname_ex(socket.gethostname())[2]
ftpurl=''
if iplist :
for ip in iplist:
ftpurl+='FTP地址:ftp://'+ip+':'+porttext.GetValue()+'\n'
urllabel.SetLabel(ftpurl)
else:
dlg1 = wx.MessageDialog(None, "FTP正在運行,確認退出嗎?", "退出", wx.YES_NO | wx.ICON_EXCLAMATION)
if dlg1.ShowModal()==wx.ID_YES:
sys.exit()
def onchecked(event): #複選框事件
usertext.SetEditable(event.IsChecked())
passtext.SetEditable(event.IsChecked())
app = wx.App()#實例化wx.App
#創建頂級窗口,並且窗口不可改變尺寸
window = wx.Frame(None,title = "FTP服務器", size = (390, 260), pos=(400,300),style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX)
#綁定退出事件
window.Bind(wx.EVT_CLOSE,onclose)
#創建panel
panel = wx.Panel(window)
font = wx.Font(11, wx.SWISS, wx.NORMAL, wx.NORMAL)
#創建複選框,並綁定事件
check=wx.CheckBox(panel,-1,'使用賬戶密碼登錄',pos=(20,20),size=(200,-1))
check.SetFont(font)
window.Bind(wx.EVT_CHECKBOX,onchecked)
#創建靜態文本和文本輸入框
userlabel = wx.StaticText(panel, label = "用戶名:", pos=(35,45))
userlabel.SetFont(font)
usertext = wx.TextCtrl(panel,-1,'',size=(95,20),pos=(90,42),style = wx.TE_READONLY)
passlabel = wx.StaticText(panel, label = "密碼:",pos=(195,45))
passlabel.SetFont(font)
passtext = wx.TextCtrl(panel,-1,'',size=(95,20),pos=(235,42),style = wx.TE_PASSWORD)
passtext.SetEditable(False)
dirlabel = wx.StaticText(panel, label = "FTP目錄:",pos=(23,72))
dirlabel.SetFont(font)
dirtext = wx.TextCtrl(panel,-1,os.getcwd(),size=(215,20),pos=(88,70),style = wx.TE_READONLY)
#創建按鈕,並且綁定按鈕事件
button=wx.Button(panel,-1,'更改',pos=(310,70),size=(40,20))
button.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL))
window.Bind(wx.EVT_BUTTON,onclick,button)
portlabel = wx.StaticText(panel, label = "FTP端口:",pos=(23,104))
portlabel.SetFont(font)
porttext = wx.TextCtrl(panel,-1,'21',size=(51,20),pos=(88,102))
startbutton=wx.Button(panel,-1,'啓動FTP',pos=(160,130),size=(70,30))
startbutton.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL))
window.Bind(wx.EVT_BUTTON,startftp,startbutton)
#加載配置文件,使程序恢復到退出前的界面
if os.path.isfile('config.ini'):
if ftpserver.getconfig('anonymous')=='False':
check.SetValue(1)
usertext.SetEditable(1)
passtext.SetEditable(1)
usertext.SetValue(ftpserver.getconfig('user'))
passtext.SetValue(ftpserver.getconfig('password'))
porttext.SetValue(ftpserver.getconfig('port'))
dirtext.SetValue(ftpserver.getconfig('dir'))
urllabel = wx.StaticText(panel, label = "", pos=(80, 170))
urllabel.SetFont(font)
window.Show(True)#窗口可見
app.MainLoop() #主循環,處理事件
使用pyinstaller打包
如果順利的話,兩條命令就可以成功打包了,反正我是沒那麼順利的了
pip安裝pyinstaller
pip install pyinstaller
開始打包:
pyinstaller -F wxgui.py -w
-F 參數:是生成單個可執行文件
-w參數:因爲我的是GUI程序,所以我使用這參數去掉命令行窗口
生成的文件:
問題總結:
這次開發這個小項目中遇到了個問題,百度谷歌都找不到解決的辦法。
程序創建個線程去啓動FTP服務,當我想要停止FTP服務時,在不退出程序的情況下該如何去停止FTP服務?
問題已解決,把多線程換成多進程就可以了,多進程自帶結束進程的方法