ホームページ >バックエンド開発 >Python チュートリアル >ソケットを使用した FTP の実装に関するチュートリアル

ソケットを使用した FTP の実装に関するチュートリアル

巴扎黑
巴扎黑オリジナル
2017-07-18 13:48:272859ブラウズ
 
    1、用户加密认证
    2、允许同时多用户登录
    3、每个用户有自己的家目录 ,且只能访问自己的家目录
    4、对用户进行磁盘配额,每个用户的可用空间不同
    5、允许用户在ftp server上随意切换目录
    6、允许用户查看当前目录下文件
    7、允许上传和下载文件,保证文件一致性
    8、文件传输过程中显示进度条
    附加功能:支持文件的断点续传

python 3.5


  用socketserver实现FTP


  
    ConfigParser 是Python自带的模块, 用来读写配置文件,将用户信息以下边的格式存入account.py文件,读出后进行判断
[DEFAULT]

[alex]
Password = 123456Quotation = 100[jack]
Password = 123456Quotation = 100

 

    2、允许同时多用户登录
      从scokerserver 继承 socketserver.ThreadingTCPServer即可实现
    3、每个用户有自己的家目录 ,且只能访问自己的家目录
      将所有路径封装在home目录下
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

USER_HOME = '%s\home' % BASE_DIR
LOG_DIR = '%s\log' % BASE_DIR
LOG_LEVEL = 'DEBUG'

 

    4、允许用户在ftp server上随意切换目录
      用os模块改变工作目录
os.chdir(func_val)

 

    5、允许用户查看当前目录下文件
      用os模块的os.listdir,下为server端代码
    def _ls(self,*args,**kwargs):'''显示当前目录下的所有文件'''if os.getcwd() == '%s\\bin'% settings.BASE_DIR:
            user_home_dir = "%s\%s" % (settings.USER_HOME, self.user["Username"])
            self.request.send(json.dumps(os.listdir(user_home_dir)).encode())else:self.request.send(json.dumps(os.listdir()).encode())

 

    6、允许上传和下载文件,保证文件一致性
      判断上传和下载的文件在传输前后的大小,用以判断文件是否保持一致
  1 #server端  2 #  3     def _put(self,*args,**kwargs):  4         '''上传文件命令'''  5         data = args[0]  6   7         response = self.get_response()  8         if response["status_code"] == 257:  # ready to receive  9             self.request.send(b'1')  # send confirmation to server 10             user_home_dir = "%s\%s" % (settings.USER_HOME, self.user["Username"]) 11             base_filename = "%s\%s" % (user_home_dir, data.get('filename')) 12             received_size = 0 13             file_obj = open(base_filename, 'wb') 14             if data.get('md5'): 15                 md5_obj = hashlib.md5() 16                 while received_size 
get and put

 

    7、文件传输过程中显示进度条
      根据已传文件大小和总文件大小比对判断,输出符号
 1     def show_progress(self,total): 2         '''显示进度条 3            total: 文件大小''' 4         received_size = 0 5         current_percent = 0 6         while received_size  current_percent: 8                 print(">",end='',flush=True) 9                 current_percent = int((received_size / total) * 100)10             new_size = yield11             received_size += new_size
进度条

 

    附加功能:支持文件的断点续传
      将上次断点信息记录下来,也就是记录已传文件大小,再次启用时,将目标文件光标移到上次断点处,然后进行续传
 1     def _breakpoint(self,*args,**kwargs): 2         '''断点续传''' 3         data = args[0] 4         if data.get('filename') is None: 5             self.send_response(255) 6         user_home_dir = "%s\%s" % (settings.USER_HOME, self.user["Username"]) 7         file_abs_path = "%s\%s" % (user_home_dir, data.get('filename')) 8         print("file abs path", file_abs_path) 9         print(data.get('breakpoint'))10 11         if os.path.isfile(file_abs_path):12             file_obj = open(file_abs_path, 'rb')13             file_obj.seek(data.get('breakpoint'))14             file_size = os.path.getsize(file_abs_path)15             self.send_response(257, data={'file_size': file_size})16             self.request.recv(1)  # 等待客户端确认17 18             if data.get('md5'):19                 md5_obj = hashlib.md5()20                 for line in file_obj:21                     self.request.send(line)22                     md5_obj.update(line)23                 else:24                     file_obj.close()25                     md5_val = md5_obj.hexdigest()26                     self.send_response(258, {'md5': md5_val})27                     print("send file done...")28             else:29                 for line in file_obj:30                     self.request.send(line)31                 else:32                     file_obj.close()33                     print("send file done...")34         else:35             self.send_response(256)36         pass
