1.FTP Server 开发

  • 用户加密认证
  • 允许同时多用户登录
  • 每个用户有自己的家目录 ,且只能访问自己的家目录
  • 对用户进行磁盘配额,每个用户的可用空间不同
  • 允许用户在ftp server上随意切换目录
  • 允许用户查看当前目录下文件
  • 允许上传和下载文件,保证文件一致性
  • 文件传输过程中显示进度条
  • 附加功能:支持文件的断点续传

python判断ftp路径 python ftps_python判断ftp路径

 

2.FTP Server 智能读取参数

学习模块optparse

其实就是在客户端在登陆过程中需要输入IP地址和端口是对应的参数读取



import optparse


class FTPClient(object):
    def __init__(self):
        parser = optparse.OptionParser() #为类添加参数
        parser.add_option("-s","--server",dest="server",help="ftp server ip_address")
        parser.add_option("-P","--port",type="int",dest="port",help="ftp server port")
        parser.add_option("-u","--username",dest="username",help="username")
        parser.add_option("-p","--password",dest="password",help="password")
        self.options,self.args = parser.parse_args()  #实例化


#dest:用于保存临时变量,其值可以作为options的属性进行访问。存储的内容就是如-f,-n 等紧挨着的那个参数内容
#type:指的是对应于参数,如-f,-n等的接下来的那个参数的数据类型,有string,int,float等等
#help:提供用户友好的帮助信息,一般可以用来解释本add_option方法的功能阐述。



3.用户远程验证



def authenticate(self):
        """用户验证"""
        if self.options.username:
            print(self.options.username,self.options.password)
            return self.get_auth_result(self.options.username, self.options.password)
        else:
            retry_count =0
            while retry_count<3:
                username=input("username").strip()
                password=input("password").strip()
                self.get_auth_result(username,password)

    def get_auth_result(self,user,password):#与服务端的远程交互
        data ={
            "action":"auth",
            "username":user,
            "password":password
        }
        self.sock.send(json.dumps(data).encode())
        self.get_response()



  

4.设计规范的状态码



STATUS_CODE = {
    100:"Invalid cmd format",
    200:"Invalid cmd"
}


 def send_response(self,status_code,data=None):
        """向客户端返回数据"""
        response = {"status_code":status_code,"status_msg":STATUS_CODE[status_code]}
        if data:
            response.update(data)
        self.request.send(json.dumps(response).encode())



 5.用户账户信息储存和解析

使用模块configparser

创建文件accounts.cfg文件储存用户登陆的账户信息




python判断ftp路径 python ftps_json_02

python判断ftp路径 python ftps_json_03

1  def _auth(self,*args,**kwargs):
 2         data = args[0]
 3         if data.get("username") is None or data.get("password") is None:
 4             self.send_response(300)
 5 
 6         user = self.authenticate(data.get("username"),data.get("password"))
 7         if user is None:
 8             self.send_response(301)
 9         else:
10             print("passed authentication",user)
11 
12     def authenticate(self,username,password):
13         """验证用户的合法性,合法就返回用户数据"""
14         config = configparser.ConfigParser()
15         config.read(settings.ACCOUNT_FILE)
16         if username in config.sections():
17             _password = config[username]["Password"]
18             if _password == password:
19                 print("pass auth..",username)
20                 return config[username]


View Code


 6.文件的下载

client端



def _get(self,cmd_list):
        print("_get",cmd_list)
        if len(cmd_list) == 1:
            print("no filename folows...")
            return
        data_header = {
            "action":"get",
            "filename":cmd_list[1]
        }
        self.sock.send(json.dumps(data_header).encode())
        response = self.get_response()
        print(response)
        if response["status_code"] == 257:
            base_filename = cmd_list[1].split("/")[-1]
            recevied_size = 0
            file_obj = open(base_filename,"wb")
            while recevied_size<response["file_size"]:
                data = self.sock.recv(1024)
                recevied_size +=len(data)
                file_obj.write(data)
            else:
                print("----file recv done-----")
                file_obj.close()



 server端



