Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autobahn sending user specific and broadcast messages from external application

Totally new to websockets.

I am having a bit of trouble understanding how to interact with python Autobahn / twisted from another application and cannot seem to find any useful examples.

I have a Python application running that needs on certain events to send one of two types of messages. The first is a broadcast message to all users. The second type is to a single specific user.

Using the following two examples I can receive messages and send a response. However I do not need to receive anything from connected clients (other than clients connecting to the websockets server) only send to them.

I have played with: https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/websocket/echo

Also (non Autobahn related): https://github.com/opiate/SimpleWebSocketServer

Questions:

1 - Is what I am trying to do possible? Can I have an external application that connects with the Autobahn application / server and broadcasts messages to all connected users or a single user.

2 - If possible can someone point me in the right direction to learn how this can be done?

Thanks

like image 751
someuser Avatar asked Apr 29 '15 18:04

someuser


1 Answers

First of all the Autobahn project offers an Open-Source implementation of the communication protocol WAMP. WAMP offers the two communication pattern RPC (Remote-Procedure-Call) and PUBSUB (Publish-Subscribe). So in your case it is necessary to figure out which of the two pattern fits your needs.

RPC

According to the WAMP FAQ RPC the RPC involves three Roles. These are:

  • Caller
  • Callee
  • Dealer

In your case, the Callee is the server whereas the Caller (Client) calls a method on the server. This is what apparently works for you. (A return value can be send to the Callee/client). The dealer is responsible for the routing and can be ignored at the moment. So considering the above pattern it seems to be not fitting for your problem.

PUBSUB

The second pattern is PUBSUB. This pattern consists of the three roles (taken from WAMP FAQ PUBSUB):

  • Publisher
  • Subscriber
  • Broker

So what happens is, that the Publisher (Server) publishes events to topics. A Subscriber (Client) can subscribe to a topic of the Publisher. Once an event is published the Subscriber receives the event including the payload. That means that you could offer a topic "Broadcast" and let all clients subscribe to the topic. If required you can send a broadcast message to all clients.

Then you have to deal with the problem of sending a message to single clients (Subscribers). According to the documentation, the publish function for publishing a topic has an optional parameter to give a list of "Clients" that are eligible to receive the event. WAMP Documentation (Class Publish)

--------EDIT--------

It is not clear what is meant by the "external application" and in what language it is supposed to be written. The explained problem by the Author can be solved if the external application is either written in python, JavaScript or Cpp or an Android App using the Autobahn framework with (WAMP).

Autobahn offers, as mentioned in the question, also a websocket protocol implementation. Another approach to solve the problem could be by using Autobahn Websockets and for the "external Application" a Websocket implementation of choice. Autobahn offers for Python and Android Websocket solutions. Of course there are more Websocket libraries or modules available. Java Websocket library, Python Websocket Client module and more...

So lets say the Websocket Server is implemented using the Autobahn framework. The external application is another client connecting to the server and sending a defined string starting with "send_broadcast:PAYLOAD" and the payload appended. On the server you could check the message for the string and if the msg starts with "send_broadcast" you can then send the broadcast to all connected clients. If you want to send the msg to just one client you can define another string like "send_to_single:IP:PAYLOAD" for example. The server implementation could then have another elif branch that checks for the "send_to_single" and call another method, maybe "def send_to_single"?, and pass another argument given the ip of the client. Instead of sending to all clients ,as in the broadcast method, you could send the msg only to the given client. Another way for your own communication protocol would be using JSON. you could define your msg as follows:

{
    "type": "broadcast",
    "msg": "your_message"
}

or

{
    "type": "single",
    "elegible": ["IP_1", "???"],
    "msg": "your_message"
}

On the Server you then load the Payload, check the type and do the further steps.

Server

import sys

from twisted.internet import reactor
from twisted.python import log
from twisted.web.server import Site
from twisted.web.static import File

from autobahn.twisted.websocket import WebSocketServerFactory, \
    WebSocketServerProtocol, \
    listenWS


class BroadcastServerProtocol(WebSocketServerProtocol):

    def onOpen(self):
        self.factory.register(self)

    def onConnect(self, request):
        print("Client connecting: {}".format(request.peer))

    def onMessage(self, payload, isBinary):
        if not isBinary:
            if "send_broadcast" in payload.decode('utf8'):
                msg = "Send broadcast was ordered"
                self.factory.broadcast(msg)

    def connectionLost(self, reason):
        WebSocketServerProtocol.connectionLost(self, reason)
        self.factory.unregister(self)


class BroadcastServerFactory(WebSocketServerFactory):

    """
    Simple broadcast server broadcasting any message it receives to all
    currently connected clients.
    """

    def __init__(self, url, debug=False, debugCodePaths=False):
        WebSocketServerFactory.__init__(self, url, debug=debug, debugCodePaths=debugCodePaths)
        self.clients = []
        self.tickcount = 0
        self.tick()

    def tick(self):
        self.tickcount += 1
        self.broadcast("tick %d from server" % self.tickcount)
        reactor.callLater(1, self.tick)

    def register(self, client):
        if client not in self.clients:
            print("registered client {}".format(client.peer))
            self.clients.append(client)

    def unregister(self, client):
        if client in self.clients:
            print("unregistered client {}".format(client.peer))
            self.clients.remove(client)

    def broadcast(self, msg):
        print("broadcasting message '{}' ..".format(msg))
        for c in self.clients:
            c.sendMessage(msg.encode('utf8'))
            print("message sent to {}".format(c.peer))


class BroadcastPreparedServerFactory(BroadcastServerFactory):

    """
    Functionally same as above, but optimized broadcast using
    prepareMessage and sendPreparedMessage.
    """

    def broadcast(self, msg):
        print("broadcasting prepared message '{}' ..".format(msg))
        preparedMsg = self.prepareMessage(msg)
        for c in self.clients:
            c.sendPreparedMessage(preparedMsg)
            print("prepared message sent to {}".format(c.peer))


if __name__ == '__main__':

    if len(sys.argv) > 1 and sys.argv[1] == 'debug':
        log.startLogging(sys.stdout)
        debug = True
    else:
        debug = False

    ServerFactory = BroadcastServerFactory
    # ServerFactory = BroadcastPreparedServerFactory

    factory = ServerFactory("ws://localhost:9000",
                            debug=debug,
                            debugCodePaths=debug)

    factory.protocol = BroadcastServerProtocol
    factory.setProtocolOptions(allowHixie76=True)
    listenWS(factory)

    webdir = File(".")
    web = Site(webdir)
    reactor.listenTCP(8080, web)

    reactor.run()

Client The client is written as well in Python using a different module implementation and still works. It is of course neccessary to comunicate with a Websocket Server using the Websocket protocol.

from websocket import create_connection
ws = create_connection("ws://localhost:9000")
print "Sending 'send_broadcast'..."
ws.send("send_broadcast:PAYLOAD")
print "Sent"
print "Reeiving..."  # OPTIONAL
result = ws.recv()   # OPTIONAL
print "Received '%s'" % result    # OPTIONAL
ws.close(

)

like image 141
Ben Avatar answered Nov 05 '22 05:11

Ben