breakpoint

   主要知识点:

    类的继承
    os模块的应用
    json模块的应用
    类方法的运用
    md5加密方法
    scoket链接
    scoketserver链接
    反射
    异常处理


小结:
          这个程序主要用socketserver实现了一台服务器链接多个客户端,并且进行信息交互,并且实现了几个简单的功能,如:get 文件下载、put 上传文件、cd 切换目录、ls 查看文件、breakpoint断点续传。

主要代码:
  1 #server端  2 #  3   4   5 import os,sys  6 import hashlib  7 import socket  8 import socketserver  9 import json 10 import configparser 11 from conf import settings 12  13  14 STATUS_CODE = { 15     250 : "Invalid cmd format,e.g:{'action':'get','filename':'test.py','size':344", 16     251 : "Invalid cmd", 17     252 : "Invalid auth data", 18     253 : "Wrong username or password..", 19     254 : "Passed authentication", 20     255 : "Filename doesn't provided", 21     256 : "File doesn't exist on server", 22     257 : "ready to send file", 23     258 : "md5 verification", 24     259 : "Directory has been switched" 25 } 26  27  28  29  30  31 class FTPHandler(socketserver.BaseRequestHandler): 32     '''定义request handler类,从BaseRequestHandler类继承''' 33     def handle(self): 34         ''' 35         获取服务器端的信息 36         如果传来cd命令,改变工作目录 37         ''' 38  39         while True: 40             self.data = self.request.recv(1024).strip() 41             if not self.data: 42                 print('Client closed..') 43                 break 44             data = json.loads(self.data.decode()) 45             if data.get('action') is not None: 46                 if hasattr(self,'_%s'%data.get('action')): 47                     func =getattr(self,'_%s'%data.get('action')) 48                     func_val = func(data) 49                     if data.get('action') == 'cd':     #cd 命令,改变工作目录 50                         os.chdir(func_val) 51                     else:pass 52                 else: 53                     print('Invalid cmd') 54                     self.send_response(251) 55             else: 56                 print('Invalid cmd format') 57                 self.send_response(250) 58  59  60     def send_response(self,status_code,data=None): 61         '''向客户端返回数据''' 62         response = {'status_code': status_code, 'status_msg':STATUS_CODE[status_code]} 63         if data: 64             response.update(data) 65         self.request.send(json.dumps(response).encode()) 66  67  68     def _auth(self,*args,**kwargs): 69         '''判断用户是否输入完整的用户名和密码 70            验证用户名和密码是否合法''' 71         data = args[0] 72         if data.get('username') is None or data.get('password') is None: 73             self.send_response(252) 74  75         user = self.authenticate(data.get('username'),data.get('password')) 76         if user is None: 77             self.send_response(253) 78         else: 79             print('passed authentication',user) 80             self.user = user 81             self.send_response(254) 82  83  84     def authenticate(self,username,password):       # 85         '''验证用户合法性,合法返回用户数据''' 86         config = configparser.ConfigParser() 87         config.read(settings.ACCOUNT_FILE) 88         if username in config.sections(): 89             _password = config[username]['Password'] 90             if _password == password: 91                 print('pass auth..',username) 92                 config[username]["Username"] = username 93                 return config[username] 94  95  96     def get_response(self): 97         '''接收客户端回复结果''' 98         data = self.request.recv(1024) 99         data = json.loads(data.decode())100         return data101 102 103     def show_progress(self,total):104         '''显示进度条105            total: 文件大小'''106         received_size = 0107         current_percent = 0108         while received_size  current_percent:110                 print(">",end='',flush=True)111                 current_percent = int((received_size / total) * 100)112             new_size = yield113             received_size += new_size114 115 116     def _put(self,*args,**kwargs):117         '''上传文件命令'''118         data = args[0]119 120         response = self.get_response()121         if response["status_code"] == 257:  # ready to receive122             self.request.send(b'1')  # send confirmation to server123             user_home_dir = "%s\%s" % (settings.USER_HOME, self.user["Username"])124             base_filename = "%s\%s" % (user_home_dir, data.get('filename'))125             received_size = 0126             file_obj = open(base_filename, 'wb')127             if data.get('md5'):128                 md5_obj = hashlib.md5()129                 while received_size 
