非凡爷爷 發表於 2026-4-14 14:09:00

你不知道的 WebSocket:服务器实时推送的核心协议,新手也能秒会

<h1 id="一引言">一、引言</h1>
<p>初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?</p>
<p>在我们之前的教程中,我们在树莓派 Pico 上用 HTML 开发了一个基于 HTTP 协议的网页服务器,以此控制树莓派 Pico 的硬件引脚:</p>
<p>https://freakstudio.cn/node/019bd0cf-8ca7-7e97-83fe-0b29d55c6f5c</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140910026-1090507842.png"></p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140909774-1699406013.png"></p>
<p>这个方式非常简单,但缺点也非常明显:每次提交表单都需要完成一次完整的网络往返(也就是页面刷新),导致存在明显延迟,这种往返通信对用户体验很不友好,延迟会导致用户重复点击,或是感觉界面无响应。</p>
<p><strong>HTTP 协议的核心模式是请求 - 响应:浏览器发起请求,服务器处理后返回响应。它属于</strong>​<strong>半双工</strong>​<strong>通信,同一时间只能单向传输数据,每次通信都需要重新建立握手连接。</strong></p>
<p>对很多嵌入式项目来说,这种协议完全够用:比如用浏览器配置开发板参数,之后开发板独立运行一段时间,但是考虑下面两个情况:</p>
<ul>
<li><strong>​温湿度实时记录和显示:​</strong>如果使用 HTTP,浏览器必须不断轮询(例如每隔 1 秒请求一次数据),服务器才能返回最新的温湿度值;这样会带来几个问题:
<ul>
<li>请求频率高,浪费带宽和 MCU 资源;</li>
<li>数据存在延迟(取决于轮询间隔);</li>
<li>设备功耗增加(频繁网络通信)。</li>
</ul>
</li>
<li><strong>​在线网络聊天室:​</strong>如果使用 HTTP,客户端需要不断轮询服务器是否有新消息(例如每几百毫秒请求一次)。这样不仅效率低,还会造成:
<ul>
<li>大量无效请求(大部分时候没有新消息);</li>
<li>服务器压力增大;</li>
<li>消息延迟明显,体验差。</li>
</ul>
</li>
</ul>
<p><strong>你可以看到,在需要服务器可以在有新消息时立即推送给所有客户端的情况下,使用 HTTP 通信并不友好。</strong></p>
<p>那么,我们实际上可以选择另一种更优的方案:​<strong>在浏览器与服务器之间建立并保持一个长连接</strong>​​<strong>,实现全双工通信</strong>​。</p>
<p>在这种模式下,客户端和服务器都可以在任意时刻主动发送数据,不再受限于 HTTP “请求 - 响应”的单向通信机制,从而避免频繁轮询带来的性能浪费和延迟问题。</p>
<p>这种通信方式特别适合​<strong>需要实时数据更新和即时交互反馈的场景</strong>​,例如实时监控、在线聊天、远程控制等。</p>
<p>而实现这一能力的核心协议,就是 ​<strong>WebSocket</strong>​。</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140906988-1073601638.png"></p>
<blockquote>
<p>客户端(如浏览器)向服务器发起连接请求,经一次 HTTP 握手完成协议升级后,建立持久全双工长连接。</p>
</blockquote>
<h1 id="二websocket-协议介绍">二、<strong>WebSocket 协议介绍</strong></h1>
<h2 id="21-基本概念">2.1 基本概念</h2>
<p>它于 2008 年诞生,<strong>2011 年成为国际标准:</strong></p>
<ol>
<li>WebSocket 最初在 HTML5 规范中以 <code>TCPConnection</code> 为名,作为基于 TCP 的套接字 API 的占位方案,用于解决浏览器端原生 TCP 通信的需求。</li>
<li>在 WebSocket 出现前,浏览器端端口 80 的全双工通信依赖 <strong>Comet 通道</strong> 技术,但存在两大核心缺陷:
<ol>
<li>实现复杂度高,部署与维护成本大;</li>
<li>因 TCP 三次握手、HTTP 请求头的额外开销,对小消息场景的传输效率极低。</li>
</ol>
</li>
<li>2008 年 6 月,Michael Carter 牵头系列技术讨论,产出了首个名为「WebSocket」的协议版本,核心目标是<strong>在不破坏网络安全假设的前提下,解决 Comet 技术的痛点;</strong></li>
<li>随后 Ian Hickson 与 Michael Carter 合作确定 WebSocket 这一名称,由 Ian Hickson 正式写入 HTML5 规范。</li>
</ol>
<p>目前​<strong>所有浏览器均已原生支持</strong>​,无需额外插件,包括 Google Chrome、Firefox、Microsoft Edge、Internet Explorer、Safari 和 Opera,其属于​<strong>真正的双向平等对话</strong>​,支持服务器主动向客户端推送信息,也支持客户端主动向服务器发消息,属于主流的<strong>服务器推送技术</strong>之一。</p>
<p>其技术特点包括:</p>
<ul>
<li>基于 <strong>TCP​ 协议</strong> 构建,服务器端实现逻辑简单,适配性强。</li>
<li>完美兼容 HTTP 生态,​<strong>默认共用 80(ws</strong>​​<strong>)、443(wss)端口</strong>​;</li>
<li>握手阶段采用 HTTP 协议,不易被网络屏蔽,可顺利通过各类 HTTP 代理服务器。</li>
<li>数据格式轻量化,​<strong>通信开销小、效率高</strong>​;</li>
<li>支持<strong>文本、二进制数据</strong>双向传输,覆盖文字、文件、音视频等各类数据场景。</li>
<li>​<strong>无同源限制</strong>​,客户端可与任意服务器建立连接,突破浏览器同源策略限制;</li>
<li>协议标识符:​<strong>ws</strong>​(未加密)、​<strong>wss</strong>​(加密,类似 HTTPS),通信地址遵循 URL 规范,示例:<code>ws://</code>​<code>example.com:80/some/path</code>。</li>
</ul>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140908101-1923129797.png"></p>
<p>一句话概括就是,WebSocket 以​<strong>TCP​ 为基础、HTTP 兼容为桥梁、双向实时为核心</strong>​,兼具低延迟、高灵活、跨域自由的特点,完美适配浏览器与服务器的实时交互场景。</p>
<h2 id="22-websocket-的工作流程">2.2 WebSocket 的工作流程</h2>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140906896-1388501947.png"></p>
<p>可以分为以下三个阶段:</p>
<ol>
<li><strong>握手阶段:HTTP 协议升级,建立持久连接(图中红色流程)</strong>
<ol>
<li>客户端发起 HTTP 升级请求:向服务器发送携带 <code>Upgrade: websocket</code> 头、随机密钥 <code>Sec-WebSocket-Key</code> 的 HTTP 请求,申请将当前 HTTP 连接升级为 WebSocket 连接。</li>
<li>服务器响应握手确认:通过约定算法生成响应密钥 <code>Sec-WebSocket-Accept</code>,返回给客户端完成协议升级。</li>
<li>连接正式建立:客户端验证响应密钥通过后,持久 TCP 长连接生效,图中标注 <code>connection opened</code>(连接已打开),握手阶段完成。</li>
</ol>
</li>
<li><strong>数据传输阶段:全双工双向实时通信(图中蓝色流程)</strong>
<ol>
<li>持久长连接生效:握手完成后,客户端与服务器维持 <code>open and persistent connection</code>(开放且持久的连接),全程无需重复建立连接。</li>
<li>双向实时消息传输:双方可随时主动向对方发送文本 / 二进制数据,实现真正的全双工通信,服务器可主动推送数据,彻底摆脱传统 HTTP 的客户端轮询限制。</li>
<li>心跳保活机制:双方通过 <code>Ping</code>(心跳请求)/<code>Pong</code>(心跳响应)控制帧周期性交互,维持连接存活,避免长连接因闲置被网络设备断开。</li>
</ol>
</li>
<li><strong>关闭阶段:优雅终止连接(图中黑色流程)</strong>
<ol>
<li>任意一方发起关闭请求:客户端或服务器可主动发送 <code>Close</code> 控制帧,申请关闭 WebSocket 连接。</li>
<li>双向确认关闭:对方收到关闭请求后,返回 <code>Close</code> 响应帧,完成双向确认。</li>
<li>连接正式关闭:双方确认后,彻底断开 TCP 长连接,图中标注 <code>connection closed</code>(连接已关闭),通信流程结束。</li>
</ol>
</li>
</ol>
<h2 id="23-websocket-的数据帧格式">2.3 WebSocket 的数据帧格式</h2>
<h3 id="231-数据帧概述">2.3.1 数据帧概述</h3>
<p>WebSocket 在完成握手建立长连接后,​<strong>所有通信都以「数据帧</strong>​​<strong>(Frame)」为最小传输单位</strong>​,无论是业务数据还是连接控制指令,都封装在二进制帧中传输:</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140910569-1817247747.png"></p>
<p><code>WebSocket</code> 数据帧是​<strong>二进制格式</strong>​,整体分为帧头(<code>Header</code>)和有效载荷(<code>Payload</code>)两大部分,其中帧头又分为<strong>固定头部(前 2 字节 / 16bit)</strong> 和​<strong>可变扩展头部</strong>​(根据长度、掩码动态变化),结构如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140903890-1036330378.png"></p>
<p>其中:</p>
<ul>
<li>帧头:包含 <code>FIN</code>、<code>Opcode</code>、<code>Masked</code>、<code>Payload length</code> 等核心字段,用于控制帧的解析与类型。
<ul>
<li><code>FIN</code> 字段(1bit,偏移 0):标记当前帧是否为​<strong>完整消息的最后一帧</strong>​,用于消息分片。
<ul>
<li>​<strong>取值规则</strong>​:
<ul>
<li><code>1</code>:最后一帧,代表消息传输完成;</li>
<li><code>0</code>:分片帧,代表消息未结束,后续还有数据帧。</li>
</ul>
</li>
<li><strong>对应分片机制:</strong>
<ul>
<li>非分片消息:单帧,<code>FIN=1</code> 且 <code>Opcode≠0</code>;</li>
<li>分片消息:首帧 <code>FIN=0</code>、<code>Opcode≠0</code>(定义消息类型),中间帧 <code>FIN=0</code>、<code>Opcode=0</code>(延续帧),最后一帧 <code>FIN=1</code>、<code>Opcode=0</code>(延续帧,标记结束)。</li>
</ul>
</li>
<li>支持大消息分片传输,适合内存有限的嵌入式设备(如树莓派 Pico W),无需缓存完整消息即可边生成边发送。</li>
</ul>
</li>
<li><code>RSV1</code>/<code>RSV2</code>/<code>RSV3</code> 字段(各 1bit,偏移 1-3):
<ul>
<li>保留位,用于协议扩展,​<strong>默认必须为 0</strong>​,仅当客户端与服务器在握手阶段协商了扩展(如压缩)时,才会使用非 0 值。</li>
<li>​<strong>对应压缩扩展</strong>​:<code>permessage-deflate</code> 压缩扩展会在握手时通过 <code>Sec-WebSocket-Extensions: permessage-deflate</code> 协商,启用后数据帧的 <code>RSV1=1</code>,表示 payload 为压缩数据。</li>
</ul>
</li>
<li><code>Opcode</code> 字段(4bit,偏移 4-7):定义帧的类型,决定 payload 的解析方式,分为​<strong>非控制帧(数据帧</strong>​<strong>)</strong> 和<strong>控制帧</strong>两大类。</li>
<li><code>Masked</code> 字段(1bit,偏移 8):
<ul>
<li>​<strong>作用</strong>​:标记帧是否经过掩码处理。</li>
<li>​<strong>强制规则</strong>​:
<ul>
<li>​<strong>客户端 → 服务器的所有帧:<code>Masked必须为1</code></strong>​​<strong>(必须做掩码)</strong>​;</li>
<li>​<strong>服务器 → 客户端的所有帧:<code>Masked必须为0</code></strong>​​<strong>(禁止做掩码)</strong>​。</li>
</ul>
</li>
<li>​<strong>掩码原理</strong>​:用 32bit(4 字节)的随机 <code>Masking key</code>,对 payload 的每个字节做 XOR 运算。</li>
</ul>
</li>
<li><code>Payload length</code> 字段(7/7+16/7+64bit,偏移 9 开始):
<ul>
<li>​<strong>作用</strong>​:表示有效载荷(扩展数据 + 应用数据)的字节长度
<ul>
<li><code>0-125</code>:直接用 7bit 表示长度,适合 ≤125 字节的小消息;</li>
<li><code>126</code>:后续 16bit(2 字节)表示长度,适合 126-65535 字节的中等消息;</li>
<li><code>127</code>:后续 64bit(8 字节)表示长度,适合 &gt; 65535 字节的大消息(最高位必须为 0,无符号)。</li>
</ul>
</li>
<li>​<strong>字节序</strong>​:大端(big-endian),无符号数。</li>
<li>​<strong>优势</strong>​:小消息用短头,大消息用长头,最大化节省带宽。</li>
</ul>
</li>
<li><code>Masking key</code> 字段(32bit,可选):
<ul>
<li>仅当 <code>Masked=1</code> 时存在,是 4 字节的​<strong>随机数</strong>​,客户端每次发帧都需生成新的随机密钥,不可固定。</li>
<li>作用:配合 Masked 字段,完成客户端到服务器的掩码处理。</li>
</ul>
</li>
</ul>
</li>
<li>有效载荷:实际传输的业务数据(如聊天消息、传感器数据、控制指令):
<ul>
<li>分为两部分:
<ul>
<li>​<strong>扩展数据</strong>​:仅协商了扩展(如压缩)时存在,长度由扩展定义;</li>
<li>​<strong>应用数据</strong>​:实际的业务数据,如聊天消息、传感器数据、控制指令。</li>
</ul>
</li>
<li>总长度 = Payload length 字段的值。</li>
</ul>
</li>
</ul>
<h3 id="232-opcode-字段分类和含义">2.3.2 <code>Opcode</code> 字段分类和含义</h3>
<p><code>Opcode</code> 字段分类和含义如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140906428-1914423741.png"></p>
<p>其中,<strong>控制帧核心特性为</strong>不可分片,payload 最大长度 125 字节,优先级高于数据帧,用于管理连接状态。</p>
<h3 id="233-控制帧详解">2.3.3 控制帧详解</h3>
<p>控制帧用于管理连接状态,Opcode 为 0x08-0x0F,不可分片,payload 最大 125 字节:</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140906188-1190768715.png"></p>
<h3 id="234-分片传输">2.3.4 分片传输</h3>
<p>WebSocket 支持将一个大消息拆分为多个帧(分片)传输,核心规则如下:</p>
<ul>
<li>​<strong>非分片消息</strong>​:单帧完成传输,<code>FIN=1</code> 且 <code>Opcode≠0</code>,适合小消息。</li>
<li>​<strong>分片消息</strong>​:≥2 帧完成传输,规则:
<ul>
<li>首帧:<code>FIN=0</code>,<code>Opcode≠0</code>(定义消息类型,如文本 / 二进制);</li>
<li>中间帧:<code>FIN=0</code>,<code>Opcode=0</code>(延续帧,继承首帧的消息类型);</li>
<li>最后一帧:<code>FIN=1</code>,<code>Opcode=0</code>(延续帧,标记消息结束)。</li>
</ul>
</li>
<li>​<strong>核心价值</strong>​:
<ul>
<li>适配嵌入式设备(如 Pico W):内存有限,无需缓存完整大消息,边生成边分片发送;</li>
<li>支持流式传输:如实时音视频,边采集边发送;</li>
<li>避免大消息阻塞连接:可穿插控制帧(Ping/Pong),保证连接存活。</li>
</ul>
</li>
</ul>
<h3 id="235-压缩扩展">2.3.5 压缩扩展</h3>
<p>WebSocket 的标准扩展(RFC 7692),用 DEFLATE 算法压缩数据消息,减少带宽占用,提升传输效率。</p>
<p>在握手阶段,客户端与服务器通过 <code>Sec-WebSocket-Extensions: permessage-deflate</code> 头协商启用压缩。</p>
<p>启用压缩后,数据消息的第一个帧 <code>RSV1=1</code>,表示 payload 为压缩数据,接收方需解压。</p>
<h2 id="24-websocket-的接口">2.4 WebSocket 的接口</h2>
<p>WebSocket 接口是客户端(如浏览器)与服务器建立、管理和终止 WebSocket 连接的核心工具,下面是基于 JavaScript 实现,提供了构造函数、方法、事件、属性和常量五大类接口:</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140910034-534269613.png"><br>
<img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140907898-1310012805.png"></p>
<p>WebSocket 接口按功能可分为五大类,各类接口相互配合,构成完整的通信控制逻辑:</p>
<ul>
<li>​<strong>构造函数(<code>Constructor</code></strong>​​<strong>)</strong>​:核心用于初始化 <code>WebSocket</code> 实例,发起与服务器的连接握手,是建立 <code>WebSocket</code> 通信的起点。需传入目标 URL 和可选的子协议参数,URL 需遵循特定格式规范,否则会触发语法错误。</li>
<li>​<strong>方法(<code>Method</code></strong>​​<strong>)</strong>​:提供主动操作连接的能力,主要包括发送数据(send)和关闭连接(close)两个核心方法。发送数据时需注意数据格式和连接状态,关闭连接时需指定合法的状态码和关闭原因,避免异常。</li>
<li>​<strong>事件(<code>Event</code></strong>​​<strong>)</strong>​:用于监听 WebSocket 连接的各类状态变化和数据事件,是被动响应连接状态的核心方式。包括连接成功(open)、接收数据(message)、连接关闭(close)、连接错误(error)四种核心事件,可通过绑定事件回调函数,实现对连接状态的实时监控和业务逻辑处理。</li>
<li>​<strong>属性(<code>Attribute</code></strong>​​<strong>)</strong>​:用于配置或获取 WebSocket 连接的相关参数,主要包括二进制数据接收类型(binaryType),可根据业务需求修改,适配不同的二进制数据处理场景。</li>
<li><strong>​只读​属性(</strong>​​<strong><code>Read-only attribute</code>)</strong>​:用于获取 WebSocket 连接的不可修改状态和参数,如连接 URL、待发送数据字节数、协商的子协议、连接状态等,为开发调试和状态判断提供依据。</li>
<li>​<strong>常量(<code>Constant</code></strong>​​<strong>)</strong>​:定义了 WebSocket 连接的四种核心状态,对应不同的通信阶段,可通过只读属性 readyState 获取当前状态,用于判断连接是否可收发数据、是否处于关闭过程等。</li>
</ul>
<p>需要注意的是,<strong>所有接口的使用都依赖于 ​</strong>​<strong><code>WebSocket</code></strong>​<strong>​ 连接的当前状态(readyState),例如发送数据(send)仅能在连接处于“已连接”(OPEN)状态时调用,否则会触发异常;关闭连接(close)在不同状态下的行为也不同,</strong>需严格遵循接口规范。</p>
<h1 id="三async_websocket_client-库介绍">三、async_websocket_client 库介绍</h1>
<h2 id="31-基本概念">3.1 基本概念</h2>
<p>这是一个​<strong>专为 MicroPython 设计的轻量级异步 WebSocket 客户端库</strong>​,实现了 <code>WebSocket</code> 协议(<code>RFC 6455</code>)的核心功能,支持 <code>ws://</code>(明文)和 <code>wss://</code>(<code>TLS</code> 加密)连接:</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140909055-675158449.png"></p>
<p><strong>​源码地址:​</strong>https://pypi.org/project/micropython-async-websocket-client</p>
<p>提供了下面的能力:</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140904465-464791211.png"></p>
<p>其核心类是:<code>AsyncWebsocketClient</code></p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140908880-1677129538.png"></p>
<p>其中初始化方法入口参数为:</p>
<pre><code class="language-Bash">def __init__(self, ms_delay_for_read: int = 5)
</code></pre>
<ul>
<li><code>ms_delay_for_read</code>:非阻塞 socket 读取的轮询间隔(毫秒),硬件性能越强可设越小,默认 5ms 适配多数 MCU。</li>
</ul>
<p>核心方法如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140902841-1216812038.png"></p>
<p>常用常量定义如下:</p>
<ul>
<li><strong>​操作码​(</strong>​​<strong><code>Opcodes</code>)</strong>​:<code>OP_TEXT</code>(文本帧)、<code>OP_BYTES</code>(二进制帧)、<code>OP_PING</code>/<code>OP_PONG</code>(心跳)、<code>OP_CLOSE</code>(关闭)</li>
<li>​<strong>关闭码(<code>Close Codes</code></strong>​​<strong>)</strong>​:<code>CLOSE_OK</code>(正常关闭)、<code>CLOSE_GOING_AWAY</code>(客户端断开)、<code>CLOSE_TOO_BIG</code>(消息过大)等,符合 <code>RFC 6455</code> 标准</li>
</ul>
<h2 id="32-快速测试">3.2 快速测试</h2>
<p>这里,我们使用树莓派 Pico2w 开发板进行测试,借助外部网站:<code>websocket-tester</code></p>
<p>这是 See-Tool 工具箱中「网络协议」分类下的一款 ​<strong>WebSocket 在线测试工具</strong>​,可输入 <code>wss://</code>​<code>ws.postman-echo.com/raw</code> 等服务地址建立连接,支持手动发送消息或定时自动发包,并在右侧调试面板实时查看收发日志与 JSON 解析,适合接口联调、实时通信排错和 WebSocket 连通性验证。</p>
<p>地址是:https://see-tool.com/websocket-tester</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140905634-109775750.png"></p>
<p>这是基于 Postman 官方 WebSocket 回显服务器的标准测试场景,核心逻辑是 「客户端发送什么内容,服务器就原样回显什么内容。</p>
<p>这里,我们首先在 upypi 上搜索 WebSocket 库并安装:</p>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140908942-585200373.png"></p>
<p>https://upypi.net/en/pkgs/async_websocket_client/1.0.0</p>
<p>复制安装命令:</p>
<pre><code class="language-Bash">mpremote mip install https://upypi.net/pkgs/async_websocket_client/1.0.0
</code></pre>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140908455-1980141804.png"></p>
<p>这里,我们将下面 main.py 文件烧录到树莓派 Pico2w 中:</p>
<pre><code class="language-Python"># Python env   : MicroPython v1.23.0
# -*- coding: utf-8 -*-
# @Time    : 2026/04/12
# @Author:
# @File    : main.py
# @Description : Async WebSocket client usage example for MicroPython