def _get(self,*args,**kwargs):
        data = args[0]
        if data.get("filename") is  None:
            self.send_response(255)
        user_home_dir = "%s/%s" %(setting.USER_HOME,self.user["Username"])
        file_abs_path = "%s/%s" %(user_home_dir,data.get("filename"))
        print(user_home_dir)
        print(file_abs_path)
        if os.path.isfile(file_abs_path):
            file_obj = open(file_abs_path,"rb")
            file_size = os.path.getsize(file_abs_path)
            self.send_response(257,data = {"file_size":file_size})
            for line in file_obj:
                self.request.send(line)
            else:
                file_obj.close()
                print("---file send done---")

        else:
            self.send_response(256)



 文件上传

client端



def _put(self,cmd_list):
        print("_put",cmd_list)
        if len(cmd_list) > 1:
            file_name = cmd_list[1]
            if os.path.isfile(file_name):
                file_size = os.path.getsize(cmd_list[1])
                print(file_size)
                data_header = {
                    "action":"put",
                    "filename":file_name,
                    "filesize":file_size
                }
                self.sock.send(json.dumps(data_header).encode())
                self.get_response()
                f = open(file_name,"rb")
                for line in f:
                    self.sock.send(line)
                else:
                    print("---%s is send done---"%file_name)
                    f.close()
            else:
                print("%s is not exits"%file_name)



server端



def _put(self,*args,**kwargs):
        data = args[0]
        file_name = data["filename"]
        file_size = data["filesize"]
        user_home_dir = "%s/%s" % (setting.USER_HOME, self.user["Username"])
        file_abs_path = "%s/%s" % (user_home_dir, file_name)
        print(user_home_dir)
        print(file_abs_path)
        if os.path.isfile(file_abs_path):
            f= open(file_abs_path,"wb")
        else:
            f= open(file_abs_path,"wb")
        self.send_response(258)
        recevied_size = 0
        while recevied_size<file_size:
            data = self.request.recv(1024)
            f.write(data)
            recevied_size+=len(data)
        else:
            print("---%s is recv done---"%file_name)



 7.文件的一致性(MD5验证)

client端



def __md5_required(self,cmd_list): #检测命令是否需要md5验证
        if "--md5" in cmd_list:
            return True


    def _get(self,cmd_list):#文件下载
        print("_get",cmd_list)
        if len(cmd_list) == 1:
            print("no filename folows...")
            return
        data_header = {
            "action":"get",
            "filename":cmd_list[1]
        }
        if self.__md5_required(cmd_list):
            data_header["md5"] = True

        self.sock.send(json.dumps(data_header).encode())
        response = self.get_response()
        print(response)
        if response["status_code"] == 257:
            base_filename = cmd_list[1].split("/")[-1]
            recevied_size = 0
            file_obj = open(base_filename,"wb")

            if self.__md5_required(cmd_list):
                md5_obj = hashlib.md5()
                while recevied_size<response["file_size"]:
                    data = self.sock.recv(1024)
                    recevied_size +=len(data)
                    file_obj.write(data)
                    md5_obj.update(data)
                else:
                    print("----file recv done-----")
                    file_obj.close()
                    md5_val = md5_obj.hexdigest()
                    md5_server = self.get_response()
                    if md5_server["status_code"] == 259:
                        if md5_server["md5"] == md5_val:
                            print("%s 文件一致性验证成功"%base_filename)
                    print(md5_val,md5_server)
            else:
                while recevied_size<response["file_size"]:
                    data = self.sock.recv(1024)
                    recevied_size +=len(data)
                    file_obj.write(data)

                else:
                    print("----file recv done-----")
                    file_obj.close()



 server端



def _get(self,*args,**kwargs): #文件下载
        data = args[0]
        if data.get("filename") is  None:
            self.send_response(255)
        user_home_dir = "%s/%s" %(setting.USER_HOME,self.user["Username"])
        file_abs_path = "%s/%s" %(user_home_dir,data.get("filename"))
        print(user_home_dir)
        print(file_abs_path)
        if os.path.isfile(file_abs_path):
            file_obj = open(file_abs_path,"rb")
            file_size = os.path.getsize(file_abs_path)
            self.send_response(257,data = {"file_size":file_size})

            if data.get("md5"):
                md5_obj = hashlib.md5()
                for line in file_obj:
                    self.request.send(line)
                    md5_obj.update(line)
                else:
                    file_obj.close()
                    md5_val = md5_obj.hexdigest()
                    self.send_response(259,{"md5":md5_val})
                    print("---file send done---")
            else:
                for line in file_obj:
                    self.request.send(line)
                else:
                    file_obj.close()
                    print("---file send done---")
        else:
            self.send_response(256)



 8.粘包问题

