快速上手python websockets

翻譯自https://websockets.readthedocs.io/en/stable/intro.html

要求

websockets 庫要求Python版本 ≥ 3.6.1。

如果可能的話,您應該使用最新的python版本.。如果您使用的是舊版本,請注意,對於每個次要版本(3.x),只有最新的bugfix版本(3.x.y)纔得到官方支持。

安裝

用以下命令安裝websockets

pip install websockets

基本例子

以下是一個websocket的服務端

它從客戶端讀取一個名稱,發送一個問候語,然後關閉連接。

#!/usr/bin/env python

# WS server example

import asyncio
import websockets

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

    greeting = f"Hello {name}!"

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

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

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

在服務器端,websockets爲每個WebSocket連接執行一次處理程序coroutine hello。當處理程序協程返回時,它將關閉連接。

下面是一個對應的WebSocket客戶端示例。

#!/usr/bin/env python

# WS client example

import asyncio
import websockets

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

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

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

asyncio.get_event_loop().run_until_complete(hello())

使用connect()函數作爲異步上下文管理器可以確保在退出hello協程之前關閉連接。

安全樣例

安全的WebSocket連接提高了機密性和可靠性,因爲它們減少了壞代理的干擾風險。

WSS協議對於WS就像HTTPS對於HTTP一樣:連接是用傳輸層安全(Transport Layer Security,TLS)加密的,傳輸層安全(Transport Layer Security,TLS)通常被稱爲安全套接字層(Secure Sockets Layer,SSL)。WSS需要像HTTPS這樣的TLS證書。

下面介紹如何調整服務器示例以提供安全連接。有關安全配置上下文的信息,請參閱ssl模塊的文檔。

#!/usr/bin/env python

# WSS (WS over TLS) server example, with a self-signed certificate

import asyncio
import pathlib
import ssl
import websockets

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

    greeting = f"Hello {name}!"

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

ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
localhost_pem = pathlib.Path(__file__).with_name("localhost.pem")
ssl_context.load_cert_chain(localhost_pem)

start_server = websockets.serve(
    hello, "localhost", 8765, ssl=ssl_context
)

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

調整後的客戶端

#!/usr/bin/env python

# WSS (WS over TLS) client example, with a self-signed certificate

import asyncio
import pathlib
import ssl
import websockets

ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
localhost_pem = pathlib.Path(__file__).with_name("localhost.pem")
ssl_context.load_verify_locations(localhost_pem)

async def hello():
    uri = "wss://localhost:8765"
    async with websockets.connect(
        uri, ssl=ssl_context
    ) as websocket:
        name = input("What's your name? ")

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

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

asyncio.get_event_loop().run_until_complete(hello())

此客戶端需要上下文,因爲服務器使用自簽名證書。

使用有效證書(即由Python安裝信任的CA簽名)連接到安全WebSocket服務器的客戶機只需將ssl=True傳遞給connect()而不是構建上下文。

基於瀏覽器的示例

下面是一個如何運行WebSocket服務器並從瀏覽器連接的示例。

在控制檯中運行此腳本:

#!/usr/bin/env python

# WS server that sends messages at random intervals

import asyncio
import datetime
import random
import websockets

async def time(websocket, path):
    while True:
        now = datetime.datetime.utcnow().isoformat() + "Z"
        await websocket.send(now)
        await asyncio.sleep(random.random() * 3)

start_server = websockets.serve(time, "127.0.0.1", 5678)

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

在瀏覽器打開該HTML文件

<!DOCTYPE html>
<html>
    <head>
        <title>WebSocket demo</title>
    </head>
    <body>
        <script>
            var ws = new WebSocket("ws://127.0.0.1:5678/"),
                messages = document.createElement('ul');
            ws.onmessage = function (event) {
                var messages = document.getElementsByTagName('ul')[0],
                    message = document.createElement('li'),
                    content = document.createTextNode(event.data);
                message.appendChild(content);
                messages.appendChild(message);
            };
            document.body.appendChild(messages);
        </script>
    </body>
</html>

同步示例

WebSocket服務器可以從客戶端接收事件,處理它們以更新應用程序狀態,並跨客戶端同步結果狀態。

下面是一個示例,任何客戶端都可以增加或減少計數器。更新將傳播到所有連接的客戶端。

異步的併發模型保證更新是序列化的。

在控制檯中運行此腳本:

#!/usr/bin/env python

# WS server example that synchronizes state across clients

import asyncio
import json
import logging
import websockets

logging.basicConfig()

