• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

小程序5:FTP程序

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

目录

1.FTP程序所需要的知识点

2.FTP程序具体实现过程

  2.1 FTP程序之注册功能

  2.2 FTP程序之登录功能

  2.3 FTP程序之下载功能

3.FTP程序源代码

FTP程序所需要的知识点

1.socketserver并发编程

2.连续send,recv黏包现象:struct

3.hashlib模块的md5加密

4.静态方法staticmethod和类方法classmethod

5.json序列化

6.反射:hasattr,setattr

7.os模块相关方法

FTP程序具体实现过程

FTP程序之注册功能

1.要明确,FTP程序是要实现服务端的并发的,所以需要引入socketserver模块来实现并发

2.写服务端下socketserver的基本语法[day31:socketserver的基本语法]

# 服务端
import socketserver

class FTPServer(socketserver.BaseRequestHandler):
    def handle(self):
        pass

myserver = socketserver.ThreadingTCPServer(("127.0.0.1",9000),FTPServer)
myserver.serve_forever()


# 客户端
import socket
sk = socket.socket()
sk.connect(("127.0.0.1",9000))

sk.close()

3.用户需要自己输入账号和密码,所以在客户端需要写输入用户名和密码的方法(输入用户名和密码后,发送给服务端)

4.在客户端定义auth方法,先写两个input输入用户名和密码

5.输入完用户名密码之后,怎样将用户信息传给服务端呢?

将用户名和密码以及操作做成一个字典,并用json序列化成字符串,并encode后,使用sk.send()发送给服务端

这部分的具体代码如下所示:

# 客户端
def auth(opt):
    usr = input("username:").strip()
    pwd = input("password:").strip()
    dic = {"user":usr,"passwd":pwd,"operate":opt}
    str_dic = json.dumps(dic) # 将字典序列化成字符串
    sk.send(str_dic.encode()) # 将字符串转化成字节流并发送出去

auth("register")

6.服务端已经将用户名密码和操作发过去了,所以现在服务端需要接收一下,服务端的整体逻辑写在类中的handle方法

再定义一个专门用来接收的方法myrecv,并使用handle方法去调用myrecv方法

这部分的具体代码如下所示:

# 服务端
class FTPServer(socketserver.BaseRequestHandler):
    def handle(self):
        opt_dic = self.myrecv()
        print(opt_dic)

    def myrecv(self):
        info = self.request.recv(1024)
        opt_str = info.decode()
        opt_dic = json.loads(opt_str)
        return opt_dic

通过以上步骤,我们实现了一収一发

7.接收到了客户端发来的数据,我们就可以在服务端写一些关于注册的逻辑

在服务端定义Auth类,专门用来实现注册登录,在handler方法也可以去调用类中的成员

那么Auth类中应该写什么呢?

1.首先在当前目录创建db文件夹,并在db问文件夹中创建userinfo.txt用来存放用户名和密码

2.对密码使用md5加密

8.在Auth类中定义md5方法,用来对密码进行一个加密操作

# 服务端
class Auth():
    def md5(usr,pwd):
        md5_obj = hashlib.md5(usr.encode())
        md5_obj.update(pwd.encode())
        return md5_obj.hexdigest()

我们先加密一份数据存放到userinfo.txt中

9.现在已经对每个用户名的密码加密了,但是还有一个问题需要考虑,在注册的时候,不能注册已经存在的用户名,所以需要对用户名进行判断

10.定义register方法,并使用classmethod装饰器,当其他类调用register方法时,会自动传递类参数.

11.拼接出一个userinfo所在文件的完整路径

1.首先获取当前文件(server.py)所在的位置

两种方法:

  方法一:os.getcwd()

  方法二:os.path.dirname(__file__)

print(os.getcwd()) # F:\OldBoyPython\week6\day36
print(__file__) # F:/OldBoyPython/week6/day36/ceshi.py
print(os.path.dirname(__file__)) # F:/OldBoyPython/week6/day36

2.使用os.path.join进行路径拼接

base_path = os.getcwd()
userinfo = os.path.join(base_path,"db","userinfo.txt")
print(userinfo) # F:\OldBoyPython\week6\day36\db\userinfo.txt

这样,我们就获取到了userinfo.txt的绝对路径了

12.当有了userinfo.txt的绝对路径后,我们就可以开始文件操作

在第9步,我们说到要检测用户名是否存在,现在我们就可以实现了

当用户名存在时,返回一个状态False和一个用户名已存在信息提示