# ======================================== 导入相关模块 =========================================

import network
import asyncio
import machine
import time
from async_websocketclient import AsyncWebsocketClient# 导入你的WebSocket库

# ======================================== 全局变量 ============================================

# 配置WiFi (请替换为你的WiFi信息)
WIFI_SSID   = "Y/OURSPACE"
WIFI_PASSWORD = "qc123456789"

# 测试服务器地址 (Postman Echo)
TEST_URL = "wss://ws.postman-echo.com/raw"

# ======================================== 功能函数 ============================================

def connect_wifi():
    """
    连接 WiFi 并返回网络对象。

    Returns:
      network.WLAN: 已连接的 WLAN 对象;连接失败时返回 None。

    ==========================================

    Connect to WiFi and return the network object.

    Returns:
      network.WLAN: Connected WLAN object; None if connection fails.
    """
    # 初始化WiFi为STA模式(客户端模式)
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    # 避免重复连接
    if not wlan.isconnected():
      print(f"Connecting to WiFi: {WIFI_SSID}")
      wlan.connect(WIFI_SSID, WIFI_PASSWORD)

      # 等待连接(最多等待10秒)
      timeout = 10
      while not wlan.isconnected() and timeout &gt; 0:
            time.sleep(1)# 替换utime.sleep为time.sleep
            timeout -= 1
            print(f"Connecting... {timeout}s remaining")

      if wlan.isconnected():
            # 打印连接信息(IP、子网掩码、网关、DNS)
            ip_info = wlan.ifconfig()
            print(f"WiFi connected")
            print(f"IP: {ip_info}")
            print(f"Gateway: {ip_info}")
            print(f"DNS: {ip_info}")
      else:
            print("WiFi connection failed")
            return None
    else:
      print("WiFi already connected")

    return wlan