STATE = {"value": 0}

USERS = set()


def state_event():
    return json.dumps({"type": "state", **STATE})


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


async def notify_state():
    if USERS:  # asyncio.wait doesn't accept an empty list
        message = state_event()
        await asyncio.wait([user.send(message) for user in USERS])


async def notify_users():
    if USERS:  # asyncio.wait doesn't accept an empty list
        message = users_event()
        await asyncio.wait([user.send(message) for user in USERS])


async def register(websocket):
    USERS.add(websocket)
    await notify_users()


async def unregister(websocket):
    USERS.remove(websocket)
    await notify_users()


async def counter(websocket, path):
    # register(websocket) sends user_event() to websocket
    await register(websocket)
    try:
        await websocket.send(state_event())
        async for message in websocket:
            data = json.loads(message)
            if data["action"] == "minus":
                STATE["value"] -= 1
                await notify_state()
            elif data["action"] == "plus":
                STATE["value"] += 1
                await notify_state()
            else:
                logging.error("unsupported event: {}", data)
    finally:
        await unregister(websocket)


start_server = websockets.serve(counter, "localhost", 6789)

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

然後在幾個瀏覽器中打開這個HTML文件。

<!DOCTYPE html>
<html>
    <head>
        <title>WebSocket demo</title>
        <style type="text/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;
            }
        </style>
    </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>
            var minus = document.querySelector('.minus'),
                plus = document.querySelector('.plus'),
                value = document.querySelector('.value'),
                users = document.querySelector('.users'),
                websocket = new WebSocket("ws://127.0.0.1:6789/");
            minus.onclick = function (event) {
                websocket.send(JSON.stringify({action: 'minus'}));
            }
            plus.onclick = function (event) {
                websocket.send(JSON.stringify({action: 'plus'}));
            }
            websocket.onmessage = function (event) {
                data = JSON.parse(event.data);
                switch (data.type) {
                    case 'state':
                        value.textContent = data.value;
                        break;
                    case 'users':
                        users.textContent = (
                            data.count.toString() + " user" +
                            (data.count == 1 ? "" : "s"));
                        break;
                    default:
                        console.error(
                            "unsupported event", data);
                }
            };
        </script>
    </body>
</html>

常見模式

您通常希望在連接的生命週期中處理多條消息。所以你必須寫一個循環。下面是構建WebSocket服務器的基本模式。

客戶consumer

用於接收消息並將其傳遞給客戶協同程序:

async def consumer_handler(websocket, path):
    async for message in websocket:
        await consumer(message)

在本例中,consumer表示用於處理在WebSocket連接上接收的消息的業務邏輯。

當客戶端斷開連接時,迭代終止。

服務端 Producer

從服務端聯程獲取消息併發送消息:

async def producer_handler(websocket, path):
    while True:
        message = await producer()
        await websocket.send(message)

在本例中,producer表示用於生成要在WebSocket連接上發送的消息的業務邏輯。

send()在客戶端斷開連接時引發ConnectionClosed異常,該異常會中斷while True循環。

我全要!

通過將上述兩種模式組合起來並並行運行這兩個任務,可以在同一連接上讀寫消息:

async def handler(websocket, path):
    consumer_task = asyncio.ensure_future(
        consumer_handler(websocket, path))
    producer_task = asyncio.ensure_future(
        producer_handler(websocket, path))
    done, pending = await asyncio.wait(
        [consumer_task, producer_task],
        return_when=asyncio.FIRST_COMPLETED,
    )
    for task in pending:
        task.cancel()

註冊Registration

如上面的同步示例所示,如果需要維護當前連接的客戶端的列表,則必須在它們連接時註冊它們,並在它們斷開連接時註銷它們。

connected = set()

async def handler(websocket, path):
    # Register.
    connected.add(websocket)
    try:
        # Implement logic here.
        await asyncio.wait([ws.send("Hello!") for ws in connected])
        await asyncio.sleep(10)
    finally:
        # Unregister.
        connected.remove(websocket)

這個簡單的例子跟蹤內存中連接的客戶機。這隻在運行一個進程時有效。例如,在實際應用程序中,處理程序可以訂閱消息代理上的某些通道。

這就是全部內容了

websockets API的設計是由簡單性驅動的。

您不必擔心執行打開或關閉握手、應答ping或規範要求的任何其他行爲。

websockets在引擎蓋下處理所有這些,所以你不必。

還有一件事…

websockets提供了一個交互式客戶端:

$python-m websockets wss://echo.websocket.org/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章