day-1 用python編寫一個簡易的FTP服務器

  從某寶上購買了一份《Python神經網絡深度學習》課程,按照視頻教程,用python語言,寫了一個簡易的FTP服務端和客戶端程序,之前也用C++寫過聊天程序,編程思路差很少,可是python編程時更順暢,代碼量更少。沒有很高深的理論知識,也不須要紮實的編程基礎,知道須要用哪些庫就好了。html

  兩種語言對比,初次感覺到python語言的易用之處,python的核心是簡潔清晰,也是僞代碼的最佳實踐語言。能夠理解爲C語言的一些經常使用功能庫的轉義,因此廣泛認爲python語言帶來編程便捷的同時,也會帶來一部分性能的犧牲。但這並不也是絕對的,有時候程序的開發效率更爲重要,更況且並不必定人人都是大牛,用C或者其它語言未必就能寫出比python更高性能的代碼。總體學習完之後,還會再來深刻對比下python和其餘語言的差別。python

1、基本功能

  登錄驗證編程

  上傳:支持斷點上傳,MD5驗證json

  簡單命令:cd、ls、mkdir服務器

二、經常使用模塊

  下面是我彙總的用到的模塊信息,要寫出完整的代碼,首先得了解基本的python函數和庫,例如:網絡

  os,sys庫對目錄和文件的處理;app

  socket和socketserver對網絡通訊的處理;socket

  json對傳輸數據格式的處理;ide

  configparse對配置文件的處理;函數

  hashlib對MD5校驗的處理;

  optparse對命令行輸入參數的格式化處理等;

  另外還須要瞭解如何利用反射機制,處理多If else分支的函數調用等。

  關於python反射機制,參考連接:http://www.javashuo.com/article/p-vddihgho-by.html

 

三、具體代碼

  

  ftp_client.py:FTP客戶端主程序

import socket
import optparse
import json
import os,sys
import hashlib

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",
    800 : "the file exist,but not enough ,is continue? ",
    801 : "the file exist !",
    802 : " ready to receive datas",
    900 : "md5 valdate success"
}

sk = socket.socket()
sk.connect(("127.0.0.1",18000))

