I have modified Jean-Paul Calderone's code from this question to achieve that two server are run asynchronously in one thread and the second server is the client of the first one (he's a broker). This is my current code:
from twisted.internet import protocol, reactor
from twisted.protocols import basic
class BaseServerProtocol(basic.LineReceiver):
def lineReceived(self, line):
self.sendLine("I can see that you said: %s" % (line,))
self.transport.loseConnection()
##############################################################
class BrokerServerProtocol(basic.LineReceiver):
def lineReceived(self, line):
factory = protocol.ClientFactory()
factory.protocol = BrokerClientProtocol
reactor.connectTCP('localhost', 12345, factory)
self.sendLine("I did connect but have nothing to return")
# TODO: return broker client's response
class BrokerClientProtocol(basic.LineReceiver):
def connectionMade(self):
self.sendLine("Hello!")
# TODO: receive the message from broker
self.transport.loseConnection()
##############################################################
def main():
import sys
from twisted.python import log
log.startLogging(sys.stdout)
factory = protocol.ServerFactory()
factory.protocol = BaseServerProtocol
reactor.listenTCP(12345, factory)
factory = protocol.ServerFactory()
factory.protocol = BrokerServerProtocol
reactor.listenTCP(12346, factory)
reactor.run()
if __name__ == '__main__':
main()
What I would like to modify here is to replace both # TODO lines - first, I'd like to fetch BaseServer's response by the BrokerClient (second todo) and then fetch it by the BrokerServer (which has just created the BrokerClient, first todo). Can anyone help?
Related
I know a similar question has been asked here, but I am still battling with the following issue:
I am using putty as a telnet client and am using Win10. The code is given below. When I start the reactor and then connect a client, I get a response after each character is typed which is printed using the dataReceived function. However I can never seem to get the function lineReceived() to fire. I also tried a simple chat server example which worked fine using the lineReceived() function (and that example had no dataReceived() function. I tried commenting out dataReceived(), thinking perhaps it was masking out lineReceived().
In the code below, I cannot get lineReceived() to fire , only dataReceived() fires after each character is typed.
#! C:/Python37/python.exe
from twisted.internet import reactor
from twisted.internet.protocol import Factory, Protocol
from datetime import datetime, tzinfo, timedelta
from twisted.protocols.basic import LineReceiver
class Echo(Protocol):
def dataReceived(self, data):
self.transport.write(data)
class LineReceiver(Protocol):
print("Starting.......")
delimiter = "\n"
TIMEOUT = 300 # Client timeout period in seconds
def timeOut(self):
print("Client: %s. %s" % (self.addr, "Connection Timed out"))
self.transport.loseConnection()
def lineLengthExceeded(self, line):
return self.transport.loseConnection()
def connectionMade(self):
print("Connected......")
self.transport.write(b"hell...")
self.timeout = reactor.callLater(
self.TIMEOUT, self.timeOut
) # start client timeout timer
self.addr = self.transport.getPeer().host
addr = self.addr
self.addr_test = self.transport.getPeer().host
self.factory.NUM_CLIENTS += 1
def connectionLost(self, reason):
print("Lost.......")
# self.sendMsg("- %s left." % self.name)
self.transport.write(b"a client left")
self.factory.NUM_CLIENTS -= 1
print("Client disconnected: - " + str(self.addr))
print("Number of connections = %s" % self.factory.NUM_CLIENTS)
# def dataReceived(self, data): # this runs a few times after an initial connection
# self.transport.write(b"you typed: " + data +b"\n" + b"\r")
# print(data) # prints to log file with byte order marks
def lineReceived(self, line):
self.sendLine(b"Welcome, %s!" % (name,))
self.transport.write(b"line rx function...")
class DataFactory(Factory):
protocol = LineReceiver
NUM_CLIENTS = 0
def main():
print("Started...Listening for incoming connections.")
if __name__ == "__main__":
main()
reactor.listenTCP(10003, DataFactory())
reactor.run()
You overwrote LineReceiver with your own Protocol subclass, which does not have a lineReceived() method. You just gave me a good reason for using the module path as a namespace instead of importing a specific object :D
from twisted.protocols import basic
from twisted.internet import endpoints, protocol, reactor
class LnRcv(basic.LineReceiver):
def lineReceived(self, line):
print(line)
class DataFactory(protocol.Factory):
protocol = LnRcv
server = endpoints.TCP4ServerEndpoint(reactor, 10003)
server.listen(DataFactory())
reactor.run()
Update
I had some spare time to fix your code. You have string/bytes mismatching all over and unreferenced objects that were not in your original code. Here's an example that should work and give you a base to off of.
from uuid import uuid4
from twisted.internet import reactor
from twisted.internet.endpoints import TCP4ServerEndpoint
from twisted.internet.protocol import Factory, Protocol
from twisted.protocols.basic import LineReceiver
class LnRcv(LineReceiver):
# Client timeout period in seconds
TIMEOUT = 10
def timeOut(self):
print ("Client: %s. %s" % (self.addr, 'Connection Timed out' ))
self.transport.loseConnection()
def connectionMade(self):
self.factory.NUM_CLIENTS += 1
print(f"Connected......number of connections = {self.factory.NUM_CLIENTS}")
peer = self.transport.getPeer()
self.addr = f"{peer.host}:{peer.port}"
self.name = uuid4().hex[:9].upper()
self.transport.write(f"Hello! I temporarily set your ID to {self.name}. What is your name? ".encode("utf8"))
# Start client timeout timer
self.timeout = reactor.callLater(self.TIMEOUT, self.timeOut)
def connectionLost(self, reason):
self.factory.NUM_CLIENTS -= 1
print(f"- {self.name} left because:\n{reason.getErrorMessage()}")
print(f"- Client disconnected: - {self.addr}")
print(f"- Number of connections = {self.factory.NUM_CLIENTS}")
def lineReceived(self, line):
self.sendLine(f"Welcome, {line.decode('utf8')}!".encode("utf8"))
class DataFactory(Factory):
protocol = LnRcv
NUM_CLIENTS = 0
def main():
print("Started...Listening for incoming connections.")
server = TCP4ServerEndpoint(reactor, 10003)
server.listen(DataFactory())
reactor.run()
if __name__ == "__main__":
main()
I have a basic server and client implemented in Twisted. My goal is to have the client process some data, report its progress back to the server, and repeat until all the data is processed. The client is able to send an initial message to the server but it is not receiving the server's response letting it know it is ok to start processing that data. Here is the code I have.
Server:
from twisted.internet import reactor, protocol
PORT = 9000
progress = 0
class MyServer(protocol.Protocol):
def dataReceived(self, data):
global progress
print(data)
if data != "Ready?":
progress = int(data)
self.transport.write("Got it.")
self.transport.loseConnection()
def connectionLost(self, reason):
global progress
if progress == 10:
print("Completed.")
reactor.stop()
class MyServerFactory(protocol.Factory):
protocol = MyServer
factory = MyServerFactory()
reactor.listenTCP(PORT, factory)
reactor.run()
Client:
from twisted.internet import reactor, protocol
import time
HOST = 'localhost'
PORT = 9000
progress = 0
class MyClient(protocol.Protocol):
def connectionMade(self):
self.transport.write("Ready?")
self.transport.loseConnection()
def dataReceived(self, data):
global progress
progress += 1
print(progress)
self.transport.write(str(progress))
self.loseConnection()
def connectionLost(self, reason):
global progress
if progress == 10:
print("Completed.")
reactor.stop()
class MyClientFactory(protocol.ClientFactory):
protocol = MyClient
factory = MyClientFactory()
reactor.connectTCP(HOST, PORT, factory)
reactor.run()
I figured out that my issue was that I was prematurely closing the connection. In some earlier testing I was trying to send multiple messages within the dataReceived function which were getting concatenated into a single message. This led me to believe that you must lose the connection for each message to go through. Rather, you simply need to let the function finish before sending another message. Here is the updated code that is working as intended.
Server:
from twisted.internet import reactor, protocol
PORT = 9000
progress = 0
class MyServer(protocol.Protocol):
def dataReceived(self, data):
global progress
print(data)
if data != "Ready?":
progress = int(data)
self.transport.write("Got it")
if progress == 10:
self.transport.loseConnection()
def connectionLost(self, reason):
print("Completed.")
reactor.stop()
class MyServerFactory(protocol.Factory):
protocol = MyServer
factory = MyServerFactory()
reactor.listenTCP(PORT, factory)
reactor.run()
Client:
from twisted.internet import reactor, protocol
import time
HOST = 'localhost'
PORT = 9000
progress = 0
class MyClient(protocol.Protocol):
def connectionMade(self):
self.transport.write("Ready?")
def dataReceived(self, data):
global progress
progress += 1
print(progress)
self.transport.write(str(progress))
if progress == 10:
self.transport.loseConnection()
def connectionLost(self, reason):
print("Completed.")
reactor.stop()
class MyClientFactory(protocol.ClientFactory):
protocol = MyClient
factory = MyClientFactory()
reactor.connectTCP(HOST, PORT, factory)
reactor.run()
Is it possible to share socket objects between 2 running processes?
To simplify the issue, assume that we have two files s1.py and s2.py. Both are tcp servers listening on different ports.
s1.py body
from twisted.internet import reactor, protocol
CLIENTS = []
class Echo(protocol.Protocol):
def connectionMade(self):
CLIENTS.append(self)
def dataReceived(self, data):
self.transport.write(data)
def connectionLost(self):
CLIENTS.remove(self)
def main():
factory = protocol.ServerFactory()
factory.protocol = Echo
reactor.listenTCP(8000, factory)
reactor.run()
main()
and s2.py
from twisted.internet import reactor, protocol
class Echo(protocol.Protocol):
def dataReceived(self, data):
for socket in list_of_serialized_redis_CLIENTS_socket_object:
socket.transport.write(data)
def main():
factory = protocol.ServerFactory()
factory.protocol = Echo
reactor.listenTCP(8001, factory)
reactor.run()
main()
Is it possible to share CLIENTS from s1.py with s2.py?
Maybe there is some way to serialize CLIENTS and store it in redis or so.
Using socket.share() might not be as straight forward as you might think.
First off, it's a Windows exclusive feature and Windows is the one platform that gets the least attention by Twisted.
Getting access to the low-level socket object in Twisted can be tricky because there are a lot of things happening behind the scenes and might cause strange outcomes if changed.
So in other words, it's certainly possible to go this route, but it's not recommended.
If you're open to new solutions, I think an RPC-style server will solve your issue.
My thinking is, since the connection objects live in s1, simply create a remote process that will "do stuff" to those connections.
Other processes can connect to that server and execute functions on objects local to s1. Twisted's Perspective Broker is a ready-made solution you could leverage.
s1.py
from twisted.internet import reactor, protocol, endpoints
from twisted.spread import pb
class Echo(protocol.Protocol):
def connectionMade(self):
self.factory.client_set.add(self)
def connectionLost(self, reason):
self.factory.client_set.remove(self)
class Remote(pb.Root):
def __init__(self, client_set):
self.client_set = client_set
def remote_s1_write(self, data):
msg_tmpl = 'From s1...{0}'
for client in self.client_set:
response = msg_tmpl.format(data).encode('utf-8')
client.transport.write(response)
def main():
client_set = set() # container will be shared between the 2 servers
# setup Echo server
factory = protocol.ServerFactory()
factory.protocol = Echo
factory.client_set = client_set
echo_server = endpoints.TCP4ServerEndpoint(reactor, interface='localhost', port=8000)
echo_server.listen(factory)
# setup PB server
pb_root = Remote(client_set)
pb_factory = pb.PBServerFactory(pb_root)
pb_server = endpoints.TCP4ServerEndpoint(reactor, interface='localhost', port=7999)
pb_server.listen(pb_factory)
reactor.run()
main()
s2.py
from twisted.internet import reactor, protocol, endpoints
from twisted.spread import pb
class Echo(protocol.Protocol):
def dataReceived(self, data):
remote = self.factory.pb_factory.getRootObject()
remote.addCallback(lambda obj: obj.callRemote('s1_write', data))
remote.addCallback(lambda echo: 's1 said: {0}'.format(data))
remote.addErrback(lambda reason: 'error: {0}'.format(str(reason.value)))
remote.addCallback(lambda response: print(response))
self.transport.write(data)
def main():
# connect to s1's PB server
pb_factory = pb.PBClientFactory()
pb_connection = endpoints.TCP4ClientEndpoint(reactor, host='localhost', port=7999)
pb_connection.connect(pb_factory)
# setup Echo server
factory = protocol.ServerFactory()
factory.protocol = Echo
factory.pb_factory = pb_factory
echo_server = endpoints.TCP4ServerEndpoint(reactor, interface='localhost', port=8001)
echo_server.listen(factory)
reactor.run()
main()
I've taken the liberty and updated the syntax. Try this code and see if it works and ask if you have any issues.
I'm trying to build a server in Twisted which would let clients connect using Server Sent Events. I would like this server also to listen to Redis and if a message comes then push it to the connected SSE clients.
I have the SSE server working. I know how to subscribe to Redis. I can't figure out how to have both pieces running without blocking each other.
I'm aware of https://github.com/leporo/tornado-redis and https://github.com/fiorix/txredisapi, which were recommended in related questions. No idea how this helps :/
How to solve this? Could you help with both: conceptual tips and code snippets?
My Twisted SSE server code:
# coding: utf-8
from twisted.web import server, resource
from twisted.internet import reactor
class Subscribe(resource.Resource):
isLeaf = True
sse_conns = set()
def render_GET(self, request):
request.setHeader('Content-Type', 'text/event-stream; charset=utf-8')
request.write("")
self.add_conn(request)
return server.NOT_DONE_YET
def add_conn(self, conn):
self.sse_conns.add(conn)
finished = conn.notifyFinish()
finished.addBoth(self.rm_conn)
def rm_conn(self, conn):
self.sse_conns.remove(conn)
def broadcast(self, event):
for conn in self.sse_conns:
event_line = "data: {}'\r\n'".format(event)
conn.write(event_line + '\r\n')
if __name__ == "__main__":
sub = Subscribe()
reactor.listenTCP(9000, server.Site(sub))
reactor.run()
My Redis subscribe code:
import redis
redis = redis.StrictRedis.from_url('redis://localhost:6379')
class RedisSub(object):
def __init__(self):
self.pubsub = redis.pubsub()
self.pubsub.subscribe('foobar-channel')
def listen(self):
for item in self.pubsub.listen():
print str(item)
This is what works for me.
I've ended up using txredis lib with a slight change to the RedisClient (added minimal subscribe capabilities).
# coding: utf-8
import os
import sys
import weakref
from txredis.client import RedisClient
from twisted.web import server, resource
from twisted.internet import reactor, protocol, defer
from twisted.python import log
from utils import cors, redis_conf_from_url
log.startLogging(sys.stdout)
PORT = int(os.environ.get('PORT', 9000))
REDIS_CONF = redis_conf_from_url(os.environ.get('REDISCLOUD_URL', 'redis://localhost:6379'))
REDIS_SUB_CHANNEL = 'votes'
class RedisBroadcaster(RedisClient):
def subscribe(self, *channels):
self._send('SUBSCRIBE', *channels)
def handleCompleteMultiBulkData(self, reply):
if reply[0] == u"message":
message = reply[1:][1]
self.sse_connector.broadcast(message)
else:
super(RedisClient, self).handleCompleteMultiBulkData(reply)
#defer.inlineCallbacks
def redis_sub():
clientCreator = protocol.ClientCreator(reactor, RedisBroadcaster, password=REDIS_CONF.get('password'))
redis = yield clientCreator.connectTCP(REDIS_CONF['host'], REDIS_CONF['port'])
redis.subscribe(REDIS_SUB_CHANNEL)
class Subscribe(resource.Resource):
isLeaf = True
sse_conns = weakref.WeakSet()
#cors
def render_GET(self, request):
request.setHeader('Content-Type', 'text/event-stream; charset=utf-8')
request.write("")
self.sse_conns.add(request)
return server.NOT_DONE_YET
def broadcast(self, event):
for conn in self.sse_conns:
event_line = "data: {}\r\n".format(event)
conn.write(event_line + '\r\n')
if __name__ == "__main__":
sub = Subscribe()
reactor.listenTCP(PORT, server.Site(sub))
RedisBroadcaster.sse_connector = sub
reactor.callLater(0, redis_sub)
reactor.run()
How do I create a twisted server that's also a client?
I want the reactor to listen while at the same time it can also be use to connect to the same server instance which can also connect and listen.
Call reactor.listenTCP and reactor.connectTCP. You can have as many different kinds of connections - servers or clients - as you want.
For example:
from twisted.internet import protocol, reactor
from twisted.protocols import basic
class SomeServerProtocol(basic.LineReceiver):
def lineReceived(self, line):
host, port = line.split()
port = int(port)
factory = protocol.ClientFactory()
factory.protocol = SomeClientProtocol
reactor.connectTCP(host, port, factory)
class SomeClientProtocol(basic.LineReceiver):
def connectionMade(self):
self.sendLine("Hello!")
self.transport.loseConnection()
def main():
import sys
from twisted.python import log
log.startLogging(sys.stdout)
factory = protocol.ServerFactory()
factory.protocol = SomeServerProtocol
reactor.listenTCP(12345, factory)
reactor.run()
if __name__ == '__main__':
main()