server端
  1 #client端  2 #  3   4 import socket  5 import hashlib  6 import os,json  7 import optparse  8 import socketserver  9  10  11 STATUS_CODE = { 12     250 : "Invalid cmd format,e.g:{'action':'get','filename':'test.py','size':344", 13     251 : "Invalid cmd", 14     252 : "Invalid auth data", 15     253 : "Wrong username or password..", 16     254 : "Passed authentication", 17     255 : "Filename doesn't provided", 18     256 : "File doesn't exist on server", 19     257 : "ready to send file", 20     258 : "md5 verification", 21     259 : "Directory has been switched" 22 } 23  24  25  26  27 class FTPClient(object): 28     '''客户端''' 29     def __init__(self): 30         '''用户信息输入格式化 31             变量定义''' 32         parser = optparse.OptionParser() 33         parser.add_option('-s', '--server', dest='server', help ='ftp server ip_addr') 34         parser.add_option('-p','--port', type='int', dest='port', help='ftp server port') 35         parser.add_option('-U', '--username', dest='username', help='username') 36         parser.add_option('-P', '--Password', dest='password', help='password') 37         self.options, self.args = parser.parse_args() 38         self.verify_args(self.options,self.args) 39         self.make_connection() 40  41  42     def make_connection(self): 43         '''连接服务器''' 44         self.sock = socket.socket() 45         self.sock.connect((self.options.server,self.options.port)) 46  47  48  49     def verify_args(self,options,args): 50         '''校验参数合法性''' 51         if options.username and options.password: 52             pass 53         elif options.username is None and options.password is None: 54             pass 55         else: 56             if options.username is None or options.password is None: 57                 print('Error:username and password must be provided together..') 58         if options.server and options.port: 59             if options.port > 0 and options.port  current_percent:108                 print(">",end='',flush=True)109                 current_percent = int((received_size / total) * 100)110             new_size = yield111             received_size += new_size112 113 114     def send_response(self,status_code,data=None):115         '''向服务器端返回数据'''116         response = {'status_code': status_code, 'status_msg':STATUS_CODE[status_code]}117         if data:118             response.update(data)119         self.sock.send(json.dumps(response).encode())120 121 122     def get_response(self):123         '''得到服务器端回复结果'''124         data = self.sock.recv(1024)125         data = json.loads(data.decode())126         return data127 128 129     def _get(self,cmd_list):130         '''下载文件'''131         print("get--",cmd_list)132         if len(cmd_list) == 1:133             print("no filename follows...")134             return135         data_header = {136             'action':'get',137             'filename':cmd_list[1]138         }139         if self.__md5_required(cmd_list):140             data_header['md5'] = True141 142         self.sock.send(json.dumps(data_header).encode())143         response = self.get_response()144         print(response)145         try:146             if response["status_code"] == 257:#ready to receive147                 self.sock.send(b'1') #send confirmation to server148                 base_filename = cmd_list[1].split('/')[-1]149                 received_size = 0150                 file_obj = open(base_filename,'wb')151                 if self.__md5_required(cmd_list):152                     md5_obj = hashlib.md5()153                     progress = self.show_progress(response['file_size']) #generator154                     progress.__next__()155                     while received_size " % cmd_list[1],end='')279 280 281     def _breakpoint(self,*args,**kwargs):282         '''断点续传'''283         with open('data\\breakpoint', 'rb') as br_po:284             data_header = json.loads(br_po.read().decode())285             br_po.close()286         if data_header:287             print(data_header)288             self.sock.send(json.dumps(data_header).encode())289             response = self.get_response()290             try:291                 if response["status_code"] == 257:  # ready to receive292                     self.sock.send(b'1')  # send confirmation to server293                     base_filename = data_header['filename'].split('/')[-1]294                     received_size = data_header['breakpoint']295                     file_obj = open(base_filename, 'ab')296                     file_obj.seek(data_header['breakpoint'])297                     if self.__md5_required(data_header):298                         md5_obj = hashlib.md5()299                         progress = self.show_progress(response['file_size'])  # generator300                         progress.__next__()301                         while received_size 