class ClientHander():
    def __init__(self):

        #初始化變量
        self.op = optparse.OptionParser()
        self.op.add_option("-s","--server",dest="server")
        self.op.add_option("-P", "--port", dest="port")
        self.op.add_option("-u", "--username", dest="username")
        self.op.add_option("-p", "--password", dest="password")
        self.options,self.args = self.op.parse_args()
        #self.verify_args(self.options,self.args)
        self.mainPath = os.path.dirname(os.path.abspath(__file__))
        self.last = 0
        self.is_authenticated = False
        self.is_connected = False
        self.is_needed_md5 = True

        #開始創建鏈接
        self.make_connection()

    # 驗證用戶輸入信息
    def verify_args(self,options,args):
        server = options.server
        port = options.port
        username = options.username
        password = options.password
        if int(port) > 0 and int(port) < 65535:
            return True
        else:
            exit("the port should been in 0-65535")

    # 鏈接服務器
    def make_connection(self):
        if not self.is_connected:
            self.sock = socket.socket()
            self.sock.connect(("127.0.0.1", 18000))
            self.is_connected = True
        return

    def interactive(self):

        # 判斷是否已經驗證過
        if not self.is_authenticated:
            self.make_connection()
            self.authenticate()

        # 開始和服務器進行交互
        cmd_info = input("[%s]"%self.current_dir).strip()
        # 利用反射機制,創建命令與功能對應關係
        cmd_list = cmd_info.split()
        if hasattr(self,cmd_list[0]):
            func = getattr(self,cmd_list[0])
            func(*cmd_list)

    def authenticate(self):
        if not self.is_connected:
            print("沒有鏈接FTP服務器")
            return
        if self.options.username is None or self.options.password is None:
            username = input("username:")
            password = input("password:")
            return self.get_auth_result(username,password)
        return self.get_auth_result(self.options.username,self.options.password)

    def response(self):
        data = self.sock.recv(1024).decode("utf-8")
        data = json.loads(data)
        return data

    def get_auth_result(self,username,password):
        data = {
            "action":"auth",
            "username":username,
            "password":password
        }
        self.sock.send(json.dumps(data).encode("utf-8"))
        res = self.response()
        print("res",res["status_code"])
        if res["status_code"] ==  254:
            self.username = username
            self.current_dir = username
            self.is_authenticated = True
            print(STATUS_CODE[254])
            return True
        else:
            print(STATUS_CODE[res["status_code"]])

    def put(self,*cmd_list):

        # 1 發送上傳命令
        action,local_path,target_path = cmd_list
        local_path = os.path.join(self.mainPath,local_path)
        file_name = os.path.basename(local_path)
        file_size = os.stat(local_path).st_size
        data = {
            "action":"put",
            "file_name": file_name,
            "file_size": file_size,
            "target_path": target_path
        }
        self.sock.sendall(json.dumps(data).encode("utf-8"))

        # 2 接收服務器檢查結果
        has_sent = 0
        is_exsit = self.sock.recv(1024).decode("utf-8")

        # 3 根據服務器檢查結果,執行相應命令
        # 文件存在但不完整
        if is_exsit == "800":
            print("服務器文件已經存在,且不完整")
            # 和服務器確認是否繼續上傳
            choice = input("The file exist but not enough,is continue?[Y/N]").strip()
            # 繼續上傳
            if choice.upper() == "Y":
                self.sock.sendall("Y".encode("utf-8"))
                # 等待服務器返回存在文件大小
                continue_position = self.sock.recv(1024).decode("utf-8")
                has_sent += int(continue_position)
            # 不繼續上傳
            else:
                self.sock.sendall("N".encode("utf-8"))
        # 文件存在且完整
        elif is_exsit == "801":
            print("服務器文件已經存在,且完整")
            return
        f = open(local_path,"rb")
        f.seek(has_sent)

        md5_obj = None
        if self.is_needed_md5:
            md5_obj = hashlib.md5()

        while has_sent < file_size:
            data = f.read(1024)
            self.sock.sendall(data)
            if md5_obj:
                md5_obj.update(data)
            has_sent += len(data)

        mdt_val_server = self.sock.recv(1024).decode("utf-8")
        mdt_val_client = md5_obj.hexdigest()
        self.show_progress(has_sent,file_size)
        print("client's md5 is %s"%mdt_val_client)
        print("server's md5 is %s" %mdt_val_server)
        f.close()

        if mdt_val_client == mdt_val_server:
            print("put success")
        else:
            print("Athenticate MD5 failed.")

    def show_progress(self,has,total):
        rate = float(has) / float(total)
        rate_num = int(rate*100)
        print("%s%% %s\r"%(rate_num,"#"*rate_num))

    def ls(self,*cmd_list):
        data = {
            "action":"ls"
        }
        self.sock.sendall(json.dumps(data).encode("utf-8"))
        data = self.sock.recv(1024).decode("utf-8")
        print(data)

    def cd(self,*cmd_list):
        # cd images
        data = {
            "action":"cd",
            "dirname":cmd_list[1]
        }
        self.sock.sendall(json.dumps(data).encode("utf-8"))
        data = self.sock.recv(1024).decode("utf-8")
        self.current_dir = os.path.basename(data)
        print(os.path.basename(data))

    def mkdir(self,*cmd_list):
        data = {
            "action":"mkdir",
            "dirname":cmd_list[1]
        }

        self.sock.sendall(json.dumps(data).encode("utf-8"))
        data = self.sock.recv(1024).decode("utf-8")
        print(data)

ch = ClientHander()
while 1:
    ch.interactive()
View Code

  ftp_server.py:FTP服務器端主程序

import os,sys

PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(PATH)

from core import main

if __name__ == '__main__':
    main.ArgvHandler()
View Code

  main.py:處理參數輸入

import optparse
import socketserver
from conf import settings
from core import server

class ArgvHandler():

    def __init__(self):
        self.op = optparse.OptionParser()
        #self.op.add_option("-s","--server",dest = "server")
        #self.op.addd_option("-P", "--port", dest="port")

        options,args = self.op.parse_args()
        self.verify_args(options,args)


    def verify_args(self,options,args):
        #首先判斷參數的長度
        if len(args) < 1:
            print("參數個數太少,請從新輸入.")
        else:
            cmd = args[0]
            if hasattr(self,cmd):
                func = getattr(self,cmd)
                func()
        self.start()

    def start(self):
        s = socketserver.ThreadingTCPServer((settings.IP,settings.PORT),server.ServerHandler)
        print("服務器已經啓動")
        s.serve_forever()

    def help(self):
        pass
View Code

  server.py:具體代碼實現