python判断ftp路径 python ftps_python判断ftp路径_04

发送端可以是1k,1k的发送数据而接受端的应用程序可以2k,2k的提取数据,当然也有可能是3k或者多k提取数据,也就是说,应用程序是不可见的,因此TCP协议是面来那个流的协议,这也是容易出现粘包的原因而UDP是面向笑死的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任一字节的数据,这一点和TCP是很同的。怎样定义消息呢?认为对方一次性write/send的数据为一个消息,需要命的是当对方send一条信息的时候,无论鼎城怎么样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

例如基于TCP的套接字客户端往服务器端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看来更笨不知道文件的字节流从何初开始,在何处结束。

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多数据后才发上一个TCP段。如连续几次下需要send的数据都很少,通常TCP会根据优化算法把 这些数据合成一个TCP段后 一次发送出去,这样接收方就收到了粘包数据

以下两种情况会出现粘包的现象:

1.发送端需要等本机的缓冲区满了以后才发送出去,造成粘包(发送数据时间间隔很短,数据很小,会合在一个起,产生粘包)

2.接收端不及时接收缓冲区的包,造成多个包接受(客户端发送一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据 ,就产生粘包)

解决方法:

1.添加睡眠时间time.sleep(0.5)

2.在2次发送之间在进行一次接受

client端



def _get(self,cmd_list):#文件下载
        print("_get",cmd_list)
        if len(cmd_list) == 1:
            print("no filename folows...")
            return
        data_header = {
            "action":"get",
            "filename":cmd_list[1]
        }
        if self.__md5_required(cmd_list):
            data_header["md5"] = True

        self.sock.send(json.dumps(data_header).encode())
        response = self.get_response()
        print(response)
        if response["status_code"] == 257:
            self.sock.send(b"1") #给服务器端发送一次数据防止粘包
            base_filename = cmd_list[1].split("/")[-1]
            recevied_size = 0
            file_obj = open(base_filename,"wb")

            if self.__md5_required(cmd_list):
                md5_obj = hashlib.md5()
                while recevied_size<response["file_size"]:
                    data = self.sock.recv(1024)
                    recevied_size +=len(data)
                    file_obj.write(data)
                    md5_obj.update(data)
                else:
                    print("----file recv done-----")
                    file_obj.close()
                    md5_val = md5_obj.hexdigest()
                    md5_server = self.get_response()
                    if md5_server["status_code"] == 259:
                        if md5_server["md5"] == md5_val:
                            print("%s 文件一致性验证成功"%base_filename)
                    print(md5_val,md5_server)
            else:
                while recevied_size<response["file_size"]:
                    data = self.sock.recv(1024)
                    recevied_size +=len(data)
                    file_obj.write(data)

                else:
                    print("----file recv done-----")
                    file_obj.close()



 server端



def _get(self,*args,**kwargs): #文件下载
        data = args[0]
        if data.get("filename") is  None:
            self.send_response(255)
        user_home_dir = "%s/%s" %(setting.USER_HOME,self.user["Username"])
        file_abs_path = "%s/%s" %(user_home_dir,data.get("filename"))
        print(user_home_dir)
        print(file_abs_path)
        if os.path.isfile(file_abs_path):
            file_obj = open(file_abs_path,"rb")
            file_size = os.path.getsize(file_abs_path)
            self.send_response(257,data = {"file_size":file_size})
            self.request.recv(1)#防止粘包做一次接受

            if data.get("md5"):
                md5_obj = hashlib.md5()
                for line in file_obj:
                    self.request.send(line)
                    md5_obj.update(line)
                else:
                    file_obj.close()
                    md5_val = md5_obj.hexdigest()
                    self.send_response(259,{"md5":md5_val})
                    print("---file send done---")
            else:
                for line in file_obj:
                    self.request.send(line)
                else:
                    file_obj.close()
                    print("---file send done---")
        else:
            self.send_response(256)



9.进度条

一个简单的进度条



import time

for i in range(10):
    print("#",end="",flush=True)
    time.sleep(0.5)



 

 

 

 

 

 

 

 

 

*****************未完待续********************