client端

TCP サーバーを作成する
アイデア: ソケットを作成します---ソケットをローカル アドレスにバインドします---接続をリッスンします---無限ループで TCP クライアント リクエストに応答します (クライアント ソケットは受信後に返されます) ) )---TCP データを受け入れる---TCP データを送信---サブソケットを閉じる---サーバーソケットを閉じる
fromソケット import *
from time import ctime

HOST = '' # バインドできることを意味します 任意有効なアドレス
PORT = 2157
BUFSIZ = 1024 # キーワード、サイズ 1k
ADR= (HOST, PORT)

tcpSerSock =ソケット(AF_INET, SOCK_STREAM) # ネットワークベースの TCP サーバーソケットバインド
tcpSerSock を作成します。
tcpSerSock.listen(5) # 同時に 5 つの接続のみを許可します

while True: # listen するための無限ループ
Print '接続を待っています....'
tcpCliSock, addr = tcpSerSock.accept() # Receive ,クライアントソケットとクライアントアドレスを保存します
print '...connected from:', addr
while True:
data = tcpCliSock.recv(BUFSIZ) # データでない場合はバッファデータを読み取ります
アイデア: を作成します。クライアントソケット---接続---データを送信(送信が空の場合は終了)---バッファからデータを受信(受信が失敗した場合は終了)---ソケットインポートからクライアントソケット
を閉じる *

HOST = 'localhost'
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpCliSock =ソケット(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)

True:
while data = raw_input('>' )
data でない場合:
Break
tcpCliSock.send(data)
data = tcpCliSock.recv(BUFSIZ)
data でない場合:
データを印刷する
tcpCliSock.close()

UDP サーバーを作成する
アイデア: ソケットを作成する---ソケットをローカルアドレスにバインドします--データを受信します---データを送信
from ソケット import *
from time import ctime

HOST = '' # は、任意の有効なアドレスをバインドできることを意味します
PORT = 2157
BUFSIZ = 1024 # キーワード、サイズは 1k です
ADR= (HOST, PORT)

udpSerSock =socket(AF_INET, SOCK_DGRAM)
udpSerSock.bind(ADR)

while True ; ...から受信し、次のように返されます:', addr
udpSerSock .close()

UDp クライアントを作成します
アイデア: クライアントソケットを作成します --- データを送信 --- データを受信します
from ソケット import *
from time import ctime

HOST = 'localhost' # 任意の有効なアドレスが可能であることを示しますバインドされる
PORT = 2157
BUFSIZ = 1024 # キーワード、サイズは1k
ADDR= (HOST, PORT)

udpCliSock =ソケット(AF_INET, SOCK_DGRAM)

while True:
data = raw_input('>')
データでない場合:
Break
udpCliSock.sendto(data, ADDR)
data, ADDR = udpCliSock.recvfrom(BUFSIZ)
データでない場合:
Break
print data
udpCliSock.close()

以上がソケットを使用した FTP の実装に関するチュートリアルの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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