async def websocket_test():
    """
    WebSocket 测试主逻辑,依次完成连接、发送、接收和关闭。

    ==========================================

    Main WebSocket test logic: connect, send, receive, and close in sequence.
    """
    global ws_client
   
    try:
      # 1. 连接WiFi
      if not connect_wifi():
            return

      # 2. 连接WebSocket服务器
      print("Connecting: {}".format(TEST_URL))
      await ws_client.handshake(TEST_URL)
      print("WebSocket connected (WSS)")

      # 3. 发送测试消息
      test_message = "Hello from Pico2W!"
      print("Sending: {}".format(test_message))
      await ws_client.send(test_message)

      # 4. 循环接收消息
      for count in range(5):
            print("\nWaiting for message ({})...".format(count + 1))
            response = await ws_client.recv()

            if response:
                print("Received: {}".format(response))
                # 发送下一条消息
                next_msg = "Pico count: {}".format(count + 1)
                await ws_client.send(next_msg)
                await asyncio.sleep(1)
            else:
                print("No message received, connection may be closed")
                break

      # 5. 关闭连接
      print("\nTest complete, closing connection...")
      await ws_client.close()
      print("Connection closed")

    except Exception as e:
      print("\nException: {} - {}".format(type(e).__name__, e))
      await ws_client.close()

# ======================================== 自定义类 ============================================

# ======================================== 初始化配置 ===========================================

# 初始化WebSocket客户端
ws_client = AsyncWebsocketClient(ms_delay_for_read=5)

# ========================================主程序===========================================

# 运行主程序
if __name__ == "__main__":
    asyncio.run(websocket_test())
</code></pre>
<p>烧录代码,运行结果如下:<br>
<img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140908579-1780322108.png"></p>
<p>这是树莓派 Pico2W 成功运行编写的异步 WebSocket 客户端库的测试结果:</p>
<p>代码成功连接到 <code>wss://</code>​<code>ws.postman-echo.com/raw</code> 回显服务器,完成了 WiFi 连接、WebSocket 握手、文本消息发送与接收的全流程,Pico 依次发送计数消息并收到服务器的正确回显,最后正常关闭连接。</p>
<blockquote>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140904032-2018495618.png"></p>
</blockquote>
<blockquote>
<p><img src="https://img2024.cnblogs.com/blog/2591203/202604/2591203-20260414140910773-675499161.png"></p>
</blockquote><br><br>
来源:https://www.cnblogs.com/FreakEmbedded/p/19865128
頁: [1]
查看完整版本: 你不知道的 WebSocket:服务器实时推送的核心协议,新手也能秒会