扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
最近终于有了一些空,准备写点关于python的网络编程的系列文章了,就从medusa这里开始吧。
Python作为一种优秀的解释性语言,现在得到了越来越多的关注。BitTorrent 这个现在非常流行的下载软件就是用Python写的,比如Zope这个web服务器软件也是用Python写的。其中Zope的处理网络请求部分是使用的medusa。从目前Zope来看,在这方面Python完全可以支持很高的网络连接。本文就medusa并结合用它写一个跨平台的小巧的可扩展,可配置的FTP服务器程序来帮助大家对它有一些基本的了解。
让单进程同时做超过一件事情只有两种方法:一种是多线程,这是最流行的做法,另一种是I/O复用,它基本上可以让你的程序具有多线程的能力而事实上并不使用多线程。如果你的程序CPU并不是瓶颈,那I/O复用就很适合,否则你应该使用多线程。事实上网络服务器很少有CPU是瓶颈的,一般情况I/O才是。Medusa中的http_server和ftp_server就是采用的I/O复用,是使用select来实现的。
Medusa本身包括了很多的模块,但大部分的模块都导入了asyncore或者async_chat,它们是Python标准库的一部分,提供了对异步、非阻塞网络应用程序的支持。所以只要熟悉了asyncore和async_chat就基本上可以说了解了Medusa 的核心部分了。这里我们要用到的模块主要有用于记数器的counter,用于操作文件系统的filesys,处理事件循环的event_loop,记录日志的logger,简单的ftp服务器ftp_server。其实还有支持http等等的模块,这里只列出了我们所当前所关心的模块。在这里我们主要使用的是Medusa的ftp_server模块,它提供最主要的部分,包括处理来自ftp客户端的连接,以及验证,对本地文件系统的操作是使用的filesys,然后记录日志使用的logger。
其中下面的ftp_server,passive_acceptor, ftp_channel, anon_authorizer, file_producer都是ftp_server.py的内容,也就是ftp_server模块的一部分。可以看出,其实主要的部分都是继承自asyncore这个标准模块。
当来自网络的请求到达时,首先被ftp_server对象处理,然后ftp_server将对每一个请求创建一个独立的ftp_channel对象,以后该连接的所有请求将由这个ftp_channel对象来处理。
一个最简单的ftp服务器程序就可以写成这样:
#!/usr/bin/python
import asyncore
from medusa import ftp_server, filesys, logger
def StartServer():
ftpServ = ftp_server.ftp_server( ftp_server.anon_authorizer( "G:\\"), ip='127.0.0.1', port=21, logger_object=logger.file_logger("log.txt") )
asyncore.loop()
if __name__ == "__main__":
print "Starting FTP Server.."
StartServer()
print "Server is running..."
上面的代码就是启动FTP服务器,并把G盘设置为根目录,同时在当前目录下生成一个叫log.txt的日志文件。其结构非常简单,主要就是asyncore.loop()在一直不停的调用select,来判断哪个描述符可读,哪个可写,可读的会直接调用ftp_server或ftp_channel中的handle_read(第一次通常是调用handle_accept),可写的则会调用handle_write(每当建立一个连接都会新建一个ftp_channel对象,且它的socket的描述符也会加到select中)。这里只是允许匿名访问,要改变认证方式需要自己重新定义这个认证的类,只要稍稍熟悉Python语言就可以做到。如果要支持虚拟目录的话,则需要从filesys.py中的os_filesystem派生新的类。
这里我们也给出一个支持从文件里读取用户和口令的认证类,这里没有对信息做加密处理,如有必要完全可以使用某种编码对口令加密(不要使用MD5哦,好象最近被破解了),也可以直接提供数据库接口来把信息存储在数据库中。代码如下:
import string
class file_authorizer:
def __init__ (self, root='/'):
self.root = root
def authorize (self, channel, username, password):
f = open('user.txt')
info = f.readlines()
f.close()
if username in ('ftp', 'anonymous'):
channel.persona = -1, -1
channel.read_only = 1
return 1, 'Ok.', filesys.os_filesystem (self.root)
else:
for i in info:
user, passwd, user_path = string.split(i, ' ')
if username == user and password == passwd:
channel.read_only = 0
return 1, 'Ok.', filesys.os_filesystem (string.strip(user_path, '\n'))
return 0, 'Password invalid.', None
anon_authorizer的代码在ftp_server.py里,这里只是多增加了一个读取文件内容的程序段,其中user.txt的格式为用户名+空格+密码+空格+该用户主目录,一行一个用户信息。同时,这里我们认为user.txt里的用户都有上传文件的权限,只需要设置一个变量chennel.read_only = 0。当然,要使用新的认证方式还需要把原来代码中的
ftpServ = ftp_server.ftp_server( ftp_server.anon_authorizer( "G:\\"), ip='127.0.0.1', port=21, logger_object=logger.file_logger("log.txt") )
修改为:
ftpServ = ftp_server.ftp_server( file_authorizer( "G:\\"), port=21, ip='127.0.0.1', port=21, logger_object=logger.file_logger("log.txt") )
现在认证基本上完成,当然控制的信息还不是很多,但完全可以再扩展嘛!我们就可以把所以服务器的配置信息提出来放到一个单独的文件中,用它来作为配置文件使用。比如我们新建一个叫config.py的文件来存放。然后就可以把服务器的ip, 端口,匿名用户的根目录提取出来。下面就是config.py的内容。
#!/usr/bin/python
ip = '127.0.0.1'
port = 21
anonymous_root = 'G:\\'
这样就可以直接在主程序中导入这个模块,通过"模块名.变量名"来访问,比如就可以通过config.ip来访问ip这个变量。这点非常方便吧?然后把主程序中对应的变量名全部替换成这种写法就可以了。
好了,到这里基本的已经完成了一个可扩展,可配置的FTP服务器,没想到原来写个FTP服务器也是如此的简单吧?这就是Python的特点。当然也还有缺陷,比如说没有流量控制,没有对每个用户的连接数的限制等等,这就有待有兴趣的朋友去扩展了。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者