Python:通过FTP上传大量文件

2024-09-29 19:36:15 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在开发一个python脚本,它监视一个目录(使用libinotify)以查找新文件,并对每个新文件进行一些处理,然后将其复制到存储服务器。我们使用的是NFS挂载,但是有一些性能问题,现在我们正在用FTP进行测试。看起来FTP使用的资源比nfs少得多(负载总是低于2,而nfs的负载高于5)。在

我们现在遇到的问题是在TIME_WAIT状态下保持打开状态的连接数量。在时间等待中,存储的峰值约为15k个连接。在

我想知道有没有什么方法可以重复使用以前的连接进行新的传输。在

有人知道有没有办法做到这一点?在

谢谢


Tags: 文件服务器目录脚本数量time状态时间
2条回答

是的,您可以使用ftplib重用连接。你所要做的就是不要关闭它们,继续使用它们。在

例如,假设您有一个模块filegenerator,它的generate()方法注册到inotify,将通知排队,yield一个接一个:

import ftplib
import os
import filegenerator

ftp = ftplib.FTP('ftp.example.com')
ftp.login()
ftp.cwd('/path/to/store/stuff')

os.chdir('/path/to/read/from/')

for filename in filegenerator.generate():
    with open(filename, 'rb') as f:
        ftp.storbinary('STOR {}'.format(filename), f)

ftp.close()

我有点困惑:

The problem we are having now is the amount of connections that keeps open in TIME_WAIT state.

听起来你的问题不是为每个文件创建一个新的连接,而是你从来没有关闭旧的连接。在这种情况下,解决办法很简单:只需关闭它们。在


要么是这样,要么你试着同时做这些事情,但没有意识到你在做什么。在

如果您希望一些并行,但不是无限的,您可以很容易地,例如创建一个由4个线程组成的池,每个线程都有一个打开的ftplib连接,每个线程从一个队列读取数据,然后是一个刚刚推到该队列上的inotify线程。在

这是一个新的答案,基于对前一个的评论。在

我们将使用一个TCP套接字,通过交替发送名称和内容来发送每个文件,如netstrings,每个文件都在一个大流中。在

我假设Python2.6,两边的文件系统使用相同的编码,并且您不需要很多并发客户机(但是您可能偶尔需要两个,例如,真实的客户机和一个测试人员)。我再次假设您有一个模块filegenerator,它的generate()方法注册到inotify,将通知排队,yield一个接一个。在

在客户端.py公司名称:

import contextlib
import socket
import filegenerator

sock = socket.socket()
with contextlib.closing(sock):
    sock.connect((HOST, 12345))
    for filename in filegenerator.generate():
        with open(filename, 'rb') as f:
            contents = f.read()
            buf = '{0}:{1},{2}:{3},'.format(len(filename), filename, 
                                            len(contents), contents)
            sock.sendall(buf)

在服务器.py公司名称:

^{pr2}$

如果在Windows上需要200多个客户机,在linux和BSD(包括Mac)上需要100多个客户机,在不太好的平台上需要十几个客户机,那么您可能希望使用事件循环设计而不是线程设计,在linux上使用epoll,在BSD上使用kqueue,在Windows上使用IO完成端口。这是一个痛苦的过程,但幸运的是,有一些框架可以为您概括一切。两个流行的(也是非常不同的)选择是Twisted和{a3}。在

尤其是gevent的一个优点是,您现在可以编写线程化代码,通过一些简单的更改,您可以将它变成像魔术一样基于事件的代码。在

另一方面,如果您最终想要基于事件的代码,那么最好从一开始就学习和使用一个框架,这样您就不必处理accept循环和{}的所有繁琐工作,直到您得到完整的消息并干净地关闭等等,只需编写您关心的部分。毕竟,上面一半以上的代码基本上都是每个服务器共享的东西的样板,所以如果你不必编写它,何必费心呢?在


你在评论中说:

Also the files are binary, so it's possible that I'll have problems if client encodings are diferent from server's.

请注意,我以二进制模式('rb''wb')打开了每个文件,并有意选择了一个协议(netstring),该协议可以处理二进制字符串,而不必尝试将它们解释为字符,也不必将嵌入的NUL字符视为EOF或类似的东西。而且,当我使用str.format时,在python2.x中,它不会进行任何隐式编码,除非您给它提供unicode字符串或提供基于区域设置的格式类型,而这两种我都没有做。(请注意,在3.x中,您需要使用bytes而不是{},这会改变一些代码。)

换句话说,客户机和服务器的编码不会进入它;您执行的二进制传输与FTP的I模式完全相同。在


但是,如果您想要相反的情况,为目标系统自动传输文本和重新编码呢?有三种简单的方法:

  1. 发送客户端的编码(要么在顶部发送一次,要么每个文件一次),然后在服务器上,从客户端解码并重新编码到本地文件。在
  2. 在text/unicode模式下执行所有操作,甚至是套接字。这很傻,在2.x中也很难做到。在
  3. 定义一个有线编码,比如UTF-8。客户端负责对文件进行解码并编码为UTF-8进行发送;服务器负责在接收时解码UTF-8并对文件进行编码。在

使用第三个选项,假设文件将使用默认的文件系统编码,更改后的客户端代码是:

with io.open(filename, 'r', encoding=sys.getfilesystemencoding()) as f:
    contents = f.read().encode('utf-8')

在服务器上:

with io.open(filename, 'w', encoding=sys.getfilesystemencoding()) as f:
    f.write(contents.decode('utf-8'))

默认情况下,io.open函数还使用通用换行符,因此客户机将把任何内容转换为Unix风格的换行符,而服务器将转换为它自己的本机换行符类型。在

注意,FTP的T模式实际上并不进行任何重新编码;它只进行换行转换(以及它的一个更有限的版本)。在

相关问题 更多 >

    热门问题