python简易FTP

python简易FTP

一、需求  

1. 用户加密认证 
2. 每个用户有自己的家目录 ,且只能访问自己的家目录
3. 允许用户在ftp server上随意切换目录cd
4. 允许上传post和下载get文件
5. 文件传输过程中显示进度条

二、程序目录结构  

 

三、源代码  

   FTPclient客户端   

 1 import os,sys
 2 
 3 BASE_dir=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 4 
 5 sys.path.append(BASE_dir)
 6 
 7 from service import supermain
 8 
 9 if __name__=="__main__":
10     supermain.client_main()
bin\start
1 import os
2 BIND_HOST="127.0.0.1"
3 BIND_PORT=8666
4 BASEDIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
5 USER_HOME=os.path.join(BASEDIR,"home")
conf\settings
 1 import hashlib,sys
 2 def get_file_MD5(file_path):
 3     obj=hashlib.md5()
 4     f=open(file_path,"rb")
 5     while True:
 6         data=f.read(8000)
 7 
 8         if not data:
 9             break
10         obj.update(data)
11     return obj.hexdigest()
12 
13 def bar(sent_num,whole_num):
14     f_sent_whole=float(sent_num)/float(whole_num)
15     int_f=int(f_sent_whole*100)
16     temp="\r%d %%"%(int_f,)
17     sys.stdout.write(temp)
18     sys.stdout.flush()
lib\commons
  1 import socket,json,re,os
  2 from conf import settings
  3 from lib import commons
  4 def client_main():
  5     sk = socket.socket()
  6     ip_port = (settings.BIND_HOST, settings.BIND_PORT)
  7     sk.connect(ip_port)
  8     client_main_class(sk)
  9 
 10 class client_main_class(object):
 11     def __init__(self,sk):
 12         self.name=None
 13         self.home=None
 14         self.sk=sk
 15         #接收服务器端发来的欢迎并打印
 16         print(str(self.sk.recv(1024),"utf8"))
 17         while True:
 18             self.info()
 19             #输入cmd|ipconfig
 20             inp=input(">>>").strip()
 21             #得到cmd
 22             inp_split=inp.split("|",1)[0]
 23             if inp_split=="help":
 24                 continue
 25             if inp_split=="exit":
 26                 break
 27             func_list=["cmd","post","get"]
 28             if inp_split in func_list:
 29                 client_func=getattr(self,inp_split)
 30                 client_func(inp)
 31 
 32     def info(self):
 33         info_data="""请按格式输入
 34         cmd|xxxx
 35        post|xxxx
 36         get|xxxx
 37         help
 38         exit
 39         """
 40         print(info_data)
 41 
 42     def login(self):
 43         print("账号未登录")
 44         username=input("请输入账号:").strip()
 45         password=input("请输入密码:").strip()
 46         info_user={"username":username,"password":password}
 47         #将账号和密码发送过去
 48         self.sk.sendall(bytes(json.dumps(info_user),"utf8"))
 49         #接收服务器发来的消息如果是4002代表登陆成功,其他就失败
 50         data=str(self.sk.recv(1024),"utf8")
 51         if data=="4002":
 52             self.name=username
 53             self.home=os.path.join(settings.USER_HOME,self.name)
 54             if not os.path.exists(self.home):
 55                 os.makedirs(self.home)
 56             print("登陆成功!✿✿ヽ(°▽°)ノ✿✿")
 57         else:
 58             print("登陆失败")
 59 
 60     def cmd(self,inp):
 61         self.sk.sendall(bytes(inp,"utf8"))
 62         server_send=self.sk.recv(1024)
 63         server_send_str=str(server_send,"utf8")
 64         if server_send_str =="4001":
 65             self.login()
 66         else:
 67             self.sk.sendall(bytes("客户端准备好接收了","utf8"))
 68             #接收服务器发来的关于接下来要发送的文字字数多少
 69             cmd_result_len=int(str(self.sk.recv(1024),"utf8"))
 70             print(cmd_result_len)
 71             #回响一下进入下一步然后准备接收文字
 72             self.sk.sendall(bytes("response","utf8"))
 73             client_cmd_result_len=0
 74             all_data=bytes()
 75             #只要字数不等就一直接收
 76             while client_cmd_result_len<cmd_result_len:
 77                 cmd_data=self.sk.recv(1024)
 78                 client_cmd_result_len+=len(cmd_data)
 79                 all_data += cmd_data
 80 
 81             print(str(all_data,"utf8"))
 82 
 83 
 84     #上传文件
 85     def post(self,inp):
 86         #post|F:\1.txt   2.txt
 87         method,files_path=inp.split("|",1)
 88 
 89         local_file_path,target_file_name=re.split("\s+",files_path)
 90         #将本地文件的名称、大小、MD5值、传过去以后在服务器端文件名称传过去
 91         local_file_name=os.path.basename(local_file_path)
 92         local_file_size=os.stat(local_file_path).st_size
 93         local_file_MD5=commons.get_file_MD5(local_file_path)
 94         local_file_info="post|%s|%s|%s|%s"%(local_file_name,local_file_size,local_file_MD5,target_file_name)
 95 
 96         self.sk.sendall(bytes(local_file_info, "utf8"))
 97         server_send = self.sk.recv(1024)
 98         server_send_str = str(server_send, "utf8")
 99         has_sent=0
