Python:socketから、最も基本的な機能を持つFTPサーバ(ソースコード付き)を構築

12591 ワード

要旨:これは対応するクライアントがwindowsリソースマネージャの簡単なFTPサーバーで、アップロード、ダウンロード、フォルダの新規作成、削除、名前変更をサポートし、ユーザーをサポートしません.
题外话:私达のネットの设计の実験の要求するクライアント、题目は间违って、サービス端を书くと思って、结局苦労して大半を书いた后でやっと知っていて、后悔してもう间に合わない......思い切って先にこれを完成するしかありません.その时、これを书く时、ネット上でどのように教えているのか(目测大神达はすべて简単だと思っています......)、ソースコードは少なくありませんが、自分のレベルが低すぎて、ソースコードはほとんど読めません(これは本当に自谦ではありません.pyftpdlibを読む时、それは天书だと思っています)、自分でソースコードを研究しながら振り回すしかありませんでした.
最后に250行を作って、基本的な机能も実现しにくくありませんが、レベルが限られているのはどんな异常処理、どんなライブラリ、全然できません.
同じように1つの機能を完成して、具体的にはいろいろな実現方法があります.いわゆる向上はある機能を実現するだけでなく、より速く実現すること、より成熟したコードスタイル、より効果的な実現構想、既存のライブラリとアーキテクチャをより合理的に利用することも含まれています.これらこそ達人と私たちの違いです.
FTPプロトコルの概要
FTPプロトコル、File Transfer Protocolは、ファイル転送に関するプロトコルであり、ファイル転送(アップロード、ダウンロード)のほか、サーバで簡単なファイル修正操作、例えば削除、名前変更、フォルダの新規作成をサポートする.お客様がサーバ上のファイルにアクセスするのは、ローカルファイルにアクセスするのと同じです.同時にユーザーメカニズムをサポートし、異なるユーザーに異なる権限を与えることができます.
基本プロセスとフレームワーク
FTPサーバにおいては、マルチユーザ登録や、ユーザ操作が伝送データによって中断されないようにするために用いられるマルチスレッド機構を下図に示す
PORTモードとPASVモードについて.
この2つのモードは、データの転送時に新しいポートを開く約束です.
PORTモードでは、クライアントがポートを開き、制御接続でポート番号、サーバ接続をサーバに通知することを約束します.
PASVモード、すなわち本明細書で実現されるモードである.
1、制御接続上、クライアントはPASVコマンドをサーバーに送る
2、サーバーは一つのポートを開き、傍受し、そのポート番号をクライアントに返す
3、クライアントはこのポートに接続する
LISTコマンドを例に、完全なプロセス
クライアント
サービス側
21番ポートの傍受
接続要求を開始し、ユーザー名を入力
——》
《——
331を返します.ユーザー名は正しいです.
パスワードの入力
——》
《——
230を返します.パスワードは正しいです.
PASVコマンド
——》
《——
227、新しいポートを開き、ポート番号Kに戻る
このときクライアントとサーバのポートKとのデータ接続が確立される
LSITコマンド
——》
《——
125データ接続がオン
《——
Kポートから、対応するディレクトリファイルのリストを返します
《——
226、データ転送完了
.......
...............
....................
Socket、thread、os、timeの簡単な紹介と使用
Socket:
このプログラムで使用するsocket機能は、socketの作成、傍受、接続の受信、接続ファイル化、受信データの送信、接続の停止など、簡単です.主要部品の紹介部分を詳しく見たり、他の資料を参考にしたりして、ここではあまり言いません.
Thread:
このプログラムではstart_にのみ使用しますnew_thread(func,(args))は、新しいスレッドで実行したい関数と、その関数に入力したいパラメータの2つのパラメータを受信します.
これは軽量レベルのスレッドを開く方法で、より良い方法はスレッドクラスを継承し、1つのクラスをスレッドにし、自分のリソースを持ってアクセスすることができます.
Os:
本文で主に触れたのはos.chdir(),os.getcwd(),os.mkdir()など、ディレクトリを変更する操作について
Time:
pyftpdlibでの時間の処理を参考にしましたが、多くはありませんので、タイムライブラリを紹介する記事を参考に詳しく見ることをお勧めします.
実現構想.
まず、21ポートにバインドされたプライマリsocketを作成します.
その後、whileループでユーザーの接続を受け入れ、接続を受け入れるたびに新しいスレッドを開いてユーザーと対話します.
スレッドではまたwhileループでユーザコマンドを受信し,1つのコマンドを受け取るたびにユーザのコマンドと伝達パラメータを解析し,対応するhandler関数処理を呼び出す.
pasv操作に遭遇した場合はhandler_pasで新しいsocketを確立し、クライアントに返します.
大体の考えはこうだ.コード部分を詳しく見る.
コードセクション
#-*-coding:utf8-*-
import time
import socket,sys
from thread import *
import os
__author__ = 'ksp'


