Here is my simple twisted server code:
from twisted.internet.protocol import Protocol,Factory
from twisted.internet import reactor
class MultiEcho(Protocol):
def __init__(self, factory):
self.recvBuf = ""
self.factory = factory
def connectionMade(self):
self.factory.echoers.append(self)
print self.factory.echoers
def dataReceived(self, data):
self.recvBuf += data
self.transport.write(data)
def connectionLost(self, reason):
self.factory.echoers.remove(self)
class MultiEchoFactory(Factory):
def __init__(self):
self.echoers=[]
def buildProtocol(self, addr):
return MultiEcho(self)
if __name__ == '__main__':
reactor.listenTCP(4321, MultiEchoFactory())
reactor.run()
Use 'telnet 127.0.0.1 4321' ,will print a list:
[<__main__.MultiEcho instance at 0x0000000002EA6B08>]
add another 'telnet 127.0.0.1 4321',the list is:
[<__main__.MultiEcho instance at 0x0000000002EA6B08>, <__main__.MultiEcho instance at 0x0000000002EA6EC8>]
I know a client is a protocol object's instance ,and the list is MultiEchoFactory.echoers,so I want to keep the list,
import them in another .py file use list[n].transport.write(somedata).
I try from myserver import MultiEchoFactory,but the MultiEchoFactory.echoers is empty.
I also rewrite the code
class MultiEchoFactory(Factory):
def __init__(self):
self.echoers=[]
to
class MultiEchoFactory(Factory):
echoers=[]
But I still can't use the protocol list in another .py file.
So,what should I do?
You need to save it in buildProtocol(), i.e.:
class MultiEchoFactory(Factory):
echoers=[]
def buildProtocol(self, addr):
echoer = MultiEcho(self)
MultiEchoFactory.echoers.append(echoer)
return echoer
Related
I have the following two Python classes:
import socketserver
class MyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
def __init__(self, server_address):
socketserver.TCPServer.__init__(self, server_address, MyTcpHandler)
self.allow_reuse_address = True
self.serve_forever()
class MyTcpHandler(socketserver.BaseRequestHandler):
data = ""
def handle(self):
self.data = self.request.recv(BUFF_SIZE).strip()
if self.data == b"shutdown":
self.request.close()
import threading
threading.Thread(target=SERVER.shutdown).start()
Thus, when the client sends "shutdown", the server itself should shutdown. As a workaround I set the global variable SERVER to the MyServer object, then I call SERVER.shutdown in another thread, as shown above.
But using a global variable is ugly as hell. So how can I communicate directly from the request handler with the socket server instead?
The server is available as self.server according to the Python docs here https://docs.python.org/2/library/socketserver.html#SocketServer.BaseRequestHandler.handle
Firstly, there is an IO class, which on __init__ is passed the asyncio loop object (io = IO(loop)), created earlier in the main class. IO class then at some point initializes the Socket class by doing self.socket = Socket(self), so that the socket object has a backwards access. Later, the Socket class initializes Websocket class which is a subclass of Transport
class Websocket(Transport):
name = 'websocket'
def __init__(self, socket):
self.socket = socket
def open(self):
url = self.prepareUrl()
factory = WebSocketClientFactory(url, debug = False)
factory.protocol = Protocol
websocket = self.socket.loop.create_connection(factory, host=self.socket.io.options.host, port=self.socket.options.port)
self.socket.io.loop.run_until_complete(websocket)
def onOpen(self):
print('print me please!')
So, socket object calls self.transport.open() (where self.transport = Websocket(self)), which creates autobahn factory, creates asyncio connection by doing self.socket.loop.create_connection() and then adds the coro future to the loop by executing run_until_complete().
Now, this is where the problem starts:
autobahn factory requires a class, which must inherit from autobahn.asyncio.websocket.WebSocketClientProtocol
My class Protocol(WebSocketClientProtocol) has the usual:
class Protocol(WebSocketClientProtocol):
#asyncio.coroutine
def onOpen(self):
print('socket opened!')
This works perfectly fine, the print('socket opened!') does print the string and my server also says the connection is open.
The question:
from the Protocol() class, when the onOpen() callback is called by autobahn, how can I make this method call the transport.onOpen() method and do print('print me please!')?
OK, so I fixed it! Easily done with PyDispatch module.
Here is my solution:
import asyncio
from pydispatch import dispatcher
from autobahn.asyncio.websocket import WebSocketClientProtocol, WebSocketClientFactory
from ..transport import Transport
class Websocket(Transport):
name = 'websocket'
def __init__(self, socket):
self.socket = socket
def open(self):
url = self.prepareUrl()
factory = WebSocketClientFactory(url, debug = False)
factory.protocol = Protocol
websocket = self.socket.loop.create_connection(factory, host=self.socket.io.options.host, port=self.socket.options.port)
dispatcher.connect(self.onOpen, signal='open', sender=dispatcher.Anonymous)
self.socket.io.loop.run_until_complete(websocket)
def onOpen(self):
print('print me please!')
class Protocol(WebSocketClientProtocol):
#asyncio.coroutine
def onOpen(self):
dispatcher.send(signal='open')
UPDATE
I've got another, IMO better solution to this. This one is not using PyDispatch. Since there is a callback when an asyncio task finishes, which returns the user-defined protocol object (which inherits from WebSocketClientProtocol), we can use that to link the two objects together:
import asyncio
from autobahn.asyncio.websocket import WebSocketClientProtocol, WebSocketClientFactory
from ..transport import Transport
class Protocol(WebSocketClientProtocol):
def __init__(self):
self.ws = None
super().__init__()
#asyncio.coroutine
def onConnect(self, response):
pass # connect handeled when SocketIO 'connect' packet is received
#asyncio.coroutine
def onOpen(self):
self.ws.onOpen()
#asyncio.coroutine
def onMessage(self, payload, isBinary):
self.ws.onMessage(payload=payload, isBinary=isBinary)
#asyncio.coroutine
def onClose(self, wasClean, code, reason):
if not wasClean:
self.ws.onError(code=code, reason=reason)
self.ws.onClose()
class Websocket(Transport):
name = 'websocket'
def __init__(self, socket, **kwargs):
super().__init__(socket)
loop = kwargs.pop('loop', None)
self.loop = loop or asyncio.get_event_loop()
self.transport = None
self.protocol = None
self.ready = True
def open(self):
url = self.prepareUrl()
if bool(self.socket.options.query):
url = '{0}?{1}'.format(url, self.socket.options.query)
factory = WebSocketClientFactory(url=url, headers=self.socket.options.headers)
factory.protocol = Protocol
coro = self.loop.create_connection(factory, host=self.socket.options.host, port=self.socket.options.port, ssl=self.socket.options.secure)
task = self.loop.create_task(coro)
task.add_done_callback(self.onWebSocketInit)
def onWebSocketInit(self, future):
try:
self.transport, self.protocol = future.result()
self.protocol.ws = self
except Exception:
self.onClose()
def send(self, data):
self.protocol.sendMessage(payload=data.encode('utf-8'), isBinary=False)
return self
def close(self):
if self.isOpen:
self.protocol.sendClose()
return self
def onOpen(self):
super().onOpen()
self.socket.setBuffer(False)
def onMessage(self, payload, isBinary):
if not isBinary:
self.onData(payload.decode('utf-8'))
else:
self.onError('Message arrived in binary')
def onClose(self):
super().onClose()
self.socket.setBuffer(True)
def onError(self, code, reason):
self.socket.onError(reason)
I am developing a chat application using Twisted framework in Python. However, sometimes the messages are delivered with a huge delay of upto 2 minutes from server to client.
Any idea what could be the reason for this delay ? Is this related to slower network ?
class MultiClientEcho(Protocol):
def __init__(self, factory):
self.factory = factory
def connectionMade(self):
ip = self.transport.getPeer().host
def dataReceived(self, data):
print "data is " + data
client.transport.write(data)
class MultiClientEchoFactory(Factory):
def __init__(self):
self.clients = []
def buildProtocol(self, addr):
return MultiClientEcho(self)
def main():
reactor.listenTCP(8000, MultiClientEchoFactory())
reactor.run()
if __name__ == '__main__':
main()
Thanks
CLIENT:
#!/usr/bin/env python
from twisted.internet import reactor, protocol
class EchoClient(protocol.Protocol):
def __init__(self, arg):
self.arg = arg
def connectionMade(self):
self.transport.write("hello, world!")
def dataReceived(self, data):
print "Server said:", data
self.transport.loseConnection()
def connectionLost(self, reason):
print "connection lost"
class EchoFactory(protocol.ClientFactory):
protocol = EchoClient
def buildProtocol(self, address):
proto = protocol.ClientFactory.buildProtocol(self, address, 12)
self.connectedProtocol = proto
return proto
def clientConnectionFailed(self, connector, reason):
print "Connection failed - goodbye!"
reactor.stop()
def clientConnectionLost(self, connector, reason):
print "Connection lost - goodbye!"
reactor.stop()
def main():
f = EchoFactory()
reactor.connectTCP("localhost", 8000, f)
reactor.run()
if __name__ == '__main__':
main()
SERVER:
#!/usr/bin/env python
from twisted.internet import reactor, protocol
from twisted.application import service, internet
class Echo(protocol.Protocol):
def dataReceived(self, data):
self.transport.write(data)
def main():
factory = protocol.ServerFactory()
factory.protocol = Echo
reactor.listenTCP(8000,factory)
reactor.run()
if __name__ == '__main__':
main()
ERROR:
exceptions.TypeError: buildProtocol() takes exactly 2 arguments (3 given)
QUESTION:
How can I get the EchoClient class in the CLIENT to accept parameters and assign instance variables ( such as arg in the EchoClient constructor above)? As noted below, it was previously suggested that I override the buildProtocol function, but my attempt at doing so has lead me to the above error. I am not really sure where to go from here. I suppose my question can be generalize to: how can I add instance variables to a protocol?
you wrote:
def buildProtocol(self, address):
proto = protocol.ClientFactory.buildProtocol(self, address, 12)
that is, you are overriding ClientFactory.buildProtocol and calling the parent class with a different signature than it knows how to handle.
Passing data from the factory to the client is only a little tricky. You can provide any __init__ you want to the factory, but twisted creates instances of IProtocol itself. Fortunately, most factories assign themselves to the factory attribute of the protocol, once it's ready to go:
class MyClientProtocol(protocol.Protocol):
def connectionMade(self):
# use self.factory here:
self.transport.write(self.factory.arg)
class MyClientFactory(protocol.ClientFactory):
protocol = MyClientProtocol
def __init__(self, arg):
self.arg = arg
In fact, the whole ProtocolFactory business is to support this kind of use; but be mindful; many instances of Protocol will share a single instance of their factory; use the factory for configuration but manage state in the protocol.
It's certainly possible that the way the standard family of Protocol/Factory implementations don't suit your needs, and that's also reasonable, so long as you fully implement the IProtocol and IProtocolFactory interfaces. The base classes exist because they handle most of the cases for you, not because they are the only possible implementation.
It's not clear from your question what exactly your tryed and what exactly the error was, but anyway you have to do two steps:
Make EchoClient's constructor take whatever arguments you need it to take and initialise whatever field you need it to initialise.
Override buildProtocol method in your factory to supply those arguments to your protocol.
What I need is a sort of man-in-the-middle implementation: I need a server who receives connections from clients (binary data with different lengths) and forwards the stream to a server it connects to (acting as a client), and then sends the data back from the server it is connected to, to the clients.
It actually works standing between the clients and the servers, and passing the data they exchange (which is a stream, so it continuously get from one side and sends to the other one).
The server is static, so it is always the same, and its address can even be hardcoded; however when a client drops the connection, this server must also drop the connection to the "real" server.
I've been looking around, but couldn't find a solution or an example for such a simple problem.
The code I've made works actually, but I have not yet managed to find how to put a reference into the server part that says "this is your assigned client", or into the client that says "this is your server". Here's my code:
#!/usr/bin/env python
from twisted.internet import protocol, reactor
from twisted.protocols import basic
client = None
server = None
class ServerProtocol(protocol.Protocol):
def connectionMade(self):
global server
factory = protocol.ClientFactory()
factory.protocol = ClientProtocol
server = self
reactor.connectTCP('localhost', 1324, factory)
def dataReceived(self, data):
global client
client.transport.write(data)
class ClientProtocol(protocol.Protocol):
def connectionMade(self):
global client
# Here's the instance of the client
client = self
def dataReceived(self, data):
global server
server.transport.write(data)
def main():
import sys
from twisted.python import log
log.startLogging(sys.stdout)
factory = protocol.ServerFactory()
factory.protocol = ServerProtocol
# Here's the instance of the server
server = ServerProtocol
reactor.listenTCP(2593, factory)
reactor.run()
if __name__ == '__main__':
main()
Now, the point is that the instance can't be contained into the global objects, and should be put inside the two classes: how?
I've managed to solve the issue by myself and, for future references (or to help anybody else who had this problem), here's the code I used to solve it.
I think both my solution and the one kindly given by jedwards work; now I just have to study his own a little more to be sure that what I've done is correct: this is my first application using the Twisted framework and studying somebody else's solution is the way to learn something new! :)
#!/usr/bin/env python
from twisted.internet import protocol, reactor
from twisted.protocols import basic
class ServerProtocol(protocol.Protocol):
def __init__(self):
self.buffer = None
self.client = None
def connectionMade(self):
factory = protocol.ClientFactory()
factory.protocol = ClientProtocol
factory.server = self
reactor.connectTCP('gameserver16.gamesnet.it', 2593, factory)
def dataReceived(self, data):
if (self.client != None):
self.client.write(data)
else:
self.buffer = data
def write(self, data):
self.transport.write(data)
print 'Server: ' + data.encode('hex')
class ClientProtocol(protocol.Protocol):
def connectionMade(self):
self.factory.server.client = self
self.write(self.factory.server.buffer)
self.factory.server.buffer = ''
def dataReceived(self, data):
self.factory.server.write(data)
def write(self, data):
self.transport.write(data)
print 'Client: ' + data.encode('hex')
def main():
import sys
from twisted.python import log
log.startLogging(sys.stdout)
factory = protocol.ServerFactory()
factory.protocol = ServerProtocol
reactor.listenTCP(2593, factory)
reactor.run()
if __name__ == '__main__':
main()
Consider this approach
#!/usr/bin/env python
import sys
from twisted.internet import reactor
from twisted.internet.protocol import ServerFactory, ClientFactory, Protocol
from twisted.protocols import basic
from twisted.python import log
LISTEN_PORT = 2593
SERVER_PORT = 1234
class ServerProtocol(Protocol):
def connectionMade(self):
reactor.connectTCP('localhost', SERVER_PORT, MyClientFactory(self))
def dataReceived(self, data):
self.clientProtocol.transport.write(data)
class ClientProtocol(Protocol):
def connectionMade(self):
# Pass ServerProtocol a ref. to ClientProtocol
self.serverProtocol.clientProtocol = self;
def dataReceived(self, data):
self.serverProtocol.transport.write(data)
class MyServerFactory(ServerFactory):
protocol = ServerProtocol
def buildProtocol(self, addr):
# Create ServerProtocol
p = ServerFactory.buildProtocol(self, addr)
return p
class MyClientFactory(ClientFactory):
protocol = ClientProtocol
def __init__(self, serverProtocol_):
self.serverProtocol = serverProtocol_
def buildProtocol(self, addr):
# Create ClientProtocol
p = ClientFactory.buildProtocol(self,addr)
# Pass ClientProtocol a ref. to ServerProtocol
p.serverProtocol = self.serverProtocol
return p
def main():
log.startLogging(sys.stdout)
reactor.listenTCP(LISTEN_PORT, MyServerFactory())
reactor.run()
if __name__ == '__main__':
main()
The ServerProtcol instance, passes a reference of itself to the MyClientFactory constructor, which then sets tells the ClientProtcol what ServerProtocol instance it's associated with.
Similarly, when the ClientProtocol connection is established, it uses it's reference to the ServerProtocol to tell the ServerProtocol what ClientProtocol to use.
Note: There's no error checking in this code, so you may encounter errors regarding NoneType if things go wrong (for example, if the real server isn't listening).
The important lines are:
reactor.connectTCP('localhost', SERVER_PORT, MyClientFactory(self))
#...
def __init__(self, serverProtocol_):
self.serverProtocol = serverProtocol_
Here, you pass a reference to the ServerProtocol to the MyClientFactory constructor. It stores this reference in a member variable. You do this so that that when the client factory creates a ClientProtocol, it can pass the reference on:
# Pass ClientProtocol a ref. to ServerProtocol
p.serverProtocol = self.serverProtocol
Then, once the connection is made from your script to the real server, the reverse happens. The ClientProtocol gives the ServerProtocol a reference to itself:
# Pass ServerProtocol a ref. to ClientProtocol
self.serverProtocol.clientProtocol = self;
Finally, both protocols use the stored references of each other to send data when it is received:
def dataReceived(self, data):
self.clientProtocol.transport.write(data)
#...
def dataReceived(self, data):
self.serverProtocol.transport.write(data)