100         if server_send_str=="4001":
101             self.login()
102             return
103         while True:
104             if server_send_str=="3000":
105                 input_continues=input("是否要断点续传?按Y(yes)或N(no)")
106                 if input_continues=="Y":
107                     self.sk.sendall(bytes("Y","utf8"))
108                     target_file_size=int(str(self.sk.recv(1024),"utf8"))
109                     has_sent=target_file_size
110 
111                 elif input_continues=="N":
112                     self.sk.sendall(bytes("N","utf8"))
113                 else:
114                     print("输入错误")
115                     continue
116             f = open(local_file_path, "rb")
117             f.seek(has_sent)
118             while has_sent<local_file_size:
119                 file_data=f.read(1024)
120                 if not file_data:
121                     break
122                 self.sk.sendall(file_data)
123                 has_sent+=len(file_data)
124                 commons.bar(has_sent,local_file_size)
125             f.close()
126             print("上传成功")
127             break
128 
129 
130 
131     #下载文件
132     def get(self,inp):
133         #不加这一步没有家目录会报错,TypeError: join() argument must be str or bytes, not 'NoneType'
134         self.sk.sendall(bytes(inp,"utf8"))
135         server_send_str=str(self.sk.recv(1024),"utf8")
136         #去登录
137         if server_send_str=="4001":
138             self.login()
139             return
140         #确认登陆
141         if server_send_str=="4100":
142                 #get|服务器那里叫什么.txt  下载过来叫什么.txt
143                 fun,files_path=inp.split("|",1)
144                 target_file_name,local_file_name=re.split("\s+",files_path)
145                 local_file_path=os.path.join(settings.USER_HOME,self.name,local_file_name)
146                 has_file_size=0
147                 #文件存在并文件大小大于0,就记录文件大小,如果文件大小为0当做没文件算
148                 if os.path.exists(local_file_path) and os.stat(local_file_path).st_size>0:
149                            has_file_size=os.stat(local_file_path).st_size
150                 get_info="get|%s|%d"%(target_file_name,has_file_size)
151                 self.sk.sendall(bytes(get_info,"utf8"))
152                 server_replay=str(self.sk.recv(1024),"utf8")
153 
154                 if server_replay=="5000":
155                     #想知道对面文件大小5001
156                     self.sk.sendall(bytes("5001","utf8"))
157                     server_file_size=int(str(self.sk.recv(1024),"utf8"))
158                     if has_file_size>0 and has_file_size<server_file_size:
159                          inp_Y_N=input("是否断点续传?Y/N:").strip()
160                          if inp_Y_N=="Y":
161                              f=open(local_file_path,"ab")
162                              self.sk.sendall(bytes("Y", "utf8"))
163                          else:
164                              f=open(local_file_path,"wb")
165                              self.sk.sendall(bytes("N", "utf8"))
166                              has_file_size = 0
167                     elif has_file_size==server_file_size:
168                         self.sk.sendall(bytes("N", "utf8"))
169                         f = open(local_file_path, "wb")
170                         has_file_size=0
171                     else:
172                         self.sk.sendall(bytes("N", "utf8"))
173                         f=open(local_file_path,"wb")
174                         has_file_size = 0
175                     # self.sk.sendall(bytes("客户端准备好接收文件了","utf8"))
176                     while has_file_size<server_file_size:
177                         sent_data=self.sk.recv(1024)
178                         f.write(sent_data)
179                         has_file_size+=len(sent_data)
180                         commons.bar(has_file_size,server_file_size)
181                     f.close()
182                     print("传输完成!!!!")
183                 elif server_replay=="5100":
184                     print("服务器没有这个文件或文件大小为0")
service\supermain

   FTPserver服务器   