class FTPs():
    def __init__(self,localip='127.0.0.1',path='c:/'):#    ip   socket,       
        self.s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
        #          
        self.PSIZE=4096
        self.lip=localip
        #   FTP    ,21
        try:
            self.s.bind((self.lip,21))
        except:
            print 'ip error'
            raise
        self.path=path
        #             
        try:
            os.chdir(path)
        except ValueError:
            print 'path Invalid'
            raise
        self.close=False
        #            
        self._months_map = {1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun', 7:'Jul',
                            8:'Aug', 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'}
    def Run(self):#   ,             
        self.s.listen(1)
        #self.s.settimeout(60)
        print 'socket created server running...'
        #       socket    60 ,       
        socket.setdefaulttimeout(60)
        while 1:
            try:
                conn,addr=self.s.accept()
            #     
            except KeyboardInterrupt:
                self.close=True
                conn.close()
                self.s.close()
                print 'KeyboardInterrupt'
                break
            print 'connect with '+addr[0]+':'+str(addr[1])
            #             
            start_new_thread(self.cftpcmd,(conn,))
    def cftpcmd(self,cnn,):#         
        cpath=self.path.replace('\\','//')
        os.chdir(cpath)
        #     ,          socket
        cf=cnn.makefile('rw',0)
        cf.write('220 ready for transfer\r
') print 'thread open and connected...' # , print cf.readline().strip() cf.write('331 name ok\r
') print cf.readline().strip() cf.write('230 log in ok\r
') # dsocket=None # selfclose=False while 1: # try: gets=cf.readline().strip() if self.close or selfclose: break except: print '\r
timeout exit thread' cnn.close() break print 'receive command: "%s"'% gets cmd=gets[:3].lower() args=gets[3:] # , 。 eval try: if cmd in ['lis',]: ev='self.handle_%s(dsocket,cf)' % (cmd) print ev eval(ev) elif cmd=='qui': selfclose=self.handle_qui(cf) elif cmd=='ret': cf.write('125 dataconnection open\r
') start_new_thread(self.handle_ret,(args,cf,dsocket)) elif cmd=='sto': cf.write('150 file status ok\r
') start_new_thread(self.handle_sto,(args,cf,dsocket)) elif cmd=='pas': ev='self.handle_%s("%s",cf)' % (cmd,args) print ev dsocket,psocket=eval(ev) elif cmd=='rnf': cf.write('350 ready for destination name\r
') oldename=args[2:] elif cmd=='rnt': cf.write('250 rename ok\r
') newname=args[2:] try: os.rename(oldename,newname) except: print 'rename error' elif hasattr(self,'handle_%s'% cmd): ev='self.handle_%s("%s",cf)' % (cmd,args) print ev eval(ev) else: cf.write('501 Invaild command\r
') print 'no handler for this command..'+'self.handle_%s("%s",cf)' % (cmd,args) except: print 'error...closing thread and conn' if dsocket != None: dsocket.close() psocket.close() cf.write('221 goodbye..\r
') cf.close() cnn.close() exit_thread() print 'main thread exit' cnn.close() def handle_user(self,args,cf): cf.write('331 username ok\r
') print '331 ok' def handle_pass(self,args,cf): cf.write('230 log in ok\r
') print '230 ok' def handle_cwd(self,args,cf):#CWD , try: os.chdir(args[1:]) except: print 'dir does not exit,make it' os.mkdir(args[1:]) os.chdir(args[1:]) cf.write('250 "%s" is current directory\r
'% os.getcwd()) print 'cwd' def handle_pwd(self,args,cf): cf.write('257 "%s" is current directory\r
'% os.getcwd()[len(self.path)-1:].replace('\\','/')) print 'pwd' def handle_lis(self,ppsock,cf):#LIST , cf.write('125 Data connection already open \r
') res='' for afile in os.listdir(os.getcwd()): fpath=os.getcwd()+'\\'+afile # tstr=self.format_time(fpath) if os.path.isfile(fpath): # size=os.path.getsize(fpath) res+= '-rw-rw-rw- 1 owner group %s %s %s\r
' % (size,tstr,afile) else: res+= 'drwxrwxrwx 1 owner group 0 %s %s\r
' % (tstr,afile) print res ppsock.send(res) cf.write('226 transfer complete\r
') ppsock.close() def handle_pas(self,args,cf):# PASV , socket psock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) psock.bind((self.lip,0)) pport=psock.getsockname()[1] psock.listen(1) cf.write('227 entering pasv mode (%s,%s,%s).\r
' % (psock.getsockname()[0],pport//256,pport%256)) ppsock,addr=psock.accept() print 'enter pasv mode port %s...'%pport return [ppsock,psock] def handle_typ(self,args,cf): cf.write('200 \r
') print 'type a' def handle_qui(self,cf): cf.write('200 \r
') print 'quit...' return True def handle_noo(self,args,cf): args=args[2:] cf.write('200 \r
') print 'noop' def handle_siz(self,args,cf): filename=args[2:] print filename size=os.path.getsize(os.getcwd()+'\\'+filename) cf.write('%s %s\r
'%(213,size)) def handle_por(self,args,cf):#port mode pass args=args[2:] cf.write('200 \r
') print 'enter port mode' def handle_ret(self,args,cf,psock):#RET , try: tpath=os.getcwd()+'\\'+args[2:] print 'ret transfering now...path:%s'%tpath f=open(tpath,'rb') # while True: data=f.read(self.PSIZE) if not data: break psock.send(data) cf.write('226 ok\r
') print 'transport completed..' psock.close() except: print 'ret error...' cf.write('226 ok\r
') psock.close() exit_thread() def handle_sto(self,args,cf,psock):#STO , try: fname=os.getcwd()+'\\'+args[2:] f=open(fname,'wb') print 'make file ok' buf=psock.recv(self.PSIZE) while len(buf)==self.PSIZE: f.write(buf) buf=psock.recv(self.PSIZE) cf.write('226 transfer complete\r
') f.write(buf) f.close() psock.close() except: print 'error in sto' psock.close() exit_thread() def handle_mkd(self,args,cf): cf.write('257 %s dir created\r
'%args) try: os.mkdir(args[1:]) except: print 'mkdir error' def handle_del(self,args,cf): cf.write('250 file removed\r
') fname=os.getcwd()+'\\'+args[2:] try: os.remove(fname) except: print 'dele error' def handle_rmd(self,args,cf): cf.write('250 dir remove\r
') try: os.rmdir(args[1:]) except: print 'remove dir error' def format_time(self,file):# raw_ftime=os.stat(file).st_mtime mtime=time.localtime(raw_ftime) now=time.time() if now-raw_ftime>180*24*60*60: tstr='%d %Y' else: tstr='%d %H:%M' res='%s %s'%(self._months_map[mtime.tm_mon],time.strftime(tstr,mtime)) return res def handle_sys(self,args,cf): cf.write('215 UNIX Type:L8\r
') print 'syst' if __name__=='__main__': abc=FTPs('192.168.8.100','e:/') abc.Run()