当前位置:编程学习 > 网站相关 >>

Python:从socket开始,搭建一个最基本功能的FTP服务器

摘要:这是一个对应客户端为windows资源管理器的简单FTP服务器,支持上传,下载,新建文件夹,删除,重命名,不支持用户。
 
题外话:我们网络设计实验要求做的客户端,题目一看错,以为要写服务端,结果辛辛苦苦写了大半之后才知道,后悔已经来不及……就只好硬着头皮先把这个做完。当时写这东西的时候找不到网上教怎么做的(目测大神们都觉得太简单……),源码倒是不少,而自己水平太低,源码基本没法看(这真不是自谦,看pyftpdlib的时候觉得那是一个天书),只好自己边研究源码边折腾。
 
最后做出来250行,基本功能倒也不难实现,但水平有限什么异常处理,什么库,根本不会,更不用说什么框架……
 
同样完成一个功能,具体下来有各种各样的实现方法,所谓提高不仅仅是会实现某个功能,还包括以更快的实现它,更成熟的代码风格,更有效的实现思路,更合理的利用已有的库和架构,这些才是一个高手和码农的差异所在。
 
FTP协议简介
 
FTP协议,File Transfer Protocol,就是有关文件传输的协议,除了传输文件(上传、下载),协议还支持在服务器进行简单的文件修改操作,如,删除,重命名,新建文件夹。使得客户访问服务器上的文件就像访问本地文件一样。同时支持用户机制,可以给不同用户不同权限。
 
基本流程及框架
 
在FTP服务器中,为了保证多用户登入,以及用户操纵不因传输数据被打断,所采用的多线程机制如下图所示
 
 
关于PORT模式和PASV模式。
 
这两种模式是关于传输数据时新开端口的一个约定
 
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)),就像看到的,该函数接收两个参数,一个是希望新线程中执行的函数,还有就是希望给该函数传入的参数。
 
这是一个轻量级的开启线程的方法,更好的做法是继承线程类,把一个类做成线程,有自己的资源可以访问。
 
Os:
 
本文中主要涉及到的是,os.chdir(),os.getcwd(),os.mkdir()等等,这些关于变更目录的操作
 
Time:
 
参考了pyftpdlib中对时间的处理,不多,建议参考介绍时间库的文章详看。
 
实现思路
 
首先创建一个主socket绑定到21端口。
 
然后以一个while循环接受用户的连接,每接受一个连接就开启一个新线程与该用户交互。
 
在线程中又以一个while循环来接收用户命令,每接到一条命令,就对用户的命令和传递参数进行解析,并调用对应的handler函数处理。
 
如遇到pasv操作,则在handler_pas中建立新的socket并返回给客户端。
 
大致思路如此。详看代码部分。
 
代码部分
 
[python]  
#-*-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  
     
补充:Web开发 , Python ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,