Quick examples

Start a server

This WebSocket server receives a name from the client, sends a greeting, and closes the connection.

server.py
#!/usr/bin/env python

import asyncio

from websockets.asyncio.server import serve

async def hello(websocket):
    name = await websocket.recv()
    print(f"<<< {name}")

    greeting = f"Hello {name}!"

    await websocket.send(greeting)
    print(f">>> {greeting}")

async def main():
    async with serve(hello, "localhost", 8765) as server:
        await server.serve_forever()

if __name__ == "__main__":
    asyncio.run(main())

serve() executes the connection handler coroutine hello() once for each WebSocket connection. It closes the WebSocket connection when the handler returns.

Connect a client

This WebSocket client sends a name to the server, receives a greeting, and closes the connection.

client.py
#!/usr/bin/env python

from websockets.sync.client import connect

def hello():
    uri = "ws://localhost:8765"
    with connect(uri) as websocket:
        name = input("What's your name? ")

        websocket.send(name)
        print(f">>> {name}")

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

if __name__ == "__main__":
    hello()

Using connect() as a context manager ensures that the WebSocket connection is closed.

Connect a browser

The WebSocket protocol was invented for the web — as the name says!

Here’s how to connect a browser to a WebSocket server.

Run this script in a console:

show_time.py
#!/usr/bin/env python

import asyncio
import datetime
import random

from websockets.asyncio.server import serve

async def show_time(websocket):
    while True:
        message = datetime.datetime.now(tz=datetime.timezone.utc).isoformat()
        await websocket.send(message)
        await asyncio.sleep(random.random() * 2 + 1)

async def main():
    async with serve(show_time, "localhost", 5678) as server:
        await server.serve_forever()

if __name__ == "__main__":
    asyncio.run(main())

Save this file as show_time.html:

show_time.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>WebSocket demo</title>
    </head>
    <body>
        <script src="show_time.js"></script>
    </body>
</html>

Save this file as show_time.js:

show_time.js
window.addEventListener("DOMContentLoaded", () => {
  const messages = document.createElement("ul");
  document.body.appendChild(messages);

  const websocket = new WebSocket("ws://localhost:5678/");
  websocket.onmessage = ({ data }) => {
    const message = document.createElement("li");
    const content = document.createTextNode(data);
    message.appendChild(content);
    messages.appendChild(message);
  };
});

Then, open show_time.html in several browsers or tabs. Clocks tick irregularly.

Broadcast messages

Let’s send the same timestamps to everyone instead of generating independent sequences for each connection.

Stop the previous script if it’s still running and run this script in a console:

sync_time.py
#!/usr/bin/env python

import asyncio
import datetime
import random

from websockets.asyncio.server import broadcast, serve

async def noop(websocket):
    await websocket.wait_closed()

async def show_time(server):
    while True:
        message = datetime.datetime.now(tz=datetime.timezone.utc).isoformat()
        broadcast(server.connections, message)
        await asyncio.sleep(random.random() * 2 + 1)

async def main():
    async with serve(noop, "localhost", 5678) as server:
        await show_time(server)

if __name__ == "__main__":
    asyncio.run(main())

Refresh show_time.html in all browsers or tabs. Clocks tick in sync.

Manage application state

A WebSocket server can receive events from clients, process them to update the application state, and broadcast the updated state to all connected clients.

Here’s an example where any client can increment or decrement a counter. The concurrency model of asyncio guarantees that updates are serialized.

This example keep tracks of connected users explicitly in USERS instead of relying on server.connections. The result is the same.

Run this script in a console:

counter.py
#!/usr/bin/env python

import asyncio
import json
import logging

from websockets.asyncio.server import broadcast, serve

logging.basicConfig()

USERS = set()

VALUE = 0

def users_event():
    return json.dumps({"type": "users", "count": len(USERS)})

def value_event():
    return json.dumps({"type": "value", "value": VALUE})

async def counter(websocket):
    global USERS, VALUE
    try:
        # Register user
        USERS.add(websocket)
        broadcast(USERS, users_event())
        # Send current state to user
        await websocket.send(value_event())
        # Manage state changes
        async for message in websocket:
            event = json.loads(message)
            if event["action"] == "minus":
                VALUE -= 1
                broadcast(USERS, value_event())
            elif event["action"] == "plus":
                VALUE += 1
                broadcast(USERS, value_event())
            else:
                logging.error("unsupported event: %s", event)
    finally:
        # Unregister user
        USERS.remove(websocket)
        broadcast(USERS, users_event())

async def main():
    async with serve(counter, "localhost", 6789) as server:
        await server.serve_forever()

if __name__ == "__main__":
    asyncio.run(main())

Save this file as counter.html:

counter.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>WebSocket demo</title>
        <link href="counter.css" rel="stylesheet">
    </head>
    <body>
        <div class="buttons">
            <div class="minus button">-</div>
            <div class="value">?</div>
            <div class="plus button">+</div>
        </div>
        <div class="state">
            <span class="users">?</span> online
        </div>
        <script src="counter.js"></script>
    </body>
</html>

Save this file as counter.css:

counter.css
body {
    font-family: "Courier New", sans-serif;
    text-align: center;
}
.buttons {
    font-size: 4em;
    display: flex;
    justify-content: center;
}
.button, .value {
    line-height: 1;
    padding: 2rem;
    margin: 2rem;
    border: medium solid;
    min-height: 1em;
    min-width: 1em;
}
.button {
    cursor: pointer;
    user-select: none;
}
.minus {
    color: red;
}
.plus {
    color: green;
}
.value {
    min-width: 2em;
}
.state {
    font-size: 2em;
}

Save this file as counter.js:

counter.js
window.addEventListener("DOMContentLoaded", () => {
  const websocket = new WebSocket("ws://localhost:6789/");

  document.querySelector(".minus").addEventListener("click", () => {
    websocket.send(JSON.stringify({ action: "minus" }));
  });

  document.querySelector(".plus").addEventListener("click", () => {
    websocket.send(JSON.stringify({ action: "plus" }));
  });

  websocket.onmessage = ({ data }) => {
    const event = JSON.parse(data);
    switch (event.type) {
      case "value":
        document.querySelector(".value").textContent = event.value;
        break;
      case "users":
        const users = `${event.count} user${event.count == 1 ? "" : "s"}`;
        document.querySelector(".users").textContent = users;
        break;
      default:
        console.error("unsupported event", event);
    }
  };
});

Then open counter.html file in several browsers and play with [+] and [-].