调用reactor.stop时,Twisted不会退出

时间:2015-11-13 12:34:54

标签: python twisted

我正在尝试在Python中重新实现netcat:

#!/usr/bin/env python2

from sys import stdin, stdout

from twisted.internet import reactor
from twisted.internet.protocol import Protocol
from twisted.internet.endpoints import TCP4ClientEndpoint, connectProtocol

class NcClient(Protocol):
    def dataReceived(self, data):
            stdout.write(data)

    def sendData(self, data):
        self.transport.write(data)
        self.transport.write("\n")

client = NcClient()

def cmdloop():
    while True:
        line = stdin.readline()
        if line == "":
            break
        else:
            client.sendData(line)
    if reactor.running:
        reactor.stop()

point = TCP4ClientEndpoint(reactor, "localhost", 6004)
connectProtocol(point, client)
reactor.callInThread(cmdloop)
reactor.run()

cmdloop检测到输入结束时,会调用reactor.stop。 据我所知,reactor.stop向Twisted管理的所有事件发送关闭事件,例如线程,连接等。响应这些事件,连接被关闭,线程等待其程序的完成等。所以当{{1调用,与localhost:6004的连接应该关闭,程序应该退出。

但是,它不会立即发生,而是仅在reactor.stop()从服务器收到消息时才会发生。好像是在循环中阻塞地读取这些消息,并且只有当它接收到它时才会继续处理关闭请求。

如何在收到消息之前关闭它?我知道NcClient,但有更礼貌的选择吗?

1 个答案:

答案 0 :(得分:2)

Your main problem is that cmdloop is running in a non-reactor thread, and yet it is calling reactor methods other than callFromThread (specifically: transport.write via client.sendData). The documentation is quite clear on this:

Methods within Twisted may only be invoked from the reactor thread unless otherwise noted. Very few things within Twisted are thread-safe.

Luckily, implementing the equivalent of netcat doesn't require threading at all. You can simply use Twisted's built-in support for standard I/O as a source of data. Here's an example version:

import sys

from twisted.internet import reactor
from twisted.internet.protocol import Protocol, Factory
from twisted.internet.endpoints import clientFromString
from twisted.internet.stdio import StandardIO

class NcClient(Protocol):
    def __init__(self, forwardTo):
        self.forwardTo = forwardTo

    def connectionMade(self):
        self.transport.registerProducer(self.forwardTo.transport, True)
        self.forwardTo.transport.resumeProducing()
        self.forwardTo.transport.registerProducer(self.transport, True)

    def dataReceived(self, data):
        self.forwardTo.transport.write(data)

    def connectionLost(self, reason):
        reactor.stop()

class StdIo(Protocol):
    def connectionMade(self):
        self.transport.pauseProducing()
        f4p = Factory.forProtocol(lambda: NcClient(self))
        d = endpoint.connect(f4p)
        @d.addCallback
        def connected(proto):
            self.client = proto

    def dataReceived(self, data):
        self.client.transport.write(data)

    def connectionLost(self, reason):
        reactor.stop()

endpoint = clientFromString(reactor, sys.argv[1])

output = StandardIO(proto=StdIo(), reactor=reactor)

reactor.run()

If you still want to use threads for some reason, you can modify NcClient to use callFromThread to invoke sendData instead of calling it directly.