@classmethod
def register(cls, opt_dic):
    with open(userinfo, mode=\'r\', encoding=\'utf-8\') as fp:
        for line in fp:
            username = line.split(":")[0]
            if username == opt_dic["user"]:
                return {"result": False, "info": "用户名存在了"}

13.用户名存在的逻辑已经写完,接下来就是用户名可以使用的逻辑

要注意:密码需要加密后再写入

with open(userinfo, mode=\'a+\', encoding=\'utf-8\') as fp:
    # 账号就是字典的账号,密码使用md5加密处理后再写入文件
    strvar = "%s:%s\n" % (opt_dic["user"], cls.md5(opt_dic["user"], opt_dic["passwd"]))
    fp.write(strvar)

如果登录成功了,返回一个状态True和一个注册成功信息提示

到此,注册部分的逻辑就已经写完了,具体代码如下所示:

@classmethod
def register(cls, opt_dic):
    # 1.检测注册的用户是否存在
    with open(userinfo, mode=\'r\', encoding=\'utf-8\') as fp:
        for line in fp:
            username = line.split(":")[0]
            if username == opt_dic["user"]:
                return {"result": False, "info": "用户名存在了"}
    # 2.当前用户可以注册
    with open(userinfo, mode=\'a+\', encoding=\'utf-8\') as fp:
        # 账号就是字典的账号,密码使用md5加密处理后再写入文件
        strvar = "%s:%s\n" % (opt_dic["user"], cls.md5(opt_dic["user"], opt_dic["passwd"]))
        fp.write(strvar)

    # 3.返回一个注册成功的状态
    return {"result": True, "info": "注册成功"}

14.注册的register方法已经写完,但是现在我们需要将register方法和下面的FTPServer类建立联系,这个时候就需要使用反射来实现了

换句话来说:就是想在FTPServer的handle方法中使用Auth中的register方法

15.构建出反射,代码如下所示

到目前为止,基本的代码已经实现,现进行测试,代码如下所示

# 服务端
import socketserver
import json
import hashlib
import os


# 找当前数据库文件所在的绝对路径
base_path = os.getcwd()
# F:\OldBoyPython\week6\day36\db\userinfo.txt
userinfo = os.path.join(base_path,"db","userinfo.txt")

class Auth():
    @ staticmethod
    def md5(usr,pwd):
        md5_obj = hashlib.md5(usr.encode())
        md5_obj.update(pwd.encode())
        return md5_obj.hexdigest()

    @ classmethod
    def register(cls,opt_dic):
        # 1.检测注册的用户是否存在
        with open(userinfo,mode=\'r\',encoding=\'utf-8\') as fp:
            for line in fp:
                username = line.split(":")[0]
                if username == opt_dic["user"]:
                    return {"result":False,"info":"用户名存在了"}
        # 2.当前用户可以注册
        with open(userinfo, mode=\'a+\', encoding=\'utf-8\') as fp:
            # 账号就是字典的账号,密码使用md5加密处理后再写入文件
            strvar = "%s:%s\n" % (opt_dic["user"],cls.md5(opt_dic["user"],opt_dic["passwd"]))
            fp.write(strvar)

        # 3.返回一个注册成功的状态
        return {"result":True,"info":"注册成功"}

class FTPServer(socketserver.BaseRequestHandler):
    def handle(self):
        opt_dic = self.myrecv()
        print(opt_dic)
        if hasattr(Auth,"register"):
            res = getattr(Auth,"register")(opt_dic)
            print(res)


    def myrecv(self):
        info = self.request.recv(1024)
        opt_str = info.decode()
        opt_dic = json.loads(opt_str)
        return opt_dic

myserver = socketserver.ThreadingTCPServer(("127.0.0.1",9000),FTPServer)
myserver.serve_forever()
# 客户端
import socket
import json

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

# 处理収发数据的逻辑
def auth(opt):
    usr = input("username:").strip()
    pwd = input("password:").strip()
    dic = {"user":usr,"passwd":pwd,"operate":opt}
    str_dic = json.dumps(dic) # 将字典序列化成字符串
    sk.send(str_dic.encode()) # 将字符串转化成字节流并发送出去

auth("register")

sk.close()

运行结果如下图所示

客户端输入用户名和密码

服务端接收到客户端发来的数据

并且userinfo.txt也已经写入了你刚才在客户端输入的用户名和密码

16.在服务端我们可以看到注册成功/注册失败的信息了,现在我们想把这个信息发回给客户端,在客户端也能显示出来

和服务端的myrecv方法一样,我们需要自定义一个接収方法mysend

既然在服务端发数据,当然要在客户端接收数据

好的,到此第一部分注册功能就全部完成了。让我们看一下运行结果

所有的信息都应该是显示在客户端上的

FTP程序之登录功能

1.现在添加了登录功能,所以反射的时候就要动态起来。

2.Auth类中只有注册和登录两个方法,如果用户在客户端传入其他方法,必须要给予错误的提示

下面,我们来测试一下结果

3.现在就可以开始写登录函数的逻辑了。。。

登录嘛,肯定是要验证用户名和密码的,所以肯定需要从userinfo.txt中取出用户名和密码进行比对

所以先进行文件操作,将用户名和密码取出来,在进行验证

@ classmethod
def login(cls,opt_dic):
    with open(userinfo,mode=\'r\',encoding=\'utf-8\') as fp:
        for line in fp:
            username,password = line.strip().split(":")
            if username == opt_dic["user"] and password == cls.md5(opt_dic["user"],opt_dic["passwd"]):
                return {"result":True,"info":"登陆成功"}

        return {"result":False,"info":"登录失败"}

其他的部分都不用改,定义了login函数,FTPServer就会自己识别是什么操作,并且通过反射获取到对应方法的返回值,将返回值发送给客户端,然后客户端接收后,打印出来

运行结果如下图所示

4.到此,登录部分的逻辑也已经完成了!!

但是在客户端调用时,还是非常死板的

这种调用方式非常的lowb,所以需要改进一下。。

我们需要搞一个界面。

5.先在客户端定义login函数和register函数,在函数里进行调用。

6.除了登录和注册函数,还需要搞一个退出的功能

在客户端定义myexit函数,用来实现退出的功能

现在我们在客户端已经定义了退出函数,但是在服务端我们也要让服务端知道退出的状态。

我们在客户端发送了一个opt_dic给服务端,然后服务端接收这个opt_dic

到此,退出功能就已经实现完了。

7.现在我们需要把登录,注册和退出形成一套界面

def main():
    # 生成菜单界面
    for i,tup in enumerate(operate_lst,start=1):
        print(i,tup[0])
    
    # 输入相应序号,实现对应操作
    num = int(input("请选择您要进行的操作>>>"))
    res = operate_lst[num-1][1]()
    return res # 将对应操作的返回值返回出来

while True:
    res = main() # 调用main获取到对应的返回值
    print(res)

在客户端我们可以通过while True实现循环调用main,进而可以进行循环登录注册和退出。

那么在服务端我们也应该是循环进行调用注册登录和退出

所以需要在服务端也加上一个while True

8.到此为止,登录,注册和退出的功能就都已经实现了。

代码如下所示

# 服务端
import socketserver
import json
import hashlib
import os

# 找当前数据库文件所在的绝对路径
base_path = os.path.dirname(__file__)
# /mnt/hgfs/python31_gx/day36/db/userinfo.txt
userinfo = os.path.join(base_path,"db","userinfo.txt")

class Auth():
    @staticmethod
    def md5(usr,pwd):
        md5_obj = hashlib.md5(usr.encode())
        md5_obj.update(pwd.encode())
        return md5_obj.hexdigest()

    @classmethod
    def register(cls,opt_dic):
        # 1.检测注册的用户是否存在
        with open(userinfo,mode="r",encoding="utf-8") as fp:
            for line in fp:
                username = line.split(":")[0]
                if username == opt_dic["user"]:
                    return {"result":False,"info":"用户名存在了"}
                    
        # 2.当前用户可以注册
        with open(userinfo,mode="a+",encoding="utf-8") as fp:
            strvar = "%s:%s\n" % (   opt_dic["user"] , cls.md5(   opt_dic["user"],opt_dic["passwd"]   )    )
            fp.write(strvar)
                    
        """
            当用户上传的时候,给他创建一个专属文件夹,存放数据
        """
        
        # 3.返回状态
        return {"result":True,"info":"注册成功"}
        
    @classmethod
    def login(cls,opt_dic):
        with open(userinfo , mode="r" , encoding="utf-8") as fp:
            for line in fp:
                username,password = line.strip().split(":")
                if username == opt_dic["user"] and password == cls.md5( opt_dic["user"] , opt_dic["passwd"] ) :
                    return {"result":True,"info":"登录成功"}
             
            return {"result":False,"info":"登录失败"}
        
    @classmethod
    def myexit(cls,opt_dic):
        return {"result":"myexit"}
        

class FTPServer(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            opt_dic = self.myrecv()
            print(opt_dic) # {\'user\': \'wangwen\', \'passwd\': \'111\', \'operate\': \'register\'}
            if hasattr(Auth,opt_dic["operate"]):
                # print(  getattr(Auth,"register")   )
                res = getattr(Auth,opt_dic["operate"])(opt_dic) # login(opt_dic)
                
                # 如果接受的操作是myexit,代表退出
                if res["result"] == "myexit":
                    return 
                
                # 把注册的状态发送给客户端
                self.mysend(res)
            else:
                dic = {"result":False,"info":"没有该操作"}
                self.mysend(dic)

    # 接收方法
    def myrecv(self):
        info = self.request.recv(1024)
        opt_str = info.decode()
        opt_dic = json.loads(opt_str)
        return opt_dic        
        
    # 发送方法
    def mysend(self,send_info):
        send_info = json.dumps(send_info).encode()
        self.request.send(send_info)

# 设置一个端口可以绑定多个程序
# socketserver.TCPServer.allow_reuse_address = True
myserver = socketserver.ThreadingTCPServer( ("127.0.0.1",9000) , FTPServer)
myserver.serve_forever()
# ### 客户端
import socket
import json
""""""
sk = socket.socket()
sk.connect( ("127.0.0.1",9000) )

# 处理收发数据的逻辑
def auth(opt):
    usr = input("username: ").strip()
    pwd = input("password: ").strip()
    dic = {"user":usr,"passwd":pwd,"operate":opt}
    str_dic = json.dumps(dic)
    # 发送数据
    sk.send(str_dic.encode("utf-8"))
    
    # 接受服务端响应的数据
    file_info = sk.recv(1024).decode()
    file_dic = json.loads(file_info)
    return file_dic
    


# 注册
def register():
    res = auth("register")
    return res

# 登录
def login():
    res = auth("login")
    return res
    
# 退出
def myexit():    
    opt_dic = {"operate":"myexit"}
    sk.send(json.dumps(opt_dic).encode())
    exit("欢迎下次再来")
    

# 第一套操作界面
#                      0                  1                2
operate_lst1 = [ ("登录",login) ,("注册",register) , ("退出",myexit) ]



"""
1.登录
2.注册
3.退出

1 (\'登录\', <function login at 0x7ff7cf171a60>)
2 (\'注册\', <function register at 0x7ff7cf17e620>)
3 (\'退出\', <function myexit at 0x7ff7cf171ae8>)
"""



def main():
    for i,tup in enumerate(operate_lst1,start=1):
        print(i , tup[0])
    num = int(input("请选择执行的操作>>> ").strip()) # 1 2 3
    # 调用函数
    # print(operate_lst1[num-1]) (\'退出\', <function myexit at 0x7f801e34aa60>)
    # print(operate_lst1[num-1][1]) <function myexit at 0x7f801e34aa60>
    # operate_lst1[num-1][1]() myexit()
    res = operate_lst1[num-1][1]()
    return res
    
while True:
    # 开启第一套操作界面
    res = main()
    print(res)

sk.close()

执行结果如下图所示

FTP注册之下载功能

1.当你登录成功后,要跳转到另一套界面,让用户选择下载上传还是退出

所以我们需要像登录注册退出那套界面逻辑一样,再搞一个operate_lst2

只有登录成功的时候,才能出现第二套界面。

2.客户端现在已经发送过去了,那么对应的服务端也应该有所接收

 

3.download我们后面再说,先把界面2的退出搞定

同理,客户端的myexit有exit()直接终止程序,在服务端也要及时终止程序

直接搞上一个return,连循环加函数全都退出

到此,界面2的退出也已经搞定了,接下来就搞最复杂的download

4.下载,先搞一下这个客户端

在客户端定义一个download方法,定义一个字典,字典里写入操作和下载的文件名

5.客户端定义了下载方法将字典发送过去,服务端也应该定义download下载方法来接收这个字典并进行逻辑操作

# 服务端
def download(self, opt_dic):
    filename = opt_dic["filename"]  # 获取用户在客户端输入的文件名
    file_abs = os.path.join(base_path, "video", filename)  # 获取到要下载视频的绝对路径
    if os.path.exists(file_abs):  # 如果文件存在
        dic = {"result": True, "info": "文件存在,可以下载"}
        self.mysend()
    else:  # 如果文件不存在
        pass

6.如果文件存在可以下载,那么就可以执行下载的流程了

在下载时,服务端需要将视频发送给客户端,因为视频很大,且需要分段发送,所以可能会存在黏包现象。

所以需要引入struct模块,并改造mysend方法,以解决黏包现象

# 服务端
def mysend(self, send_info, sign=False):
    send_info = json.dumps(send_info).encode()
    if sign:
        # 1.发送数据的长度
        res = struct.pack("i", len(send_info))
        self.request.send(res)
    # 2.发送真实的数据
    self.request.send(send_info)
# 客户端
def myrecv(info_len=1024,sign=False):
    if sign:
        # 1.接受数据的长度
        info_len = sk.recv(4)
        info_len = struct.unpack("i",info_len)[0]

    # 2.接受真实的数据
    file_info = sk.recv(info_len).decode()
    file_dic = json.loads(file_info)
    return
                      

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap