ホームページ  >  記事  >  バックエンド開発  >  高度な FTP サーバーの使用の概要

高度な FTP サーバーの使用の概要

PHP中文网
PHP中文网オリジナル
2017-06-20 10:47:153998ブラウズ
高级FTP服务器
1. 用户加密认证
2. 多用户同时登陆
3. 每个用户有自己的家目录且只能访问自己的家目录
4. 对用户进行磁盘配额、不同用户配额可不同
5. 用户可以登陆server后,可切换目录
6. 查看当前目录下文件
7. 上传下载文件,保证文件一致性
8. 传输过程中现实进度条
9.支持断点续传
10.用户操作日志

服务端 启动参数  start
客户端 启动参数  -s localhost -P 9500

程序结构:
seniorFTP/#综合目录
|- - -ftp_client/#客户端程序目录
|           |- - -__init__.py
|           |- - -bin/#启动目录
|           |      |- - -__init__.py
|           |      |- - -client_ftp.py#客户端视图启动
|           |
|           |- - -cfg/#配置目录
|           |      |- - -__init__.py
|           |      |- - -config.py#配置文件
|           |
|           |- - -down/#下载文件目录
|           |
|           |- - -putfile/#上传文件目录
|           |
|           |
|           |- - -REDMAE
|- - -ftp_server/#服务端程序目录
|           |- - -__init__.py
|           |- - -bin/#启动目录
|           |      |- - -__init__.py
|           |      |- - -start.py#服务端视图启动
|           |      |- - -user_reg.py#用户注册启动
|           |
|           |- - -cfg/#配置目录
|           |      |- - -__init__.py
|           |      |- - -config.py#配置文件
|           |      |- - -userpwd.cfg#用户信息文件
|           |
|           |- - -core/#文件目录
|           |      |- - -__init__.py
|           |      |- - -ftp_server.py#服务端主要逻辑 类
|           |      |- - -logs.py#日志主要逻辑 类
|           |      |- - -main.py#服务端启动主程序
|           |
|           |- - -home/#用户文件目录
|           |      |- - -用户/#个人目录
|           |
|           |- - -log/#日志文件目录
|           |
|           |- - -REDMAE
|           |
|
|- - -REDMAE

先上流程图:

详细代码如下:

|- - -ftp_client/#客户端程序目录
|           |- - -__init__.py
|           |- - -bin/#启动目录
|           |      |- - -__init__.py
|           |      |- - -client_ftp.py#客户端视图启动
<br>
  1 #!usr/bin/env python  2 #-*-coding:utf-8-*-  3 # Author calmyan  4 import socket,os,json,getpass,hashlib  5 import os ,sys,optparse  6   7 STATUS_CODE={  8     240:'格式出错,格式:{"action":"get","filename":"filename","size":100}',  9     241:'指令错误', 10     242:'用户密码出错', 11     243:'用户或密码出错', 12     244:'用户密码通过校验', 13 } 14 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 15 sys.path.append(BASE_DIR)#增加环境变量 16 from cfg import config 17 class FTPClient(object): 18     def __init__(self): 19         paresr=optparse.OptionParser() 20         paresr.add_option('-s','--server',dest='server',help='服务器地址') 21         paresr.add_option('-P','--port',type="int",dest='port',help='服务器端口') 22         paresr.add_option('-u','--username',dest='username',help='用户名') 23         paresr.add_option('-p','--password',dest='password',help='密码') 24         (self.options,self.args)=paresr.parse_args()#返回一个字典与列表的元组 25         self.verify_args(self.options,self.args)#判断参数 26         self.ser_connect()#连接服务端 27         self.cmd_list=config.CMD_LIST 28         self.rat=0#文件断点 29  30     #实例化一个连接端 31     def ser_connect(self): 32         self.c=socket.socket()#实例化一个连接端 33         self.c.connect((self.options.server,self.options.port))#进行连接 34  35     #判断用户与密码是否成对出现 36     def verify_args(self,options,args): 37         if (options.username is None and options.password is None) or (options.username is not None and options.password is not None):#判断用户与密码是否成对出现 38             pass##判断用户与密码单个出现 39         else: 40             exit('出错:请输入用户与密码!')#退出 41         if options.server and options.port:#端口判断 42             if options.port>0 and options.port<65535: 43                 return True 44             else: 45                 print(&#39;端口号:[%s]错误,端口范围:0-65535&#39;%options.port) 46  47     #登陆方法 48     def landing(self):#登陆方法 49         &#39;&#39;&#39;用户验证&#39;&#39;&#39; 50         if self.options.username is not None:#判断用户名已经输入 51             #print(self.options.username,self.options.password) 52             return self.get_user_pwd(self.options.username,self.options.password)#返回结果 53         else: 54             print(&#39;用户登陆&#39;.center(60,&#39;=&#39;)) 55             ret_count=0#验证次数 56             while ret_count<5: 57                 username=input(&#39;用户名:&#39;).strip() 58                 password=getpass.getpass(&#39;密码:&#39;).strip() 59                 if self.get_user_pwd(username,password): 60                     return self.get_user_pwd(username,password)#调用远程验证用户 返回结果 61                 else: 62                     ret_count+=1#次数加一 63                     print(&#39;认证出错次数[%s]&#39;%ret_count) 64             else: 65                 print(&#39;密码出错次数过多!&#39;) 66                 exit() 67  68     #&#39;&#39;&#39;用户名与密码检验&#39;&#39;&#39; 69     def get_user_pwd(self,username,password): 70         &#39;&#39;&#39;用户名与密码检验&#39;&#39;&#39; 71         #发送 头文件 72         data={ 73             &#39;action&#39;:&#39;auth&#39;, 74             &#39;username&#39;:username, 75             &#39;password&#39;:password 76         } 77         self.c.send(json.dumps(data).encode())#发送到服务器 78         response = self.get_response()#得到服务端的回复 79         if response.get(&#39;status_code&#39;) == 244: 80             print(STATUS_CODE[244]) 81             self.user = username#存下用户名 82             self.user_dir=response.get(&#39;dir&#39;)#目录 83             return True 84         else: 85             print(response.get("status_msg") ) 86  87     #服务器回复 88     def get_response(self):#服务器回复 89         &#39;&#39;&#39;服务器回复信息&#39;&#39;&#39; 90         data=self.c.recv(1024)#接收回复 91         data = json.loads(data.decode()) 92         return data 93  94     #指令帮助 95     def help(self):#指令帮助 96         attr=&#39;&#39;&#39; 97         help            指令帮助 98         ---------------------------------- 99         info            个人信息100         ----------------------------------101         ls              查看当前目录(linux/windows)102         ----------------------------------103         pwd             查看当前路径(linux/windows)104         ----------------------------------105         cd 目录         切换目录(linux/windows)106         ----------------------------------107         get filename    下载文件108         ----------------------------------109         put filename    上传文件110         ----------------------------------111         --md5           使用md5  在get/put 后112         ----------------------------------113         mkdir name      创建目录(linux/windows)114         ----------------------------------115         rmdir name      删除目录(linux/windows)116         ----------------------------------117         rm filename     删除文件 (linux/windows)118         ----------------------------------119         exit            退出120         ----------------------------------121         &#39;&#39;&#39;.format()122         print(attr)123 124     ##交互125     def inter(self):#交互126         if  self.landing():#通过用户密码认证127             print(&#39;指令界面&#39;.center(60,&#39;=&#39;))128             self.help()129             while True:130                 cmd = input(&#39;[%s]-->指令>>>:'%self.user_dir).strip()131                 if len(cmd)==0:continue#输入空跳过132                 if cmd=='exit':exit()#退出指令133                 cmd_str=cmd.split()#用空格分割 取命令到列表134                 #print(cmd_str)135                 #print(len(cmd_str))136                 if len(cmd_str)==1 and cmd_str[0] in self.cmd_list:#如果是单个命令 并且在命令列表中137                 #if len(cmd_str)==1:#如果是单个命令 并且在命令列表中138                     if cmd_str[0]==config.HELP:139                         self.help()140                         continue141                     func=getattr(self,'cmd_compr')#调用此方法142                     ret=func(cmd_str)143                     if ret:144                         continue145                     else:146                         pass147                 elif len(cmd_str)>1:148                     if hasattr(self,'cmd_%s'%cmd_str[0]):#判断类中是否有此方法149                         func=getattr(self,'cmd_%s'%cmd_str[0])#调用此方法150                         func(cmd_str)#执行151                         continue152                 else:153                     print('指令出错!')154                     self.help()#155 156     #'''是否要md5'''157     def cmd_md5_(self,cmd_list):158         '''是否要md5'''159         if '--md5' in cmd_list:160             return True161 162     #进度条163     def show_pr(self,total):#进度条164         received_size = 0 #发送的大小165         current_percent = 0 #166         while received_size < total:167              if int((received_size / total) * 100 )   > current_percent :168                   print("#",end="",flush=True)#进度显示169                   current_percent = int((received_size / total) * 100 )170              new_size = yield #断点跳转 传入的大小171              received_size += new_size172 173     #单个命令174     def cmd_compr(self,cmd_str,**kwargs):175         mag_dict={176                     "action":"compr",177                     'actionname':cmd_str[0]178                 }179         self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送数据180         cmd_res_attr=self.get_response()#得到服务器的回复181         if type(cmd_res_attr) is not int:#如果不int 类型182             if cmd_res_attr["status_code"] ==241:#命令不对183                 print(cmd_res_attr['status_msg'])184                 return185             if cmd_res_attr["status_code"] ==240:#命令不对186                 print(cmd_res_attr['status_msg'])187                 return188         size_l=0#收数据当前大小189         self.c.send('准备好接收了,可以发了'.encode('utf-8'))190         receive_data= ''.encode()191         while size_l< cmd_res_attr:192             data=self.c.recv(1024)#开始接收数据193             size_l+=len(data)#加上194             receive_data += data195         else:196             receive_data=receive_data.decode()197             try:198                 receive_data=eval(receive_data)#转为列表 或字典199             except Exception as e:200                 pass201             if type(receive_data) is dict:#如果是字典202                 for i in receive_data:203                     print(i,receive_data[i])204                 return 1205             if type(receive_data) is list:#如果是列表206                 for i in receive_data:207                     print(i)208                 return 1209             print(receive_data)210             return 1211 212     #切换目录213     def cmd_cd(self,cmd_list,**kwargs):214         &#39;&#39;&#39;切换目录&#39;&#39;&#39;215         mag_dict={216                     "action":"cd",217                     &#39;actionname&#39;:cmd_list[1]218                 }219         self.c.send(json.dumps(mag_dict).encode(&#39;utf-8&#39;))#发送数据220         msg_l=self.c.recv(1024)#接收数据 消息221         data=json.loads(msg_l.decode())222         if data["status_code"] ==251:#目录不可切换223             print(data[&#39;status_msg&#39;])224             return225         elif data["status_code"] ==252:#目录可以换226             print(data[&#39;status_msg&#39;])227             self.c.send(b&#39;1&#39;)#发送到服务器,表示可以了228             data=self.c.recv(1024)229             print(data.decode())230             user_dir=data.decode()231             print(user_dir)232             self.user_dir=user_dir233             return234         elif data["status_code"] ==256:#目录不存在235             print(data[&#39;status_msg&#39;])236             return237 238     #删除文件239     def cmd_rm(self,cmd_list,**kwargs):240         mag_dict={241                     "action":"rm",242                     &#39;filename&#39;:cmd_list[1]243                 }244         self.c.send(json.dumps(mag_dict).encode(&#39;utf-8&#39;))#发送文件信息245         data=self.get_response()#得到服务器的回复246         if data["status_code"] ==245:#文件不存在247             print(data[&#39;status_msg&#39;])248             #print(&#39;删除前空间:&#39;,data[&#39;剩余空间&#39;])249             return250         elif data["status_code"] ==254:#文件删除完成251             print(data[&#39;status_msg&#39;])252             print(&#39;删除前空间:&#39;,data[&#39;剩余空间&#39;])253             pass254         self.c.send(b&#39;1&#39;)#发送到服务器,表示可以255         data=self.get_response()#得到服务器的回复256         if data["status_code"] ==255:#文件删除完成257             print(data[&#39;status_msg&#39;])258             print(&#39;删除后空间:&#39;,data[&#39;剩余空间&#39;])259             return260 261     #创建目录262     def cmd_mkdir(self,cmd_list,**kwargs):263         mag_dict={264                     "action":"mkdir",265                     &#39;filename&#39;:cmd_list[1]266                 }267         self.c.send(json.dumps(mag_dict).encode(&#39;utf-8&#39;))#发送文件信息268         data=self.get_response()#得到服务器的回复269         if data["status_code"] ==257:#目录已经存在270             print(data[&#39;status_msg&#39;])271             return272         elif data["status_code"] ==256:#目录创建中273             print(data[&#39;目录&#39;])274             pass275         self.c.send(b&#39;1&#39;)#发送到服务器,表示可以276         data=self.get_response()#得到服务器的回复277         if data["status_code"] ==258:#目录创建中完成278             print(data[&#39;status_msg&#39;])279             return280         pass281 282     #删除目录283     def cmd_rmdir(self,cmd_list,**kwargs):284         mag_dict={285                     "action":"rmdir",286                     &#39;filename&#39;:cmd_list[1]287                 }288         self.c.send(json.dumps(mag_dict).encode(&#39;utf-8&#39;))#发送文件信息289         data=self.get_response()#得到服务器的回复290         if data["status_code"] ==256:#目录不存在291             print(data[&#39;status_msg&#39;])292             return293         elif data["status_code"] ==260:#目录不为空294             print(data[&#39;status_msg&#39;])295             print(data[&#39;目录&#39;])296             return297         elif data["status_code"] ==257:#目录删除中298             print(data[&#39;目录&#39;])299             pass300         self.c.send(b&#39;1&#39;)#发送到服务器,表示可以301         data=self.get_response()#得到服务器的回复302         if data["status_code"] ==259:#目录删除完成303             print(data[&#39;status_msg&#39;])304             return305         pass306 307     #上传方法308     def cmd_put(self,cmd_list,**kwargs):#上传方法309         if len(cmd_list) > 1:310             filename=cmd_list[1]#取文件名311             filename_dir=config.PUT_DIR+filename#拼接文件名路径312 313             if os.path.isfile(filename_dir):#是否是一个文件314                 filesize=os.stat(filename_dir).st_size#获取文件大小315                 #执行行为 名字,大小,是否316                 mag_dict={317                     "action":"put",318                     'filename':filename,319                     'size':filesize,320                     'overridden':True,321                     'md5':False322                 }323                 if self.cmd_md5_(cmd_list):#判断是否进行MD5324                     mag_dict['md5'] = True325                 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息326                 data=self.get_response()#得到服务器的回复327                 if data["status_code"] ==250:#磁盘空间不足328                     print(data['status_msg'])329                     print(mag_dict['size'])330                     return331                 if data["status_code"] ==249:#磁盘空间足够332                     print(data['status_msg'])333                     print('剩余空间',data['剩余空间'])334                     self.c.send(b'1')#发送到服务器,表示可以上传文件了335                     data=self.get_response()#得到服务器的回复336                     if data["status_code"] ==230:#断点续传337                         print(data['status_msg'])338                         print(data['文件大小'])339                         self.rat=data['文件大小']#文件指针位置340                         pass341                     elif data["status_code"] ==231:#非断点续传342                         print(data['status_msg'])343                         self.rat=0#文件指针位置344                         pass345                     f=open(filename_dir,'rb')#打开文件346                     f.seek(self.rat)#移动到位置347                     print(mag_dict['md5'])348                     self.c.send(b'1')#发送到服务器,表示可以上传文件了349                     if mag_dict['md5']==True:350                         md5_obj = hashlib.md5()#定义MD5351                         progress = self.show_pr(mag_dict['size']) #进度条 传入文件大小352                         progress.__next__()353                         while self.rat<filesize:354                             line=f.read(1024)355                             self.c.send(line)356                             try:357                                 progress.send(len(line))#传入当前数据大小358                             except StopIteration as e:359                                 print("100%")360                                 break361                             md5_obj.update(line)#计算MD5362 363                         else:364                             print(filename,&#39;发送完成!&#39;)365                             f.close()366                             md5_val = md5_obj.hexdigest()367                             md5_from_server = self.get_response()#服务端的MD5368                             if md5_from_server[&#39;status_code&#39;] == 248:369                                 if md5_from_server[&#39;md5&#39;] == md5_val:370                                     print("%s 文件一致性校验成功!" % filename)371                                     return372                     else:373                         progress = self.show_pr(mag_dict[&#39;size&#39;]) #进度条 传入文件大小374                         progress.__next__()375                         #for line in f:376                         while self.rat<filesize:377                             line=f.read(1024)378                             self.c.send(line)379                             try:380                                 progress.send(len(line))#传入当前数据大小381                             except StopIteration as e:382                                 print("100%")383                                 break384                             #print(line)385                         else:386                             print(filename,&#39;发送完成!&#39;)387                             f.close()388                             return389             else:390                 print(filename,&#39;文件不存在!&#39;)391 392     #下载方法393     def cmd_get(self,cmd_list,**kwargs):#下载方法394         #cmd_split= args[0].split()#指令解析395         # if len(cmd_list) == 1:396         #     print("没有输入文件名.")397         #     return398         #down_filename = cmd_list[1].split(&#39;/&#39;)[-1]#文件名399         down_filename=cmd_list[1]#取文件名400         file_path=&#39;%s/%s&#39;%(config.GET_DIR,down_filename)#拼接文件路径 用户down目录401         if os.path.isfile(file_path):#文件是否存402             filesize=os.stat(file_path).st_size#获取文件大小403             name_down=True404         else:405             filesize=0406             name_down=False407         mag_dict={408                     "action":"get",409                     &#39;filename&#39;:cmd_list[1],410                     &#39;name_down&#39;:name_down,411                     &#39;size&#39;:filesize412                 }413         if self.cmd_md5_(cmd_list):#判断是否进行MD5414             mag_dict[&#39;md5&#39;] = True415         self.c.send(json.dumps(mag_dict).encode())#发送416         self.c.send(b&#39;1&#39;)#发送到服务器,防粘包417 418         response = self.get_response()#服务器返回文件 的信息419         if response["status_code"] ==247:#如文件存在420             if name_down==True and response[&#39;file_size&#39;]==filesize:421                 print(&#39;文件已经下载完成&#39;)422                 self.c.send(b&#39;2&#39;)423                 return424             self.c.send(b&#39;1&#39;)#发送到服务器,表示可以接收文件了425             #if name_down:426             received_size = filesize#当前接收的数据大小427             #else:428             #received_size = 0#当前接收的数据大小429 430             file_obj = open(file_path,"ab")#打开文件431             if self.cmd_md5_(cmd_list):432                 md5_obj = hashlib.md5()433                 progress = self.show_pr(response[&#39;file_size&#39;]) #进度条 传入文件大小434                 progress.__next__()435                 while received_size< response[&#39;file_size&#39;]:436                     if response[&#39;file_size&#39;] - received_size>1024:#表示接收不止一次437                         size=1024438                     else:#最后一次439                         size=response['file_size'] - received_size440                         #print('最后一个大小',size)441                     data= self.c.recv(size)#接收数据442 443                     try:444                         progress.send(len(data))#传入当前数据大小445                     except StopIteration as e:446                         print("100%")447                     received_size+=len(data)#接收数据大小累加448                     file_obj.write(data)#写入文件449                     md5_obj.update(data)#进行MD5验证450                 else:451                     print("下载完成".center(60,'-'))452                     file_obj.close()453                     md5_val = md5_obj.hexdigest()#获取MD5454                     #print(md5_val)455                     md5_from_server = self.get_response()#服务端的MD5456                     #print(md5_from_server['md5'])457                     if md5_from_server['status_code'] == 248:458                         if md5_from_server['md5'] == md5_val:459                             print("%s 文件一致性校验成功!" % down_filename)460                     pass461             else:462                 progress = self.show_pr(response['file_size']) #进度条 传入文件大小463                 progress.__next__()464                 while received_size< response[&#39;file_size&#39;]:465                     if response[&#39;file_size&#39;] - received_size>1024:#表示接收不止一次466                         size=1024467                     else:#最后一次468                         size=response['file_size'] - received_size469                         #print('最后一个大小',size)470                     data= self.c.recv(size)#接收数据471 472                     try:473                       progress.send(len(data))#传入当前数据大小474                     except StopIteration as e:475                       print("100%")476                     received_size+=len(data)#接收数据大小累加477                     file_obj.write(data)#写入文件478                     pass479 480                 else:481                     print("下载完成".center(60,'-'))482                     file_obj.close()483                     pass484             self.c.send(b'1')#发送到服务器,表示可以接收文件了485 486 if __name__=='__main__':487 488     c=FTPClient()489     c.inter()
View Code
|           |- - -cfg/#配置目录
|           |      |- - -__init__.py
|           |      |- - -config.py#配置文件
 1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4  5 import os ,sys 6 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 7 sys.path.append(BASE_DIR)#增加环境变量 8 #print(BASE_DIR) 9 10 PUT_DIR=BASE_DIR+'\putfile\\'#定义用户上传目录文件路径变量11 GET_DIR=BASE_DIR+'\down\\'#定义用户下载目录文件路径变量12 HELP='help'13 CMD_LIST=['ls','pwd','info','help']
View Code
|- - -ftp_server/#服务端程序目录
|           |- - -__init__.py
|           |- - -bin/#启动目录
|           |      |- - -__init__.py
|           |      |- - -start.py#服务端视图启动
 1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4 import socket,os,json 5 import os ,sys 6 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 7 sys.path.append(BASE_DIR)#增加环境变量 8  9 from core import main10 11 if __name__ == '__main__':12 13     main.ArvgHandler()
View Code
|           |      |- - -user_reg.py#用户注册启动
 1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4  5 import configparser 6 import os ,sys 7 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 8 sys.path.append(BASE_DIR)#增加环境变量 9 from cfg import config10 #修改个信息 磁盘大小11 def set_info(name,pwd,size):12     config_info=configparser.ConfigParser()#读数据13     config_info.read(config.AUTH_FILE)#读文件 用户名密码14     #print(config_info.options(name))15     config_info[name]={}16     config_info.set(name,config.PWD,pwd)#密码17     config_info.set(name,config.QUOTATION,size)#磁盘信息18     config_info.write(open(config.AUTH_FILE,'w'))#写入文件19     file_path='%s/%s'%(config.USER_HOME,name)#拼接目录路径20     os.mkdir(file_path)#创建目录21     print('创建完成'.center(60,'='))22     print('用户名:[%s]\n密码:[%s]\n磁盘空间:[%s]'%(name,pwd,size))23 24 if __name__ == '__main__':25     name=input('name:')26     pwd=input('pwd:')27     size=input('size:')28     set_info(name,pwd,size)
View Code
|           |      |- - -userpwd.cfg#用户信息文件
 1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4  5 import os ,sys 6 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 7 sys.path.append(BASE_DIR)#增加环境变量 8 #HOME_PATH = os.path.join(BASE_DIR, "home") 9 10 11 12 #USER_DIR='%s\\data\\'%BASE_DIR#定义用户数据目录文件路径变量13 #USER_DIR='%s/data'%BASE_DIR#定义用户数据目录文件路径变量14 #USER_HOME='%s\\home\\'%BASE_DIR#定义用户家目录文件路径变量15 USER_HOME='%s/home'%BASE_DIR#定义用户家目录文件路径变量16 #LOG_DIR='%s\\log\\'%BASE_DIR#日志目录17 USER_LOG='%s/log/user_log.log'%BASE_DIR#日志登陆文件18 USER_OPERT='%s/log/user_opert.log'%BASE_DIR#日志操作文件19 20 LOG_LEVEL='DEBUG'#日志级别21 22 AUTH_FILE='%s/cfg/userpwd.cfg'%BASE_DIR#用户名密码文件23 HOST='0.0.0.0'# IP24 PORT=9500#端口25 QUOTATION='Quotation'#磁盘空间26 PWD='PWD'#密码
View Code
|           |- - -core/#服务端主要文件目录
|           |      |- - -__init__.py
|           |      |- - -ftp_server.py#服务端主要逻辑 类
  1 #!usr/bin/env python  2 #-*-coding:utf-8-*-  3 # Author calmyan  4 import socketserver,os,json,pickle,configparser,time  5 time_format='%Y%m%d%H%M%S'#定义时间格式  6 times=time.strftime(time_format)#定义时间  7   8 STATUS_CODE={  9     230:'文件断点继传', 10     231:'新文件', 11     240:'格式出错,格式:{"action":"get","filename":"filename","size":100}', 12     241:'指令错误', 13     242:'用户名或密码为空', 14     243:'用户或密码出错', 15     244:'用户密码通过校验', 16     245:'文件不存在或不是文件', 17     246:'服务器上该文件不存在', 18     247:'准备发送文件,请接收', 19     248:'md5', 20     249:'准备接收文件,请上传', 21     250:'磁盘空间不够', 22     251:'当前已经为主目录', 23     252:'目录正在切换', 24     253:'正在查看路径', 25     254:'准备删除文件', 26     255:'删除文件完成', 27     256:'目录不存在', 28     257:'目录已经存在', 29     258:'目录创建完成', 30     259:'目录删除完成', 31     260:'目录不是空的', 32 } 33 import os ,sys,hashlib 34 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 35 sys.path.append(BASE_DIR)#增加环境变量 36 from cfg import config 37 from core.logs import log_log 38 from core.logs import user_opert 39  40  41 class MyTCPHandler (socketserver.BaseRequestHandler):# 42  43     def setup(self): 44        print('监听中。。。') 45     #'''用户名与密码是否为空''' 46     def cmd_auth(self,*args,**kwargs):#用户校验 47         '''用户名与密码是否为空''' 48         data=args[0]#获取 传来的数据 49         if data.get('username') is None or data.get('password') is None:#如果用户名或密码为空 50             self.send_mge(242)#发送错误码 51         name=data.get('username')#用户名 52         pwd=data.get('password')#密码 53         print(name,pwd) 54         user=self.authusername(name,pwd)#用户名与密码的校验 获名用户名 55         if user is None:#用户名不存在 56             self.send_mge(243) 57         else: 58             self.user=name#保存用户名 59             self.home_dir='%s/%s'%(config.USER_HOME,self.user)#拼接 用户home目录路径 用户根目录 60             self.user_home_dir=self.home_dir#当前所在目录 61             # self.user_dir=self.user_home_dir.split('/')[-1]#当前所在目录 相对 62             self.dir_join()#进行目录拼接 63             self.send_mge(244,data={'dir':self.user_dir})#相对 目录 64  65     #目录拼接 66     def dir_join(self,*args,**kwargs): 67         self.user_dir=self.user_home_dir.split(self.home_dir)[-1]+'/'#当前所在目录 相对 68         print(self.user_dir) 69  70     #'''用户名与密码的校验'' 71     def authusername(self,name,pwd): 72         '''用户名与密码的校验''' 73         config_info=configparser.ConfigParser()#读数据 74         config_info.read(config.AUTH_FILE)#读文件 用户名密码 75         if name in config_info.sections():#用户名存 76             password=config_info[name]['PWD'] 77             if password==pwd:#密码正确 78                 print('通过校验!') 79                 config_info[name]['USERname']=name#名字的新字段 80                 info_str='用户[%s],成功登陆'%name 81                 self.log_log.warning(info_str)#记录日志 82                 #log_log(info_str) 83                 return config_info[name] 84             else: 85                 info_str='用户[%s],登陆错误'%name 86                 #log_log(info_str) 87                 self.log_log.warning(info_str)#记录日志 88                 return 0 89  90     #判断文件 是否存在 91     def file_name(self,file_path): 92         if os.path.isfile(file_path):#文件是否存 93             return  True 94         else: 95             return False 96  97     #判断目录是否存在 98     def file_dir(self,file_path): 99         if os.path.isdir(file_path):#目录是否存100             return  True101         else:102             return False103 104     #删除文件105     def cmd_rm(self,*args,**kwargs):106         cmd_dict=args[0]#获取字典107         action=cmd_dict["action"]108         filename =cmd_dict['filename']#文件名109         file_path='%s/%s'%(self.user_home_dir,filename)#拼接文件路径110         if not self.file_name(file_path):111             self.send_mge(245)#文件不存在112             return113         else:114             user_size=self.disk_size()#获取磁盘信息115             self.send_mge(254,data={'剩余空间':user_size})#准备删除文件116             file_size=os.path.getsize(file_path)#获取文件大小117             pass118         self.request.recv(1) #客户端确认 防粘包119         os.remove(file_path)120         new_size=float((float(user_size)+float(file_size))/1024000)#空间大小增加121         self.set_info(str(new_size))#传入新大小122         self.send_mge(255,data={'剩余空间':new_size})#删除文件完成123         info_str=self.log_str('删除文件')#生成日志信息124         self.user_opert.critical(info_str)#记录日志125         return126 127     #创建目录128     def cmd_mkdir(self,*args,**kwargs):129         cmd_dict=args[0]#获取字典130         action=cmd_dict["action"]131         filename =cmd_dict['filename']#目录名132         file_path='%s/%s'%(self.user_home_dir,filename)#拼接目录路径133         if self.file_dir(file_path):134             self.send_mge(257)#目录已经 存在135             return136         else:137             self.send_mge(256,data={'目录':'创建中...'})#目录创建中138             self.request.recv(1) #客户端确认 防粘包139             os.mkdir(file_path)#创建目录140             self.send_mge(258)#目录完成141             info_str=self.log_str('创建目录')#生成日志信息142             self.user_opert.critical(info_str)#记录日志143             return144 145     #删除目录146     def cmd_rmdir(self,*args,**kwargs):147         cmd_dict=args[0]#获取字典148         action=cmd_dict["action"]149         filename =cmd_dict['filename']#目录名150         file_path='%s/%s'%(self.user_home_dir,filename)#拼接目录路径151         if not self.file_dir(file_path):152             self.send_mge(256)#目录不存在153             return154         elif os.listdir(file_path):155             self.send_mge(260,data={'目录':'无法删除'})#目录不是空的156             return157         else:158             self.send_mge(257,data={'目录':'删除中...'})#目录创建中159             self.request.recv(1) #客户端确认 防粘包160             os.rmdir(file_path)#删除目录161             self.send_mge(259)#目录删除完成162             info_str=self.log_str('删除目录')#生成日志信息163             self.user_opert.critical(info_str)#记录日志164             return165 166     #磁盘空间大小167     def disk_size(self):168         attr_list=self.user_info()#调用个人信息169         put_size=attr_list[1]#取得磁盘信息170         user_size=float(put_size)*1024000#字节171         return user_size172 173     #'''客户端上传文件 '''174     def cmd_put(self,*args,**kwargs):175         '''客户端上传文件 '''176         cmd_dict=args[0]#获取字典177         filename =cmd_dict['filename']#文件名178         file_size= cmd_dict['size']#文件大小179         #user_home_dir='%s/%s'%(config.USER_HOME,self.user)#拼接 用户home目录路径180         file_path='%s/%s'%(self.user_home_dir,filename)#拼接文件路径181         user_size=self.disk_size()#取得磁盘信息182         if float(file_size)>float(user_size):#空间不足183             self.send_mge(250,data={'剩余空间':user_size})184             return185         self.send_mge(249,data={'剩余空间':user_size})#发送一个确认186         self.request.recv(1) #客户端确认 防粘包187         if self.file_name(file_path):#判断文件名是否存在,188             s_file_size=os.path.getsize(file_path)##获取服务器上的文件大小189             if file_size>s_file_size:#如果服务器上的文件小于要上传的文件进190                 tmp_file_size=os.stat(file_path).st_size#计算临时文件大小191                 reversed_size=tmp_file_size#接收到数据大小192                 self.send_mge(230,data={'文件大小':reversed_size})#发送临时文件大小193                 pass194             else:# file_size==s_file_size:#如果大小一样195                 file_path=file_path+'_'+times#命名新的文件 名196                 reversed_size=0#接收到数据大小197                 self.send_mge(231)#发送 不是断点文件198                 pass199         else:200             reversed_size=0#接收到数据大小201             self.send_mge(231)#发送 不是断点文件202             pass203 204         f=open(file_path,'ab')205         self.request.recv(1) #客户端确认 防粘包206         if cmd_dict['md5']:#是否有 md5207             md5_obj = hashlib.md5() #   进行MD5208             while reversed_size< int(file_size):#接收小于文件 大小209                 if int(file_size) - reversed_size>1024:#表示接收不止一次210                     size=1024211                 else:#最后一次212                     size=int(file_size) - reversed_size213                     #print('最后一个大小',size)214                 data= self.request.recv(size)#接收数据215                 md5_obj.update(data)216                 reversed_size+=len(data)#接收数据大小累加217                 f.write(data)#写入文件218             else:219                 f.close()220                 print('[%s]文件上传完毕'.center(60,'-')%filename)221                 md5_val = md5_obj.hexdigest()#得出MD5222                 print(md5_val)223                 self.send_mge(248,{'md5':md5_val})#发送md5给客户端224         else:225             while reversed_size< int(file_size):#接收小于文件 大小226                 if int(file_size) - reversed_size>1024:#表示接收不止一次227                     size=1024228                 else:#最后一次229                     size=int(file_size) - reversed_size230                     #print('最后一个大小',size)231                 data= self.request.recv(size)#接收数据232                 reversed_size+=len(data)#接收数据大小累加233                 f.write(data)#写入文件234             else:235                 print('[%s]文件上传完毕'%filename.center(60,'-'))236                 f.close()237         new_size=float((float(user_size)-float(file_size))/1024000)#扣除空间大小238         self.set_info(str(new_size))#传入新大小239         info_str=self.log_str('文件上传')#生成日志信息240         self.user_opert.critical(info_str)#记录日志241         return242 243     #用户下载文件244     def cmd_get(self,*args,**kwargs):#用户下载文件245         ''' 用户下载文件'''246         data=args[0]247         print(data)248         if data.get('filename') is None:#判断文件名不为空249             self.send_mge(245)250             return251 252         self.request.recv(1) #客户端确认 防粘包253         file_path='%s/%s'%(self.user_home_dir,data.get('filename'))#拼接文件路径 用户文件路径254         if os.path.isfile(file_path):#判断文件是否存在255             file_obj=open(file_path,'rb')#打开文件句柄\256             file_size=os.path.getsize(file_path)#获取文件大小257             if data['name_down']:258                 send_size=data['size']#已经发送数据大小259                 #self.send_mge(230,data={'文件大小':file_size})#断点续传260             else:261                 send_size=0262                 #self.send_mge(231)#非断点续传263             #self.request.recv(1) #客户端确认 防粘包264             file_obj.seek(send_size)#移动到265             self.send_mge(247,data={'file_size':file_size})#发送相关信息266             attr=self.request.recv(1024) #客户端确认 防粘包267             if attr.decode()=='2':return #如果返回是268             if data.get('md5'):269                 md5_obj = hashlib.md5()270                 while send_size<file_size:271                     line=file_obj.read(1024)272                 #for line in file_obj:273                     self.request.send(line)274                     md5_obj.update(line)275                 else:276                     file_obj.close()277                     md5_val = md5_obj.hexdigest()278                     self.send_mge(248,{'md5':md5_val})279                     print("发送完毕.")280             else:281                 while send_size<file_size:282                     line=file_obj.read(1024)283                 #for line in file_obj:284                     self.request.send(line)285                 else:286                     file_obj.close()287                     print("发送完毕.")288             self.request.recv(1) #客户端确认 防粘包289             info_str=self.log_str('下载文件')#生成日志信息290             #user_opert(info_str)#记录日志291             self.user_opert.critical(info_str)#记录日志292             return293 294     #切换目录295     def cmd_cd(self,cmd_dict,*args,**kwargs):296         '''切换目录'''297         cmd_attr=cmd_dict['actionname']#获取命令298         if cmd_attr=='..' or cmd_attr=='../..':299             if (self.home_dir)==self.user_home_dir:300                 self.send_mge(251)301                 return302             elif cmd_attr=='../..':303                 self.send_mge(252)#可以切换到上级目录304                 self.user_home_dir=self.home_dir#绝对目录 = home305                 self.user_dir='/'306                 clinet_ack=self.request.recv(1024)#为了去粘包307                 self.request.send(self.user_dir.encode())#返回相对目录308                 return309             else:310                 self.send_mge(252)#可以切换到上级目录311                 print(self.user_home_dir)#绝对目录312                 print(os.path.dirname(self.user_home_dir))#父级目录313                 self.user_home_dir=os.path.dirname(self.user_home_dir)#父级目录314                 self.dir_join()#目录拼接切换315                 clinet_ack=self.request.recv(1024)#为了去粘包316                 self.request.send(self.user_dir.encode())#返回相对目录317                 return318 319         elif os.path.isdir(self.user_home_dir+'/'+cmd_attr):#如果目录存在320             self.send_mge(252)321             self.user_home_dir=self.user_home_dir+'/'+cmd_attr#目录拼接322             self.dir_join()#相对目录拼接切换323             clinet_ack=self.request.recv(1024)#为了去粘包324             print(clinet_ack.decode())325             self.request.send(self.user_dir.encode())326             return327         else:328             self.send_mge(256)#目录不存在329             return330 331     #查看目录路径 CD332     def cmd_pwd(self,cmd_dict):333         self.request.send(str(len(self.user_dir.encode('utf-8'))).encode('utf-8'))#发送大小334         clinet_ack=self.request.recv(1024)#为了去粘包335         self.request.send(self.user_dir.encode())#发送相对路径336         info_str=self.log_str('查看目录路径')#生成日志信息337         #logger.warning338         self.user_opert.critical(info_str)#记录日志339         return340 341     #修改个信息 磁盘大小342     def set_info(self,new_size):343         config_info=configparser.ConfigParser()#读数据344         config_info.read(config.AUTH_FILE)#读文件 用户名密码345         print(config_info.options(self.user))346         config_info.set(self.user,config.QUOTATION,new_size)347         config_info.write(open(config.AUTH_FILE,'w'))348 349     #读取个人信息350     def user_info(self):351         config_info=configparser.ConfigParser()#读数据352         config_info.read(config.AUTH_FILE)#读文件 用户名密码353         print(config_info.options(self.user))354         pwds=config_info[self.user][config.PWD]#密码355         Quotation=config_info[self.user][config.QUOTATION]#磁盘配额 剩余356         user_info={}357         user_info['用户名']=self.user358         user_info['密码']=pwds359         user_info['剩余磁盘配额']=Quotation360         return user_info,Quotation361 362     #查看用户信息363     def cmd_info(self,*args,**kwargs):364         attr=self.user_info()365         info_dict=attr[0]366         self.request.send(str(len(json.dumps(info_dict))).encode('utf-8'))#367         clinet_ack=self.request.recv(1024)#为了去粘包368         self.request.send(json.dumps(info_dict).encode('utf-8'))#发送指令369         info_str=self.log_str('查看用户信息')#生成日志信息370         self.user_opert.critical(info_str)#记录日志371         return372 373     #日志信息生成374     def log_str(self,msg,**kwargs):375         info_str='用户[%s]进行了[%s]操作'%(self.user,msg)376         return info_str377 378 379     #目录查看380     def cmd_ls(self,*args,**kwargs):381         data=os.listdir(self.user_home_dir)#查看目录文件382         print(data)383         datas=json.dumps(data)#转成json格式384         self.request.send(str(len(datas.encode('utf-8'))).encode('utf-8'))#发送大小385         clinet_ack=self.request.recv(1024)#为了去粘包386         self.request.send(datas.encode('utf-8'))#发送指令387         info_str=self.log_str('目录查看')#生成日志信息388         self.user_opert.critical(info_str)#记录日志389         return390     ##单个命令391     def cmd_compr(self,cmd_dict,**kwargs):392         attr=cmd_dict['actionname']#赋于变量393         if hasattr(self,'cmd_%s'%attr):#是否存在394             func=getattr(self,'cmd_%s'%attr)#调用395             func(cmd_dict)396             return397         else:398             print('没有相关命令!')399             self.send_mge(241)400             return401 402     #'''发送信息码给客户端'''403     def send_mge(self,status_code,data=None):404         '''发送信息码给客户端'''405         mge={'status_code':status_code,'status_msg':STATUS_CODE[status_code]}#消息406         if data:#不为空407             mge.update(data)#提示码进行更新408         print(mge)409         self.request.send(json.dumps(mge).encode())#发送给客户端410 411     #重写handle方法412     def handle(self):#重写handle方法413         while True:414             #try:415             self.data=self.request.recv(1024).strip()#接收数据416             print('ip:{}'.format(self.client_address[0]))#连接的ip417             print(self.data)418             self.log_log=log_log()#登陆日志419             self.user_opert=user_opert()#操作日志420             if not self.data:421                 print("[%s]客户端断开了!."%self.user)422                 info_str='用户[%s],退出'%self.user423 424                 break425             cmd_dict=json.loads(self.data.decode())#接收 数据426             if cmd_dict.get('action') is not None:#判断数据格式正确427                 action=cmd_dict['action']#文件 头428                 if hasattr(self,'cmd_%s'%action):#是否存在429                     func=getattr(self,'cmd_%s'%action)#调用430                     func(cmd_dict)431                 else:432                     print('没有相关命令!')433                     self.send_mge(241)434             else:435                 print('数据出错!')436                 self.send_mge(240)437             #except Exception as e:438              #  print('客户端断开了!',e)439               # break
View Code
|           |      |- - -logs.py#日志主要逻辑 类
 1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4 import os,logging,time 5 from cfg import config 6 LOG_LEVEL=config.LOG_LEVEL 7  8  9 def log_log():#登陆日志,传入内容10     logger=logging.getLogger('用户成功登陆日志')#设置日志模块11     logger.setLevel(logging.DEBUG)12     fh=logging.FileHandler(config.USER_LOG,encoding='utf-8')#写入文件13     fh.setLevel(config.LOG_LEVEL)#写入信息的级别14     fh_format=logging.Formatter('%(asctime)s %(message)s',datefmt='%m/%d/%Y %I:%M:%S %p')#日志格式15     fh.setFormatter(fh_format)#关联格式16     logger.addHandler(fh)#添加日志输出模式17     #logger.warning(info_str)18     return logger19 20 def user_opert():#用户操作日志,传入内容21     logger=logging.getLogger('用户操作日志')#设置日志模块22     logger.setLevel(logging.CRITICAL)23     fh=logging.FileHandler(config.USER_OPERT,encoding='utf-8')#写入文件24     fh.setLevel(config.LOG_LEVEL)#写入信息的级别25     fh_format=logging.Formatter('%(asctime)s %(message)s',datefmt='%m/%d/%Y %I:%M:%S %p')#日志格式26     fh.setFormatter(fh_format)#关联格式27     logger.addHandler(fh)#添加日志输出模式28     #logger.critical(info_str)29     return logger
