python實現ftp服務器(使用wxpython實現GUI界面)

開發背景:

有時需要在局域網傳輸文件,總是要用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的,以後使用過程中再慢慢改進。
打開軟件:
python實現ftp服務器(使用wxpython實現GUI界面)
選擇FTP目錄:
python實現ftp服務器(使用wxpython實現GUI界面)
啓動FTP:
python實現ftp服務器(使用wxpython實現GUI界面)

關鍵步驟:

這次小項目中使用到了ini格式的配置文件,該配置文件提供給FTP服務啓動需要的配置信息(是否匿名,FTP端口等),還可以記錄FTP啓動時GUI界面狀態,使界面關閉後打開還是FTP啓動時的界面。
ini 文件是文本文件,中間的數據格式一般爲:

[Section1 Name] 
KeyName1=value1 
KeyName2=value2 
...

python可以通過configparse內置模塊寫入讀取配置信息
python實現ftp服務器(使用wxpython實現GUI界面)
該界面啓動就執行下面的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程序,所以我使用這參數去掉命令行窗口
生成的文件:
python實現ftp服務器(使用wxpython實現GUI界面)

問題總結:

這次開發這個小項目中遇到了個問題,百度谷歌都找不到解決的辦法。
程序創建個線程去啓動FTP服務,當我想要停止FTP服務時,在不退出程序的情況下該如何去停止FTP服務?
問題已解決,把多線程換成多進程就可以了,多進程自帶結束進程的方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章