I am currently building a client-server-type application with Pythons autobahn (asyncio) framework on both ends. I already have the basic setup of the server, which consists of the server itself and a database managing module. For the client I want to have the networking in a separate module, so I can call <networkingModule>.sendMessage("msg") for example. Is that a good way to do it?
My problem however is, the code used to initialize the client doesn't give me a real object to work with.
import asyncio
factory = WebSocketClientFactory("ws://localhost:9000", debug = False)
factory.protocol = <ClientProtocolName>
loop = asyncio.get_event_loop()
coro = loop.create_connection(factory, '127.0.0.1', 9000)
loop.run_until_complete(coro)
loop.run_forever()
loop.close()
The class itself is:
class <ClientProtocolName>(WebSocketClientProtocol):
def onConnect(self, response):
print("Server connected: {0}".format(response.peer))
def onOpen(self):
print("WebSocket connection open.")
self.sendInput()
def _sendMessage(self, msg):
self.sendMessage(bytes(msg, "utf-8"))
def onMessage(self, payload, isBinary):
if isBinary:
print("Binary message received: {0} bytes".format(len(payload)))
else:
print("Text message received: {0}".format(payload.decode('utf8')))
def onClose(self, wasClean, code, reason):
print("WebSocket connection closed: {0}".format(reason))
So my question is: Is there a way to build a "networking module" so I can access the sendMessage method from outside and would that be a good way to go about this problem, or should I just stuff all my programm logic into the client itself?
Related
I'm working on a application. Where am using python websockets. Now I need UDP and WS asynchronously running and listening on different ports.
I'm unable to do it because WS recv() waits indefinitely untill a message is received. Message will be received and pushed into queue. I need UDP to receive and push to same queue. This below class implements only websockets. I need another class with UDP and both class instance run asynchronously.
import websockets
import json
from sinric.command.mainqueue import queue
from sinric.callback_handler.cbhandler
import CallBackHandler
from time import sleep
class SinricProSocket:
def __init__(self, apiKey, deviceId, callbacks):
self.apiKey = apiKey
self.deviceIds = deviceId
self.connection = None
self.callbacks = callbacks
self.callbackHandler = CallBackHandler(self.callbacks)
pass
async def connect(self): # Producer
self.connection = await websockets.client.connect('ws://2.5.2.2:301',
extra_headers={'Authorization': self.apiKey,
'deviceids': self.deviceIds},
ping_interval=30000, ping_timeout=10000)
if self.connection.open:
print('Client Connected')
return self.connection
async def sendMessage(self, message):
await self.connection.send(message)
async def receiveMessage(self, connection):
try:
message = await connection.recv()
queue.put(json.loads(message))
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed')
async def handle(self):
# sleep(6)
while queue.qsize() > 0:
await self.callbackHandler.handleCallBacks(queue.get(), self.connection)
return
thanks for your time in the comments. I solved this issue by running instances of WS and UDP in 2 different daemon threads.
A good way to solve this issue would be to use threads. You could accept a message and put it into a queue, then handle the queue on a different thread.
I have a websocket server written in python tornado. the server will receive many connections from clients and as you know, we have on_message function that is fired when a websocket message is received. so, here is my question that if a message(say request from client) need 5 sec to be processed then when the server is processing some request, the server goes in blocking mode and can't accept or receive more connection or data. after some research i figure out that Asyncio can resolve my problem but i don't now know to use it. so, how do i call process method to avoid blocking?
following is my code:
class WavesClient(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True
def open(self):
print("New client connected")
def on_message(self, message):
self.process(message)
def on_close(self):
print("Client disconnected")
def process(self,message):
#it takes 5 sec to complete
I use tornado primarily to host my webapps, therefore I can tell you if any part of your code in tornado is blocking, the whole server will block.
Now to your code:
#tornado.gen.coroutine
def on_message(self, message):
process_results = yield self.process(message)
self.write_message(process_results)
#tornado.gen.coroutine
def process(self, message):
# a long process, this is the equivalent of time.sleep(2) which is blocking
yield tornado.gen.sleep(2)
return 'finished'
With tornado you have to yield from a function to get the return value.
Also, if your function is yielding, you must wrap it using the tornado.gen.coroutine decorator
This question is similar to yours. And the answer is informative as well.
I've a web-socket server which connects with the clients. Following is the code:-
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor
class Chat(LineReceiver):
def __init__(self, users):
self.users = users
self.name = None
self.state = "GETNAME"
def connectionMade(self):
self.sendLine("What's your name?")
def connectionLost(self, reason):
if self.users.has_key(self.name):
del self.users[self.name]
def lineReceived(self, line):
if self.state == "GETNAME":
self.handle_GETNAME(line)
else:
self.handle_CHAT(line)
def handle_GETNAME(self, name):
if self.users.has_key(name):
self.sendLine("Name taken, please choose another.")
return
self.sendLine("Welcome, %s!" % (name,))
self.name = name
self.users[name] = self
self.state = "CHAT"
def handle_CHAT(self, message):
# Need to send the message to the connected clients.
class ChatFactory(Factory):
def __init__(self):
self.users = {} # maps user names to Chat instances
def buildProtocol(self, addr):
return Chat(self.users)
reactor.listenTCP(8123, ChatFactory())
reactor.run()
Clients get connected to the above code(server), and sends the data to the server.
Now, I've another python script, basically a scraper which scrapes the web, processes it and finally need to send the data to the connected clients.
script.py
while True:
# call `send_message` function and send data to the connected clients.
How can I achieve it?? Any example would be of great help!!
UPDATE
After using Autobahn
I've a server that fetches data from 3rd party API. I want to send this data to all the connected web-socket clients. Here is my code:-
class MyServerProtocol(WebSocketServerProtocol):
def __init__(self):
self.connected_users = []
self.send_data()
def onConnect(self, request):
print("Client connecting: {0}".format(request.peer))
def onOpen(self):
print("WebSocket connection open.")
self.connected_users.append(self) # adding users to the connected_list
def send_data(self):
# fetch data from the API and forward it to the connected_users.
for u in self.users:
print 1111
u.sendMessage('Hello, Some Data from API!', False)
def onClose(self, wasClean, code, reason):
connected_users.remove(self) # remove user from the connected list of users
print("WebSocket connection closed: {0}".format(reason))
if __name__ == '__main__':
import sys
from twisted.python import log
from twisted.internet import reactor
factory = WebSocketServerFactory(u"ws://127.0.0.1:9000")
factory.protocol = MyServerProtocol
reactor.listenTCP(9000, factory)
reactor.run()
My Server will never receive a message or probably will receive, but as of right now there's no such use-case, hence no need for OnMessage event for this example).
How do I write my send_data function in order to send data to all my connected clients??
You need to avoid this pattern when writing software with Twisted:
while True:
# call `send_message` function and send data to the connected clients.
Twisted is a cooperative multitasking system. "Cooperative" means that you have to give up control of execution periodically so that other tasks get a chance to run.
twisted.internet.task.LoopingCall can be used to replace many while ... loops (particularly while True loops):
from twisted.internet.task import LoopingCall
LoopingCall(one_iteration).start(iteration_interval)
This will call one_iteration every iteration_interval seconds. In between, it will give up control of execution so other tasks can run.
Making one_iteration send a message to a client is just a matter of giving one_iteration a reference to that client (or those clients, if there are many).
This is a variation on the FAQ How do I make Input on One Connection Result in Output on Another.
If you have a ChatFactory with a dict containing all your clients, just pass that factory to one_iteration:
LoopingCall(one_iteration, that_factory)
or
LoopingCall(lambda: one_iteration(that_factory))
I am new to web-sockets. I am using tornado/python for my back-end and written the following code.
class BaseWebSocketHandler(websocket.WebSocketHandler):
"""Base Class to establish an websocket connection."""
def open(self):
"""Opening the web socket connection."""
self.write_message('Connection Established.')
def on_message(self, message):
"""On message module send the response."""
pass
def on_close(self):
"""Close the connection."""
self.write_message('bye')
class MeterInfo(BaseWebSocketHandler):
"""Establish an websocket connection and send meter readings."""
def on_message(self, message):
"""On message module send to the response."""
self.write_message({'A': get_meter_reading()})
My JavaScript code is like the following,
var meter = new WebSocket("ws://"+window.location.host+"/socket/meterstatus/");
meter.onopen = function() {
$('#meter-well').text('Establishing connection...');
};
meter.onmessage = function (evt) {
var data = JSON.parse(evt.data)
var text = "<div class='meter'><h2>" + data.A +"</h2></div>";
$('#meter-pre').html(text);
};
meter.onclose = function (evt) {
console.log(JSON.parse(evt.data))
$('#meter-pre').append('\n'+evt.data);
};
window.setInterval(function(){ meter.send('') }, 100);
I am making a blank web-socket request request to the back-end every 100 millisecond. this seems a very bad solution to me. Is there any better way to do it without making multiple send() to the back-end and only notifying the user only on any changes in the meter reading?
Also i have gone through MQTT protocol to do this in a better way, can someone suggest how can i implement that?
You almost found the solution to your issue here:
class MeterInfo(BaseWebSocketHandler):
"""Establish an websocket connection and send meter readings."""
def on_message(self, message):
"""On message module send to the response."""
self.write_message({'A': get_meter_reading()})
As you could notice tornado needs some event to ping client through write_message method. You are using new message from client as such event, try to change to simple timeout call as event, like this:
# BaseWebSocketHandler removed, because we need to track all opened
# sockets in the class. You could change this later.
class MeterInfo(websocket.WebSocketHandler):
"""Establish an websocket connection and send meter readings."""
opened_sockets = []
previous_meter_reading = 0
def open(self):
"""Opening the web socket connection."""
self.write_message('Connection Established.')
MeterInfo.opened_sockets.append(self)
def on_close(self):
"""Close the connection."""
self.write_message('bye')
MeterInfo.opened_sockets.remove(self)
#classmethod
def try_send_new_reading(cls):
"""Send new reading to all connected clients"""
new_reading = get_meter_reading()
if new_reading == cls.previous_meter_reading:
return
cls.previous_meter_reading = new_reading
for socket in cls.opened_sockets:
socket.write_message({'A': new_reading})
if __name__ == '__main__':
# add this after all set up and before starting ioloop
METER_CHECK_INTERVAL = 100 # ms
ioloop.PeriodicCallback(MeterInfo.try_send_new_reading,
METER_CHECK_INTERVAL).start()
# start loop
ioloop.IOLoop.instance().start()
Check out tornado.ioloop documentation for more about PeriodicCallback and other options.
If you want to use tornado to MQTT protocol, it's not possible with tornado. You could try emqtt server for example, but this is actual server, not framework to write apps, so IMHO it would be more comprehensive that ping through web socket with tornado.
I'm trying to create a client/server using Twisted.
I'd like to create a daemon, which will be connected to another server as a client and act as a server for other clients.
I've writen something like that which I think describes my problem:
server = sys.argv[1]
control_port = 8001
class ControlClient(protocol.Protocol):
def makeConnection(self, transport):
[some code here -snip-]
self.firstOrder(order, transport)
def firstOrder(self, action, transport):
self.t = transport
self.t.write(action + "\0")
def sendOrder(self, action):
self.t.write(action + "\0")
def dataReceived(self, data):
[some code here -snip-]
[HERE I WANT TO SEND DATA TO CLIENTS CONNECTED TO MY TWISTED SERVER, USING CONTROL SERVER]
class ControlServer(ControlClient):
def dataReceived(self, data):
print "client said " + data
def makeConnection(self, transport):
self.t = transport
self.t.write("make connection")
print "make connection"
def sendData(self, data):
self.t.write("data")
class ClientFactory(protocol.ClientFactory):
protocol = ControlClient
def clientConnectionFailed(self, connector, reason):
print "Connection failed - goodbye!"
reactor.stop()
def clientConnectionLost(self, connector, reason):
print "Connection lost - goodbye!"
reactor.stop()
class ServerFactory(protocol.ServerFactory):
protocol = ControlServer
def main():
c = ClientFactory()
reactor.connectTCP(server, control_port, c)
s = ServerFactory()
reactor.listenTCP(9000, s)
reactor.run()
if __name__ == '__main__':
main()
As you can see, I'd like to send (as a server) some data received (as a client). My problem is of course my ServerControl is not instantiated in my ClientControl so I don't have access to transport which is required to send data to clients.
The only thing you seem to be missing is that you can keep a list of your client connections and make that list available to the code that's trying to send out data to all the clients.
There's an example of this in the Twisted FAQ: http://twistedmatrix.com/trac/wiki/FrequentlyAskedQuestions#HowdoImakeinputononeconnectionresultinoutputonanother
That example only has one factory, but the idea is the same. To handle your case with two factories, just give one factory a reference to the other.