import socketserver
import json
import configparser
from conf import settings
import os
import hashlib

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",

    800 : "the file exist,but not enough ,is continue? ",
    801 : "the file exist !",
    802 : " ready to receive datas",

    900 : "md5 valdate success"

}


class ServerHandler(socketserver.BaseRequestHandler):
    def handle(self):
        while 1:
            data = self.request.recv(1024).strip()
            if not data:continue
            data = json.loads(data.decode("utf-8"))

            if data.get("action"):
                if hasattr(self,data.get("action")):
                    func = getattr(self,data.get("action"))
                    func(**data)
                else:
                    print("Invalid cmd")
            else:
                print("Invalid cmd")

    def send_response(self,status_code):
        response = {"status_code":status_code}
        self.request.sendall(json.dumps(response).encode("utf8"))

    def auth(self,**data):
        username = data["username"]
        password = data["password"]
        print("收到客戶端驗證請求,用戶名:",username,"密碼:",password)
        user = self.authenticate(username,password)
        if user:
            self.send_response(254)
        else:
            self.send_response(253)

    def authenticate(self,username,password):
        cfg = configparser.ConfigParser()
        cfg.read(settings.ACCOUNT_PATH)
        print("正在讀取配置用戶配置信息:",settings.ACCOUNT_PATH)

        if username in cfg.sections():
            if cfg[username]["Password"] == password:
                self.useranme = username
                self.mainPath = os.path.join(settings.BASE_DIR,"home",self.useranme)
                print("成功匹配上用戶信息,用戶密碼:", cfg[username]["Password"])
                print("用戶根目錄爲:",self.mainPath)
                return username

    def put(self,**data):
        print("data",data)
        file_name = data.get("file_name")
        file_size = data.get("file_size")
        target_path = data.get("target_path")

        abs_path = os.path.join(self.mainPath,target_path,file_name)

        has_received = 0

        if os.path.exists(abs_path):
            file_has_size = os.stat(abs_path).st_size
            if file_has_size < file_size:
                #斷點續傳
                self.request.sendall("800".encode("utf-8"))
                choice = self.request.recv(1024).decode("utf-8")
                if choice.upper() == "Y":
                    self.request.sendall(str(file_has_size).encode("utf-8"))
                    f = open(abs_path,"ab")
                    has_received += file_has_size
                else:
                    f = open(abs_path,"wb")
            else:
                #文件徹底存在
                self.request.sendall("801".encode("utf-8"))
                return
        else:
            self.request.sendall("802".encode("utf-8"))
            f = open(abs_path, "wb")

        md5_obj = hashlib.md5()

        while has_received < file_size:
            try:
                data = self.request.recv(1024)
                f.write(data)
                md5_obj.update(data)
                has_received += len(data)
            except Exception as e:
                print("傳輸文件發生異常:",str(e))
                break

        # 給客戶端發送MD5
        md5_val = md5_obj.hexdigest()
        self.request.sendall(md5_val.encode("utf-8"))

        f.close()

    def ls(self,**data):
        file_list = os.listdir(self.mainPath)
        print("ls %s"%self.mainPath)
        if not file_list:
            file_str = "<empty dir>"
        else:
            file_str = "\n".join(file_list)
        self.request.sendall(file_str.encode("utf-8"))

    def cd(self,**data):
        dir_name = data.get("dirname")
        print("cd %s\n"%dir_name)
        if dir_name == "..":
            self.mainPath = os.path.dirname(self.mainPath)
        else:
            self.mainPath = os.path.join(self.mainPath,dir_name)

        self.request.sendall(self.mainPath.encode("utf-8"))

    def mkdir(self,**data):
        dirname = data.get("dirname")
        path = os.path.join(self.mainPath,dirname)
        if not os.path.exists(path):
            if "/" in dirname:
                os.makedirs(path)
            else:
                os.mkdir(path)
            self.request.sendall("create success".encode("utf-8"))
        else:
            self.request.sendall("dirname exist".encode("utf-8"))
View Code

  accounts.cfg:帳戶信息,登錄驗證用

[DEFAULT]
[tanbiao]
Password = tanbiao
Quotation = 100

[root]
Password = root
Quotation = 100
View Code

  setting.py:配置信息

import os

BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

IP="127.0.0.1"
PORT=18000


ACCOUNT_PATH=os.path.join(BASE_DIR,"conf","accounts.cfg")
View Code
相關文章
相關標籤/搜索