ftplib python:NOOP命令在ASCII模式下有效,而不是二进制模式下。

4
我有一个多线程FTP脚本。当数据套接字正在接收数据时,一个线程循环向控制套接字发送NOOP命令,以在大型传输期间保持控制连接活动状态。
如果我想保持控制连接处于活动状态,我无法使用FTP.retrbinary()命令,因为我必须分离数据和控制套接字,而retrbinary不会这样做。
下面是代码:
def downloadFile(filename, folder):
    myhost = 'HOST'
    myuser = 'USER'
    passw = 'PASS'
    #login
    ftp = FTP(myhost,myuser,passw)

    ftp.set_debuglevel(2)
    ftp.voidcmd('TYPE I')
    sock = ftp.transfercmd('RETR ' + filename)
    def background():
        f = open(folder + filename, 'wb')
        while True:
            block = sock.recv(1024*1024)
            if not block:
                break
            f.write(block)
        sock.close()
    t = threading.Thread(target=background)
    t.start()
    while t.is_alive():
        t.join(120)
        ftp.voidcmd('NOOP')
    ftp.quit();


我的问题: FTP.transfercmd("RETR " + filename) 默认使用 ASCII 传输,而我正在传输视频,因此必须使用二进制传输(因此调用ftp.voidcmd('TYPE I') 以强制二进制模式)。

如果我调用ftp.voidcmd('TYPE I'),则 NOOP 命令会成功发送,并且输出如下:

*cmd* 'NOOP'
*put* 'NOOP\r\n'
*get* '200 NOOP: data transfer in progress\n'
*resp* '200 NOOP: data transfer in progress'
*cmd* 'NOOP'
*put* 'NOOP\r\n'
*get* '200 NOOP: data transfer in progress\n'
*resp* '200 NOOP: data transfer in progress'
*cmd* 'NOOP'
*put* 'NOOP\r\n'
*get* '200 NOOP: data transfer in progress\n'
*resp* '200 NOOP: data transfer in progress'

等等,该文件是ASCII格式的,因此已经损坏。如果我确实调用ftp.voidcmd('TYPE I'),那么NOOP命令只会发送一次,并且控制套接字直到传输完成才会响应。如果文件很大,则控制套接字会超时,就像从未发送过NOOP一样...

非常奇怪,但我确定它很简单。似乎transfercmd()没有按照预期拆分控制和数据套接字...因此ftp变量与数据流未分离...或者其他奇怪的事情。

感谢您提供的任何建议。


你有没有尝试使用FTP.retrbinary()代替同时使用FTP.voidcmd(TYPE I)FTP.transfercmd() - uselpa
@uselpa - 感谢您的回复。是的,retrbinary不是一个选项,因为它不返回数据套接字,而这对于向控制套接字发送单独的命令是必要的。 - hammus
请尝试使用 tcpdump 和/或 strace 来缩小问题范围。看到在响应 NOOP 时出现 "数据传输正在进行中",我有点惊讶,也许这是 ftplib 的一个特殊情况。 - Dima Tisnek
顺便问一下,你为什么需要保持控制连接?这是一个最小的测试案例吗,还是你的整个代码?如果只是简单的下载,有很多其他的库可以使用,例如 pycurl - Dima Tisnek
线程化的 RETRBINARY NOOP 命令的副本。 - Dima Tisnek
@qarma - 关于保持控制连接的问题:我这样做是为了能够知道何时传输完成...否则,ftplib会一直等待传输完整响应...我将研究您在答案底部建议的伪代码。 - hammus
1个回答

3

tcpdump确认服务器只有在整个文件发送完毕后才会发送226 Transfer complete.我怀疑这是FTP规范的一部分。实际上,看一下ftplib.py中的retrbinary代码:

    self.voidcmd('TYPE I')
    conn = self.transfercmd(cmd, rest)
    while 1:
        data = conn.recv(blocksize)
        if not data:
            break
        callback(data)
    conn.close()
    return self.voidresp()

最后一行期望在传输完成后(服务器已知的情况下)获得传输结果。

实际上,你的代码似乎缺少了 voidresp() 部分。

我对 ftp 不是很熟悉,但据我所见,像 lftp 这样的后台下载工具实际上会为每个并行下载打开一个新的控制连接。

如果你的文件确实很大,那么你的担忧是合理的。

FTP 有许多扩展,可能有一些可以满足你的需求。

或者你可以像这样制作一个循环:

pos = 0
while not full file:
    command REST
    download for a while in separate thread
    command ABRT
    wait for separate thread to abort
    pos += length of downloaded chunk

感谢,悬赏已奖励,我会坚持使用您的伪代码解决方案。感谢您的时间。 - hammus
我认为问题在于一段时间后控制通道被关闭,而且没有发送“226传输完成”消息。 - DejanLekic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接