1 import subprocess,os,sys
2 BASEDIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
3 
4 sys.path.insert(0,BASEDIR)
5 
6 from service import supermain
7 
8 if __name__=="__main__":
9      supermain.MultiServer()
bin\start
1 import os,sys
2 BASEDIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
3 USER_HOME=os.path.join(BASEDIR,"home")
4 
5 BIND_HOST="127.0.0.1"
6 BIND_PORT=8666
conf\settings
  1 import socketserver,json,os,subprocess,re
  2 from conf import settings
  3 class action(object):
  4     def __init__(self,conn):
  5         self.conn=conn
  6         self.is_login=False
  7         self.home=None
  8         self.name=None
  9         self.current_dir=None
 10 
 11     def login(self):
 12         #发送4001到client客户端代表需要登陆
 13         self.conn.sendall(bytes("4001","utf8"))
 14 
 15         client_info=json.loads(str(self.conn.recv(1024),"utf8"))
 16         if client_info["username"]=="wxy" and client_info["password"]=="123":
 17             self.is_login=True
 18             self.name=client_info["username"]
 19             self.conn.sendall(bytes("4002","utf8"))
 20             #初始化家目录和当前目录
 21             self.initialize()
 22         else:
 23             self.conn.sendall(bytes("4003"), "utf8")
 24 
 25     def initialize(self):
 26          self.home=os.path.join(settings.USER_HOME,self.name)
 27          self.current_dir=os.path.join(settings.USER_HOME,self.name)
 28 
 29     def cmd(self,client_inp):
 30             #4004已在登陆状态
 31             self.conn.sendall(bytes("4004","utf8"))
 32             print(str(self.conn.recv(1024),"utf8"))
 33             client_cmd,client_msg=client_inp.split("|",1)
 34             #dir F:\wxy
 35             client_msg_list=re.split('\s+',client_msg,1)
 36             #查看目录结构
 37             if client_msg_list[0]=="dir":
 38                 #只有dir就直接在后面加上家目录文件列表
 39               if len(client_msg_list)==1:
 40                   if self.current_dir:
 41                      client_msg_list.append(self.current_dir)
 42                   else:
 43                      client_msg_list.append(self.home)
 44 
 45             if client_msg_list[0]=="cd":
 46               if len(client_msg_list)==1:
 47                   if self.current_dir:
 48                      client_msg_list.append(self.current_dir)
 49                   else:
 50                      client_msg_list.append(self.home)
 51             #列表里的字符串以空格的方式连接成一个字符串
 52             client_msg=" ".join(client_msg_list)
 53             #"cd F:\FTP"
 54             print(client_msg)
 55             try:
 56                 #获取信息
 57                 a_stdout=subprocess.Popen(client_msg,shell=True,stdout=subprocess.PIPE)
 58                 cmd_result=a_stdout.stdout.read()
 59                 # cmd_result=subprocess.check_output(client_msg,shell=True)
 60                 #cmd_result返回的是bytes格式gbk编码换成utf8的字节
 61                 cmd_result=bytes(str(cmd_result,"gbk"),"utf8")
 62             except Exception as e:
 63                 cmd_result=bytes("cmd格式不对,参考cmd|cd F:\wxy","utf8")
 64 
 65             cmd_result_len=len(cmd_result)
 66             #int转str转bytes然后发过去
 67             self.conn.sendall(bytes(str(cmd_result_len),"utf8"))
 68             response=self.conn.recv(1024)
 69             self.conn.sendall(cmd_result)
 70 
 71 
 72 
 73 
 74 
 75     def post(self,client_inp):
 76         func,client_file_name,client_file_size,client_file_MD5,target_file_name=client_inp.split("|",4)
 77         target_file_path=os.path.join(self.home,target_file_name)
 78         has_file_len=0
 79         client_file_size_int=int(client_file_size)
 80         #文件是否已经存在,存在就计算出文件大小发送给客户端要不要计算文件大小
 81         if os.path.exists(target_file_path) and os.stat(target_file_path).st_size>0:
 82             target_file_size=os.stat(target_file_path).st_size
 83 
 84             #3000有文件存在
 85             self.conn.sendall(bytes("3000","utf8"))
 86             #3001收到要断点续传
 87             Y_N_recv=str(self.conn.recv(1024), "utf8")
 88             if Y_N_recv=="3001":
 89                 self.conn.sendall(bytes(str(target_file_size),"utf8"))
 90                 has_file_len=target_file_size
 91                 f=open(target_file_path,"ab")
 92 
 93             else:
 94                 f=open(target_file_path,"wb")
 95         #不存在发3006
 96         else:
 97             self.conn.sendall(bytes("3006","utf8"))
 98             f=open(target_file_path,"wb")
 99         while client_file_size_int>has_file_len:
100             data=self.conn.recv(1024)
101             if not data:
102                 break
103             f.write(data)
104             has_file_len+=len(data)
105         f.close()
106         print("传输完成")
107 
108 
109 
110 
111     def get(self,client_inp):
112         #确认已经登陆了
113         self.conn.sendall(bytes("4100","utf8"))
114         client_inp=str(self.conn.recv(1024),"utf8")
115         print(client_inp)
116         #get|服务器文件名|客户端文件大小
117         fun,target_file_name,has_file_size=client_inp.split("|",2)
118         #因为has_file_size在前面合并的时候已经变成字符串了所以要变回int
119         has_file_size=int(has_file_size)
120         target_file_path=os.path.join(self.home,target_file_name)
121         has_sent=0
122         if os.path.exists(target_file_path) and os.stat(target_file_path).st_size>0:
123             target_file_size=os.stat(target_file_path).st_size
124             self.conn.sendall(bytes("5000","utf8"))
125             #5001想知道服务器文件大小
126             self.conn.recv(1024)
127             self.conn.sendall(bytes(str(target_file_size),"utf8"))
128             inp_Y_N=str(self.conn.recv(1024),"utf8")
129             if inp_Y_N=="Y":
130                 has_sent=has_file_size
131             f=open(target_file_path,"rb")
132             f.seek(has_sent)
133             while has_sent<target_file_size:
134                 sent_file_data=f.read(1024)
135 
136                 self.conn.sendall(sent_file_data)
137                 has_sent+=len(sent_file_data)
138             f.close()
139             print("传输完成!!")
140         else:
141             self.conn.sendall(bytes("5100"),"utf8")
142 
143 class MultiServerHandler(socketserver.BaseRequestHandler):
144     def handle(self):
145         conn=self.request
146         conn.sendall(bytes("欢迎使用","utf8"))
147         server_obj=action(conn)
148         while True:
149             #收到cmd|ipconfig
150             client_inp=conn.recv(1024)
151             # 看客户端是否已经断开连接
152             if not client_inp:
153                 break
154             client_inp=str(client_inp,"utf8")
155             #检查是否已经登陆
156             if server_obj.is_login:
157                 selection_func=client_inp.split("|",1)[0]
158                 #得到func=server_obj.selection_func,直接执行action里面的cmd或post这些
159                 func=getattr(server_obj,selection_func)
160                 func(client_inp)
161             else:
162                 #去登陆
163                 server_obj.login()
164 class MultiServer(object):
165     def __init__(self):
166         server=socketserver.ThreadingTCPServer((settings.BIND_HOST,settings.BIND_PORT),MultiServerHandler)
167         server.serve_forever()
service\supermain

 

posted @ 2020-02-05 18:32  学大师  阅读(217)  评论(0)    收藏  举报