View Code
|           |      |- - -main.py#服务端启动主程序
 1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4  5 import socketserver,os,json,pickle 6 import os ,sys 7 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 8 sys.path.append(BASE_DIR)#增加环境变量 9 from cfg import config10 11 12 from  core.ftp_server import  MyTCPHandler13 14 import optparse15 class ArvgHandler(object):16     def __init__(self):#   可  传入系统参数17         self.paresr=optparse.OptionParser()#启用模块18         #self.paresr.add_option('-s','--host',dest='host',help='服务绑定地址')19         #self.paresr.add_option('-s','--port',dest='host',help='服务端口')20         (options,args)=self.paresr.parse_args()#返回一个字典与列表的元组21 22         self.verufy_args(options,args)#进行校验23     def verufy_args(self,options,args):24         '''校验与调用'''25         if hasattr(self,args[0]):#反射判断参数26             func=getattr(self,args[0])#生成一个实例27             func()#开始调用28         else:29             self.paresr.print_help()#打印帮助文档30     def start(self):31         print('服务启动中....')32         s=socketserver.ThreadingTCPServer((config.HOST,config.PORT),MyTCPHandler)#实例化一个服务端对象33         s.serve_forever()#运行服务器34         print('服务关闭')
View Code

 

<br><br>
<br>

以上が高度な FTP サーバーの使用の概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。