非阻塞客户端FTP检索和写入

时间:2017-05-10 04:49:54

标签: python twisted

我正在使用Twisted构建FTP测试服务器和客户端。服务器运行良好。它与Twisted ftpserver.py示例基本相同。客户端是我在文件检索和写入过程中遇到阻塞问题的地方。我试图通过一些快速的Twisted线程实用程序解决它,但无济于事。

这是我的服务器:

#!/usr/bin/env python2
from __future__ import print_function, division, absolute_import

# Import twisted things

from twisted.protocols.ftp import FTPFactory
from twisted.protocols.ftp import FTPRealm
from twisted.internet import reactor
from twisted.cred.portal import Portal
from twisted.cred.checkers import AllowAnonymousAccess

p = Portal(FTPRealm("test/"), [AllowAnonymousAccess()])

f = FTPFactory(p)
f.timeOut = None

reactor.listenTCP(5504, f)
reactor.run()

客户端与此配对,是一个简单的wxPython GUI,它提供了一个文本框,用于写入要检索的文件的名称。在此GUI中,有wx.Timer每个执行一个方法50毫秒。这就是阻止我的FTP文件检索的原因。我发现因为主线程正在用完,接收数据的协议正在打嗝。如果你想知道为什么我有这个设置我正在模拟一个更大的项目的用例。

我尝试解决此问题的方法是在需要检索文件时在特定点上使用deferToThread。但是,通过打印当前线程,我发现正在接收数据的协议正在主线程中运行。这是我试图解决的问题。非常感谢任何帮助。

我的客户代码:

#!/usr/bin/env python2
from __future__ import  print_function, division, absolute_import

import wx
import sys
import threading

from twisted.internet import wxreactor
wxreactor.install()

from twisted.internet import reactor

from twisted.protocols.ftp import FTPClient

from twisted.internet import protocol
from twisted.internet import threads
from twisted.python import log

# This is the GUI
class TextSend(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Request Files", size=(200, 75))

        self.protocol = None # ftp client protocol
        self.factory = None

        panel = wx.Panel(self)

        vertSizer = wx.BoxSizer(wx.VERTICAL)
        horzSizer = wx.BoxSizer(wx.HORIZONTAL)

        self.fileName = None
        self.textbox = wx.TextCtrl(parent=panel, id=100, size=(100,-1))
        self.btn = wx.Button(panel, label="Retr.")

        # timer and checkbox for timer
        self.timer = wx.Timer(self, id=wx.ID_ANY)
        self.check = wx.CheckBox(parent=panel, label="Start blocking")

        #Bind
        self.textbox.Bind(wx.EVT_TEXT, self.getText)
        self.btn.Bind(wx.EVT_BUTTON, self.press)
        self.check.Bind(wx.EVT_CHECKBOX, self.onCheck)
        self.Bind(wx.EVT_TIMER, self.onTimer, self.timer)

        horzSizer.Add(self.textbox, flag=wx.ALIGN_CENTER)
        horzSizer.Add(self.btn, flag=wx.ALIGN_CENTER)

        vertSizer.Add(horzSizer, flag=wx.ALIGN_CENTER)
        vertSizer.Add(self.check, flag=wx.ALIGN_CENTER)

        panel.SetSizer(vertSizer)
        panel.Layout()

    def getText(self, evt):
        self.fileName = str(self.textbox.GetValue())

    def onCheck(self, evt):
        yes = self.check.GetValue()
        if yes:
            print("Starting timer")
            self.timer.Start(50)
        else: # no
            self.timer.Stop()

    def onTimer(self, evt):
        #print("Triggered timer")
        pass

    def press(self, evt):
        print("Send:", self.fileName)

        d = threads.deferToThread(self.retrieve)
        d.addCallback(self.done)

    def retrieve(self):
        print(threading.current_thread())
        # This is what does the retrieving.  Pass in FileWriter and
        # FileWriter's dataReceived method is called by main thread
        self.protocol.retrieveFile(self.fileName, FileWriter(self.fileName), offset=0).addCallbacks(self.done, self.fail)
        return "Done with deferToThread"

    def done(self, msg):
        print(threading.current_thread())
        print("DONE Retrieving:", msg)

    def fail(self, error):
        print('Failed. Error was:')
        print(error)

# This writes to the file of a same name as the one retrieved.
class FileWriter(protocol.Protocol):

    def __init__(self, fileName):
        self.f = open(fileName, 'wb')
        print("FROM FileWriter __init__:", threading.current_thread())

    def dataReceived(self, data):
        print("Byte size", len(data))
        print("FROM FileWriter dataReceived:", threading.current_thread())
        self.f.write(data)

    def connectionLost(self, reason):
        print("Writing closed and done")
        print("FROM FileWriter connectionLost:", threading.current_thread())
        self.f.close()

# Client FTP Protocol
class TestClient(FTPClient, object):

    def __init__(self, factory, username, password, passive):
        super(TestClient, self).__init__(username=username, password=password, passive=passive)
        self.factory = factory

    def connectionMade(self):
        print("hello")
        gui = self.factory.gui
        gui.protocol = self

# Twisted Client Factory
class FileClientFactory(protocol.ClientFactory):

    def __init__(self, gui):
        self.gui = gui
        self.protocol = None

    def buildProtocol(self, addr):
        user = 'anonymous'
        passwd = 'twisted@matrix.com'
        self.protocol = TestClient(self, username=user, password=passwd, passive=1)
        return self.protocol

    def clientConnectionLost(self, transport, reason):
        print("Connectiong lost normally:", reason)

    def clientConnectionFailed(self, transport, reason):
        print("Connection failed:", reason)


if __name__ == "__main__":
    # Initialize and show GUI
    logger = log.startLogging(sys.stdout)
    app = wx.App(False)
    app.frame = TextSend()
    app.frame.Show()
    reactor.registerWxApp(app)

    # Build Factory
    f = FileClientFactory(app.frame)

    # Connect to FTP server
    reactor.connectTCP("localhost", 5504, f)
    reactor.run()

    wxPython main loop.
    app.MainLoop()

1 个答案:

答案 0 :(得分:1)

你不能deferToThread(function_that_uses_twisted_apis)。 Twisted API几乎都是非线程安全的。您必须仅在reactor线程中使用它们(例外是几个与线程调度相关的API)。

相反,摆脱阻止代码。将 it 放入另一个线程,另一个进程,或将其重写为非阻塞。