3

I'm new to websockets. I've been using the examples on the Getting Started page of the websockets docs, mainly the Synchronization Example.

In this use case, I have a sqlite3 database on localhost. I edit that database from a python GUI program on localhost which just imports the database code layer directly. The client then tells the websocket server to send out some extracted data to all clients.

(Eventually this will be on a LAN, with the server machine running a Flask API.)

This is working, with the code below, but it's not clear if I'm doing it correctly. Basically I want to send websockets messages when certain database activity takes place, and I'm confused about how to do a 'simple' non-async send, when invoked from code, ultimately in response to a GUI interaction, as opposed to doing a send in response to an incoming websocket message. In pseudo-code:

def send(ws,msg):
  ws.send(msg)

send(ws,'OK!')

The way I'm accomplishing that is wrapping the async def that does the sending in a non-async 'vanilla' def.

The websocket server code:

# modified from https://websockets.readthedocs.io/en/stable/intro.html#synchronization-example

import asyncio
import websockets

USERS = set()

async def register(websocket):
    print("register: "+str(websocket))
    USERS.add(websocket)

async def unregister(websocket):
    print("unregister: "+str(websocket))
    USERS.remove(websocket)

# each new connection calls trackerHandler, resulting in a new USERS entry
async def trackerHandler(websocket, path):
    await register(websocket)
    try:
        async for message in websocket:
            await asyncio.wait([user.send(message) for user in USERS])
    finally:
        await unregister(websocket)

start_server = websockets.serve(trackerHandler, "localhost", 8765)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

in the database interface code (on localhost, this file is just imported directly to the GUI app; but on the LAN server, this is the file specified in the WSGI call in Flask):

import asyncio
import websockets
# uri = "ws://localhost:8765"

# wrap the asynchronous send function inside a synchronous function

def wsSend(uri,msg):
    async def send():
        async with websockets.connect(uri) as websocket:
            # await websocket.send(str.encode(str(msg)))
            await websocket.send(json.dumps({"msg":msg}))
            # print(f"> {msg}")

            # greeting = await websocket.recv()
            # print(f"< {greeting}")

    asyncio.get_event_loop().run_until_complete(send())

...
...

def tdbPushTables(uri,teamsViewList=None,assignmentsViewList=None,teamsCountText="---",assignmentsCountText="---"):
    # uri = "ws://localhost:8765"
    if not teamsViewList:
        teamsViewList=tdbGetTeamsView()
    if not assignmentsViewList:
        assignmentsViewList=tdbGetAssignmentsView()
    if uri=='pusher':
        pusher_client.trigger('my-channel','teamsViewUpdate',teamsViewList)
        pusher_client.trigger('my-channel','assignmentsViewUpdate',teamsViewList)
    else:
        wsSend(uri,json.dumps({
                "teamsView":teamsViewList,
                "assignmentsView":assignmentsViewList,
                "teamsCount":teamsCountText,
                "assignmentsCount":assignmentsCountText}))

it's actually the client that initiates the call to tdbPushTables:

def buildLists(self):
    self.teamsList=tdbGetTeamsView()
    self.assignmentsList=tdbGetAssignmentsView()
    self.updateCounts()
    tdbPushTables('ws://localhost:8765',self.teamsList,self.assignmentsList,self.teamsCountText,self.assignmentsCountText)

Feels spooky. Is it spooky or is this actually the right way to do it? Should I be using the websockets module for the server, but a different module to do the 'simple'/synchronous sending of the websocket message to the server?

Two known side effects of this solution: 1) it opens and closes the websocket connection on every call - probably not really a problem...?, and 2) it results in non-fatal handled messages like this in the server transcript:

register: <websockets.server.WebSocketServerProtocol object at 0x041C46F0>
Task exception was never retrieved
future: <Task finished coro=<WebSocketCommonProtocol.send() done, defined at C:\Users\caver\AppData\Roaming\Python\Python37\site-packages\websockets\protocol.py:521> exception=ConnectionClosedOK('code = 1000 (OK), no reason')>
Traceback (most recent call last):
  File "C:\Users\caver\AppData\Roaming\Python\Python37\site-packages\websockets\protocol.py", line 555, in send
    await self.ensure_open()
  File "C:\Users\caver\AppData\Roaming\Python\Python37\site-packages\websockets\protocol.py", line 812, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: code = 1000 (OK), no reason
unregister: <websockets.server.WebSocketServerProtocol object at 0x041C46F0>

EDIT: looks like the websocket (singular) module has a synchronous interface, and the websockets (plural) docs explain that if you want to go synchronous you should use a different module; so, this works:

(instead of importing asyncio and websockets)

from websocket import create_connection

def wsSend(uri,msg):
    ws=create_connection(uri)
    ws.send(json.dumps({"msg":msg}))
    ws.close()

It does still result in the same handled traceback showing up in the server transcript each time wsSend is called; there's probably a way to silence that traceback output, but, regardless, it still doesn't seem to affect anything.

1 Answer 1

-1

Your code feels spooky, because you are mixing async code with synchronous code.

Based on personal experience, the code is simpler to follow if you keep most of the code asynchronous.

The structure will become something like:

import asyncio
import websockets

async def main():
    # Create websocket connection
    async with websockets.connect(uri) as websocket:
        await your_function_that_does_some_processing(websocket)


asyncio.get_event_loop().run_until_complete(main())

Have in mind that big sections of blocking code can generate trouble.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks - I'm not understanding how to implement my goals using the code structure you describe - could you give an example? How would you pass arguments uri (and msg) into the call to main()?
Found a synchronous websockets module, 'websocket' (singular). See EDIT at the end of the question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.