Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initiating TCP Client after running reactor.run()

Tags:

python

twisted

I am trying to make a p2p app for sending just text messages. The way I am doing it is by having a server that keeps running as long as the application is running, and a client that connects to other node's server to send messages. For testing purposes I am doing it with localhost, i.e. talking to myself.

So i have following:

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")

However the problem is calling send_message (last line) after reactor.run seems to have no effect.

The problem is I need to run the tcp client part (connectTCP) only when the user fills in a message, and sends it. So I am trying to do that with calling send_message. So how can I fix the above code to make that work?

From what I have read so far, using LoopingCall would be the way to go, but than I have to store new messages the client inputs into a variable, and constantly check that variable for a new message and than run send_message This would result in a delay between the user input and the function callback, regardless is this my best option?

Is there some other way to do it in this scenario? Or am I lacking understanding of some crucial part of twisted's architecture?

EDIT: As requested, here's the GUI code, that takes the message input from the client:

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()

Thanks

like image 225
mur Avatar asked Aug 03 '14 13:08

mur


1 Answers

The crucial problem is that both Tkinter and Twisted solve similar problems in similar ways, namely, reacting asyncronously to external events. The fact that Tkinter is focused on gui events and Twitsted on network events is of only passing importance.

The specific thing they do is that they have a "main loop" structure, a sort of point of no return from which you lose control. In the case of twisted, that's usually reactor.run(), and in tkinter, that'll be Tkinter.mainloop(). Both will not return until the program exits.

Fortunately, you can get Twisted to manage tk's event loop for you! At the begining of your program, you should add:

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

then, Once you've created your gui as normal, you should not call Tkinter.mainloop(), use:

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

The odd bit with Tk.protocol() is optional, but will get rid of some gruesome exceptions by shutting the reactor normally when the gui tries to exit.


In case that's not quite enough, here's some real, working code! First a really simple server

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()

and a client, with a gui and network activity:

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()
like image 81
SingleNegationElimination Avatar answered Nov 01 '22 22:11

SingleNegationElimination