[Twisted-Python] flushing data to clients after a global variable is updated in server

Glyph Lefkowitz glyph at twistedmatrix.com
Thu Apr 30 13:39:07 MDT 2015


> On Apr 29, 2015, at 12:33 AM, Jessica Tsui <jesadjust at gmail.com> wrote:
> 
> Hi, it's me again. I am still working on the kivy app, and now I am trying to add a function to the app - the server side has two seconds to decide if he wants to alter the original data sent from one client to other clients. If he would like to do so, he has to press a button. If he does not press the button in 2 seconds, the original data will be sent to other clients automatically.
> 
> Right now I am trying to achieve that with the following code - by calling the MultiClientEcho().dataReceived(msg, "censored") line in the function as if MultiClientEcho received another data, however once i click the button, the program crashed and said (MultiClientEcho().dataReceived(msg, "censored")
>  TypeError: __init__() takes exactly 3 arguments (1 given))

The first problem here is that "MultiEchoClient()" means "create a new MultiEchoClient".  Since MultiEchoClient.__init__ takes "factory" and "app" parameters, you tried to create it with 1 parameter (just "self", which is passed implicitly) instead of the required 3 (self, factory, app).

The second problem, once you've addressed that, is that you almost certainly don't want to create a new MultiEchoClient :).  It's not clear to me which MultiEchoClient you are trying to send this message to.

The third problem is that you should never call dataReceived yourself.  dataReceived is a method invoked by Twisted to tell your protocol that data has arrived.

The fourth problem is that you're treating dataReceived as delivering a discrete message.  It doesn't; it delivers a segment of some data in the stream coming from a client.  This is our most popular FAQ; basically, you need to use a NetstringReceiver or something to ensure you're getting complete messages in dataReceived: https://twistedmatrix.com/trac/wiki/FrequentlyAskedQuestions#Whyisprotocol.dataReceivedcalledwithonlypartofthedataIcalledtransport.writewith <https://twistedmatrix.com/trac/wiki/FrequentlyAskedQuestions#Whyisprotocol.dataReceivedcalledwithonlypartofthedataIcalledtransport.writewith>

Hopefully once you've addressed all these it will work more like you want it to :).

> I wonder how can I fix this and achieve the function I am aiming at?

I've put other comments inline in the code below.
> import kivy
> from kivy.app import App
> from kivy.uix.label import Label
> from kivy.uix.scatter import Scatter
> from kivy.uix.boxlayout import BoxLayout
> from kivy.uix.scrollview import ScrollView
> from kivy.uix.button import Button
> from kivy.graphics.vertex_instructions import Rectangle
> from kivy.graphics.context_instructions import Color
> from kivy.graphics.instructions import Instruction
> from kivy.base import runTouchApp
> from kivy.lang import Builder
> import socket
You don't actually use "socket" anywhere in this module so you don't need to import it :).
> from kivy.core.window import Window
> import pygame
> import random
> from kivy.support import install_twisted_reactor
> install_twisted_reactor()
> from twisted.internet import protocol, defer
> from time import sleep
> from twisted.internet import reactor, task
> from twisted.protocols.basic import LineReceiver
> from twisted.internet.protocol import Protocol, Factory
> 
> censored = 0
> 
> class MultiClientEcho(protocol.Protocol):
>     def __init__(self, factory, app):
>         self.factory = factory
>         self.app = app
> 
>     def connectionMade(self):
>         self.factory.clients.append(self)
> 
>     def dataReceived(self, data):
> 
>         storedmessage = self.factory.app.handle_message(data)
> 
>         global censored
> 
Rather than making this a global variable, consider putting it (as with "app" and with "clients") onto the factory, so you can instantiate multiple MultiClientEchoFactory instances in one process.
>         def f(data):
>                 for client in self.factory.clients:
This is a bit too much indentation - you should stick to 4 spaces per indent for stylistic reasons :).
>                     client.transport.write(data)
>                     print "this will run in 1 sec after it's scheduled: %s" % data
> 
>         if censored == 0 and storedmessage:
>                 reactor.callLater(2, f, data)
You're not hanging on to the result of this callLater call, which means you are giving up any way of stopping this call from happening in the future.  If you want to allow the caller to cancel it, note that reactor.callLater returns an IDelayedCall, which has a "cancel()" method that stops it from happening if it hasn't happened yet.  See the API documentation here; https://twistedmatrix.com/documents/15.1.0/api/twisted.internet.interfaces.IReactorTime.callLater.html <https://twistedmatrix.com/documents/15.1.0/api/twisted.internet.interfaces.IReactorTime.callLater.html> 
>                 # client.transport.write(data)
>         elif censored == 1:
>                 reactor.callLater(0, f, 'censored')
>                 censored == 0
I think maybe you mean "censored = 0" here? "censored == 0" just means "compare censored to 0" which will create a True or False value but otherwise do nothing; in this context, it pretty much means "do nothing".
> 
>     def connectionLost(self, reason):
>         self.factory.clients.remove(self)
> 
> 
> class MultiClientEchoFactory(protocol.Factory):
>     protocol = MultiClientEcho
> 
>     def __init__(self, app):
>         self.clients = []
>         self.app = app
> 
>     def buildProtocol(self, addr):
>         return MultiClientEcho(self, self.app)
> 
> 
> class ServerApp(App):
>     def build(self):
>         self.label = Label(text="server started\n")
> 
> 
>         self.approve_btn = Button(text="approve")
>         # self.approve_btn.bind(on_release=self.send_message)
>         self.banned_btn = Button(text="banned")
>         self.banned_btn.bind(on_release=self.banned_message)
>         self.layout = BoxLayout(orientation='vertical', spacing=10)
> 
>         reactor.listenTCP(8000, MultiClientEchoFactory(self))
> 
Rather than calling listenTCP directly, you should be using some kind of Endpoint here; TCP4ServerEndpoint if you just want to hard code port 8000, or serverFromString if you want to allow your user to customize it.  See this document: https://twistedmatrix.com/documents/15.1.0/core/howto/endpoints.html <https://twistedmatrix.com/documents/15.1.0/core/howto/endpoints.html>        self.layout.add_widget(self.label)
>         self.layout.add_widget(self.banned_btn)
> 
>         return self.layout
> 
>     def banned_message(self, msg):
>         global censored
>         censored = 1
>         self.label.text += "censored\n"
>         MultiClientEcho().dataReceived(msg, "censored")
>         print censored
>         return censored
> 
>     def handle_message(self, msg):
>         self.label.text += "%s\n" % msg
>         return msg
> 
> 
> if __name__ == '__main__':
>     ServerApp().run()
> 
> for i in range(0,1):
>     print 1-i
>     sleep(0.1)
> 
> 
> 

Thanks again for using Twisted!

-g

-------------- next part --------------
An HTML attachment was scrubbed...
URL: </pipermail/twisted-python/attachments/20150430/dd617168/attachment-0002.html>


More information about the Twisted-Python mailing list