安装

websocket不是python的自带库,需要下载安装:

pip install websockets

案例

client:

import asyncio
import websockets

async def hello():
    uri = "ws://localhost:8765"
    async with websockets.connect(uri) as websocket:
        # 发送消息给服务器
        message = "Hello, WebSocket!"
        await websocket.send(message)
        print(f"发送消息: {message}")

        # 接收服务器的回复
        response = await websocket.recv()
        print(f"收到回复: {response}")

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

serve:

import asyncio
import websockets

async def echo(websocket):
    """处理单个客户端的连接,接收消息并原样返回"""
    async for message in websocket:
        print(f"收到客户端消息: {message}")
        # 将消息回显给客户端
        await websocket.send(f"服务器回显: {message}")

async def main():
    # 启动 WebSocket 服务器,绑定到本地 8765 端口
    async with websockets.serve(echo, "localhost", 8765):
        print("WebSocket 服务器已启动,监听 ws://localhost:8765")
        await asyncio.Future()  # 保持服务器运行

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

每一个async的作用--链接

1. async with websockets.connect(uri) as websocket:

  • 异步上下文管理器。进入该上下文时,会执行 websockets.connect(uri) 的异步操作(建立 WebSocket 连接),并且等待连接建立完成,期间当前协程会暂停,让出事件循环。

  • 退出上下文时,会自动等待连接关闭的异步操作完成。

  • async with 保证了在进入和退出时正确地处理异步资源的获取与释放。

2. await websocket.send(message)

  • 等待发送操作完成。websocket.send 是一个异步方法,它将消息写入缓冲区并可能等待网络发送完成。使用 await 后,当前协程(调用send()的协程)会暂停,直到数据发送完毕或出错,才会返回当前协程,期间事件循环可以执行其他任务。

  • 注意:这里的“等待发送完成”并不一定意味着数据已经到达对方,而是指数据已进入内核缓冲区,可以继续发送后续消息。

3. await websocket.recv()

  • 等待接收服务器的回复。这是一个异步阻塞操作:如果没有消息到达,当前协程(调用recv()的协程)会暂停,让出事件循环;当收到完整消息后,协程恢复并返回消息内容。

  • 这体现了异步 I/O 的核心:在等待网络数据时,CPU 可以处理其他任务。

4. async for message in websocket:

  • 异步迭代websocket 对象是一个异步可迭代对象,每次迭代都会等待下一条消息的到来。

  • 循环体会在收到消息时执行,当没有消息时,当前协程暂停,事件循环可以运行其他客户端的协程。

  • 这种写法避免了手动调用 recv() 和循环控制,是处理消息流的简洁方式。

-- async 的核心目的就是让协程在“没事干”(比如等待网络、磁盘 I/O)的时候主动让出事件循环的控制权,从而让其他就绪的协程得以执行。

--await 则是调用了某个异步接口(async def修饰)之后,异步接口没有完成之前,当前执行的协程不会往下执行,需要等待异步接口完成,期间如果异步接口需要等待,则当前协程则会让出事件循环控制权。

关键字/语法 作用
async def 定义异步的协程函数,调用返回协程对象
async with 异步上下文管理器,进入和退出时自动等待异步操作
async for 异步迭代器,每次迭代自动等待下一个元素
await 挂起当前协程,等待一个可等待对象(协程、任务、Future、异步方法等)完成,期间事件循环运行其他任务

这样每次调用一个接口的时候都要加上一个async就很麻烦,既然已经使用async def定义协程函数了,为什么协程函数里边还要在每一个异步函数之前加上await,async with已经修饰这代码块了,为什么代码快中还要在调用的每一个函数前加上await?

1. async def 只是定义了一个协程工厂

当你写:

async def hello():
    ...

你只是定义了一个协程函数。调用它(例如 hello())并不会执行函数体内的任何代码,而是立即返回一个协程对象。这个协程对象需要被事件循环调度(比如通过 await 或 asyncio.create_task)才能真正开始执行。

换句话说:async def 只是让函数具备了“可暂停”的能力,但不会自动运行。

2. await 是实际执行异步操作的指令

在协程内部,当你调用一个异步函数(比如 websocket.send(message)),它返回的是一个可等待对象(awaitable),通常是另一个协程对象。如果你不加 await,这个协程对象就被丢弃了,里面的代码永远不会执行。这就像你写了一个函数却没有调用它。

因此,必须使用 await 来告诉事件循环:“请执行这个异步操作,并在它完成之前,我可以让出控制权。” 正是 await 触发了真正的 I/O 操作,并让协程在等待期间挂起。

接口

websocket.serve

        async with websockets.serve(self.handler, self.host, self.port):
            print(f"WebSocket 服务器已启动,监听 {self.host}:{self.port}")
            await asyncio.Future()

既然是服务端,那么应该不用指定ip,为什么serve还要指定ip; asyncio.Future()的作用为什么可以保持服务器运行?

问题1:为什么服务端还需要指定 IP?

在网络编程中,服务端必须明确指定要监听的地址和端口。这是因为一台计算机可能拥有多个网络接口(网卡)(例如:本地回环 127.0.0.1、局域网 IP、公网 IP 等)。通过指定 IP,你可以控制服务端仅接受来自特定接口的连接:

  • "localhost" 或 "127.0.0.1":只允许本机上的客户端连接,外部机器无法访问。常用于本地开发或测试。

  • "0.0.0.0":监听所有可用的网络接口,允许任何能到达该主机的客户端连接(包括本机和远程)。

  • 具体的局域网 IP:只允许通过该 IP 地址访问。

问题2:await asyncio.Future() 为什么能保持服务器运行?

理解 asyncio.Future

asyncio.Future 是一个占位符对象,代表一个将来会完成的异步操作的结果。它类似于并发编程中的“Promise”。默认情况下,一个新建的 Future 对象处于未完成状态,除非你手动调用 .set_result() 或 .set_exception() 来让它完成。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