在运行reactor.run()后启动TCP客户端

6 投票
1 回答
1052 浏览
提问于 2025-04-18 15:49

我正在尝试制作一个点对点的应用程序,用来发送文本消息。我现在的做法是,运行一个服务器,只要应用程序在运行,服务器就一直在工作,同时客户端会连接到其他节点的服务器来发送消息。为了测试,我是在本地进行的,也就是和自己对话。

目前我有以下代码:

from twisted.internet import reactor
from mylib import MessageSFactory

def send_message(message):
    reactor.connectTCP("localhost", 8080, MessageCFactory(message))

reactor.listenTCP(8080, MessageSFactory())
reactor.connectTCP("localhost", 8080, MessageCFactory("this message gets received"))
reactor.run()

send_message("this message doesn't")

但是问题是,在调用 reactor.run 之后,调用 send_message(最后一行)似乎没有任何效果。

问题在于,我需要在用户填写完消息并发送时,才运行 TCP 客户端部分(connectTCP)。所以我想通过调用 send_message 来实现这个功能。那么我该如何修改上面的代码,让它能正常工作呢?

根据我目前的了解,使用 LoopingCall 可能是个不错的选择,但这样我就得把客户端输入的新消息存储到一个变量里,并不断检查这个变量是否有新消息,然后再运行 send_message。这样会导致用户输入和函数回调之间有延迟,这样做是否是我最好的选择呢?

在这种情况下,还有其他方法可以实现吗?还是我对 twisted 的架构理解得不够?

编辑:应要求,这里是 GUI 代码,它负责从客户端获取消息输入:

from Tkinter import *

def send_message():
   print("message: %s" % (e1.get()))

master = Tk()
Label(master, text="Message").grid(row=0)
e1 = Entry(master)
e1.grid(row=0, column=1)
Button(master, text='Send', command=send_message).grid(row=3, column=1, sticky=W, pady=4)
mainloop()

谢谢

1 个回答

2

关键问题在于,Tkinter和Twisted都在以类似的方式解决类似的问题,也就是如何异步地响应外部事件。Tkinter主要关注的是图形界面事件,而Twisted则关注网络事件,这一点其实并不是特别重要。

它们的具体做法是都有一个“主循环”结构,这就像是一个无法回头的点,从这个点开始你就失去了对程序的控制。对于Twisted来说,这个点通常是reactor.run(),而对于Tkinter来说,就是Tkinter.mainloop()。在程序退出之前,这两个方法都不会返回。

幸运的是,你可以让Twisted为你管理Tkinter的事件循环!

在你程序的开始部分,你应该添加:

from Tkinter import Tk
from twisted.internet import tksupport
root_window = Tk()
tksupport.install(root_window)

然后,一旦你正常创建了你的图形界面,你就不应该调用Tkinter.mainloop(),而是使用:

from twisted.internet import reactor
root_window.protocol("WM_DELETE_WINDOW", reactor.stop)
reactor.run()

关于Tk.protocol()的那部分是可选的,但它可以通过正常关闭反应器来避免一些麻烦的错误,当图形界面尝试退出时。


如果这还不够,这里有一些真实的、可以运行的代码!首先是一个非常简单的服务器:

from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor

class Echo(Protocol):
    def dataReceived(self, data):
        print 'recieved:', data
    def connectionLost(self, reason):
        print 'connection closed', reason

f = Factory()
f.protocol = Echo
reactor.listenTCP(8080, f)
reactor.run()

然后是一个带有图形界面和网络活动的客户端:

from Tkinter import *
from twisted.internet import tksupport, reactor
master = Tk()
tksupport.install(master)

def send_message():
    message = e1.get()
    reactor.connectTCP("localhost", 8080, MessageCFactory(message))
    print("message: %s" % (message))

Label(master, text="Message").grid(row=0)
e1 = Entry(master)
e1.grid(row=0, column=1)
Button(master, text='Send', command=send_message).grid(row=3, column=1, sticky=W, pady=4)

from twisted.internet.protocol import ClientFactory, Protocol
from twisted.internet import reactor

class MessageCProto(Protocol):
    def connectionMade(self):
        self.transport.write(self.factory.message)
        self.transport.loseConnection()

class MessageCFactory(ClientFactory):
    protocol = MessageCProto

    def __init__(self, message):
        self.message = message

master.protocol("WM_DELETE_WINDOW", reactor.stop)
reactor.run()

撰写回答