news 2026/9/20 11:58:37

MicroPython ESP8266 网络编程实战:基于 TCP Socket 的客户端与服务器开发指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MicroPython ESP8266 网络编程实战:基于 TCP Socket 的客户端与服务器开发指南
  • 嵌入式
  • 语言运行时
  • 编程语言
  • 解释器
  • 编译器
  • 物联网
  • 系统编程

【免费下载链接】micropython

MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems

项目地址:https://gitcode.com/gh_mirrors/mi/micropython
点击查看免费下载

导读

TCP Socket 是互联网应用最基础的通信构件——它能够在两个网络设备之间建立一条可靠的字节流通道,HTTP、telnet、SSH、SMTP 等上层协议全部构建在它之上。本教程基于 MicroPython 官方 ESP8266 教程的《Network - TCP sockets》章节,以乐鑫 ESP8266 开发板为核心,手把手带你完成三个实战项目:通过 telnet 服务流式拉取 Star Wars Ascii 动画、用 HTTP GET 请求下载网页、以及在本机上搭建一个展示全部 GPIO 引脚状态的 HTTP 服务器。读完本文,你将掌握socket模块的核心 API 用法、getaddrinfo地址解析的正确姿势、客户端与服务器两种编程模型,并能直接在 REPL 或boot.py中落地运行。

前置要求:本文所有示例都假设你的 ESP8266 已经通过network.WLAN成功接入 WiFi(可参考 ESP8266 网络基础教程)。Socket 模块的完整 API 参考见 socket 模块文档。

TCP Socket 是什么:互联网的"可靠字节流"

在深入代码之前,先建立对 TCP Socket 的直观认识:

  • **Socket(套接字)**是网络设备上的一个端点。当两个设备上的 socket 连接在一起,通信就可以进行。可以把 socket 想象成两台设备之间的一条虚拟管道。
  • 可靠字节流:与 UDP 的无连接、不保证送达不同,TCP 保证数据按序、不丢失地传输,因此适合承载 HTTP、telnet、SSH 这类对数据完整性要求高的协议。
  • 端口(Port):每个上层协议都绑定一个固定的整数端口,例如 HTTP 是 80、telnet 是 23、SSH 是 22。给定"IP 地址 + 端口号",你就能定位并连接到一个远程设备上的特定服务。

在 MicroPython 中,socket模块提供对 BSD socket 接口的访问。它与 CPython 最大的差异在于:MicroPython 的 socket 对象直接实现了 stream(文件类)接口,而 CPython 需要用makefile()转换(在 MicroPython 中makefile()仍然存在,但只是空操作,socket 模块文档 明确指出调用它仅为保持与 CPython 的兼容性)。

地址解析:getaddrinfo 的正确用法

所有网络程序的第一步都是把"主机名 + 端口"解析成可连接的地址。MicroPython 的规范做法是使用socket.getaddrinfo(host, port)

>>> import socket >>> addr_info = socket.getaddrinfo("towel.blinkenlights.nl", 23)

这个函数返回的是一个5 元组列表,每个元组结构为(family, type, proto, canonname, sockaddr)。以addr_info[0][-1]取到最后一个元素,就是(ip, port)形式的可直接连接的地址:

>>> addr = addr_info[0][-1]

如果你在 REPL 里分别输入addr_infoaddr,会直观看到两者保存的信息差异——前者是包含地址族、socket 类型、协议等完整信息的列表,后者只是我们实际需要的 IP 与端口对。

从源码层面看,extmod/modsocket.c 中mod_socket_getaddrinfo的实现揭示了它的内部行为:

  1. 先尝试把主机名当 IP 地址解析:如果传入的字符串本身就是点分十进制形式(如"10.10.10.10"),直接通过netutils_parse_ipv4_addr解析,跳过 DNS 查询;
  2. 否则遍历已注册的网卡(NIC):在所有mod_network_nic_list中的网卡里寻找实现了gethostbyname协议的网卡执行 DNS 查询,找不到可用网卡时抛出OSError("no available NIC")
  3. 组装 5 元组返回:固定返回AF_INETSOCK_STREAM类型的地址元组。

getaddrinfo还支持最多 6 个参数(host, port, af, type, proto, flags),用于过滤地址族与 socket 类型,例如socket.getaddrinfo(host, 80, 0, socket.SOCK_STREAM)可保证返回的地址适合流式连接。官方文档建议:在编写可移植程序时始终使用getaddrinfo,即使地址是数字形式也应如此——这是最高效且最可移植的地址处理方式。

仓库中的 tests/net_inet/getaddrinfo.py 覆盖了该函数的各类边界情况:不存在的域名、非法主机名、纯 IP 地址、0.0.0.0以及有效域名,全部用try/except OSError包裹验证其异常行为,可作为你测试网络解析的参考模板。

实战一:连接 telnet 服务,流式下载 Star Wars Ascii 动画

blinkenlights.nl 网站的 Star Wars Asciimation 服务使用 telnet 协议(端口 23),向所有连接者流式推送 ASCII 字符动画。它不需要任何认证,连接即可开始接收数据,非常适合作为 TCP 客户端入门的第一个练习。

第一步:导入 socket 模块

>>> import socket

第二步:解析服务器地址

>>> addr_info = socket.getaddrinfo("towel.blinkenlights.nl", 23) >>> addr = addr_info[0][-1]

第三步:创建 socket 并连接

>>> s = socket.socket() >>> s.connect(addr)

第四步:循环接收并打印数据

>>> while True: ... data = s.recv(500) ... print(str(data, 'utf8'), end='') ...

当这个循环开始执行,动画就会一帧帧地在终端上"播放"出来,按Ctrl-C即可中断。

这段代码演示了 TCP 客户端的完整四步流程:解析地址 → 创建 socket → connect → 循环 recv。几个值得注意的细节:

  • s.recv(500)指定每次最多读取 500 字节——这是缓冲区上限,实际返回的字节数可能更少,因此循环不断调用直到服务端关闭连接;
  • recv返回的是bytes对象,需要用str(data, 'utf8')解码为文本;end=''避免 print 自动换行破坏动画帧;
  • 这段代码同样可以在 PC 上的标准 Python 中运行(MicroPython 的 socket API 与 CPython 高度兼容),非常适合在电脑上先调试通网络服务再移植到开发板。

从源码看,extmod/modsocket.c 中socket_recv会先检查 socket 是否已绑定网卡(未连接时抛ENOTCONN),随后调用网卡协议层的recv实现,并将返回的字节填充到vstr中构造 bytes 对象返回;若对端关闭连接返回 0,则返回空 bytes。

实战二:HTTP GET 请求——向 Web 服务器下载网页

HTTP 协议运行在 80 端口,与 telnet 的"连上就收"不同,客户端必须先发送一个GET请求,指明要获取的页面路径,服务器才会返回内容。下面的函数封装了完整的 HTTP 下载流程:

def http_get(url): import socket _, _, host, path = url.split('/', 3) addr = socket.getaddrinfo(host, 80)[0][-1] s = socket.socket() s.connect(addr) s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8')) while True: data = s.recv(100) if data: print(str(data, 'utf8'), end='') else: break s.close()

调用方式:

>>> http_get('http://micropython.org/ks/test.html')

执行后网页 HTML 会被逐块打印到控制台。逐行拆解这个函数:

步骤代码作用
解析 URL_, _, host, path = url.split('/', 3)http://micropython.org/ks/test.html拆成 host 与路径。split('/', 3)按斜杠最多切 3 次,跳过http:与空串,得到host = 'micropython.org'path = 'ks/test.html'
解析地址addr = socket.getaddrinfo(host, 80)[0][-1]解析主机名到(ip, 80)地址对
建立连接s.connect(addr)TCP 三次握手
发送请求s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' ...))构造 HTTP/1.0 GET 请求报文。\r\n是 HTTP 的行结束符,末尾空行表示请求头结束;Host头用于虚拟主机
接收响应while True: data = s.recv(100)分块读取响应;当recv返回空(服务端关闭连接)时退出循环
释放资源s.close()显式关闭 socket

注意 HTTP 报文格式:请求行GET /path HTTP/1.0、请求头Host: hostname,以及请求头与正文之间必须有一个空行(\r\n\r\n),这是 HTTP 规范要求、极易遗漏的细节。bytes(..., 'utf8')将字符串编码为字节流发送。

http_get是一次性函数;如果你要复用连接发送/接收多次数据,应使用s.sendall(data)(保证全部字节发送完毕)配合s.read()/s.readline()等流式方法,这些方法在 MicroPython 中由 stream 接口直接提供(见 extmod/modsocket.c 中 socket 的方法表,read/readline/write直接映射到通用 stream 实现)。

实战三:HTTP 服务器——让 ESP8266 成为 Web 服务器

现在把角色反转:让 ESP8266 充当服务器,向浏览器提供页面。下面的代码创建一个简单的 HTTP 服务器,页面是一张展示所有 GPIO 引脚电平状态的表格:

import machine pins = [machine.Pin(i, machine.Pin.IN) for i in (0, 2, 4, 5, 12, 13, 14, 15)] html = """<!DOCTYPE html> <html> <head> <title>ESP8266 Pins</title> </head> <body> <h1>ESP8266 Pins</h1> <table border="1"> <tr><th>Pin</th><th>Value</th></tr> %s </table> </body> </html> """ import socket addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] s = socket.socket() s.bind(addr) s.listen(1) print('listening on', addr) while True: cl, addr = s.accept() print('client connected from', addr) cl_file = cl.makefile('rwb', 0) while True: line = cl_file.readline() if not line or line == b'\r\n': break rows = ['<tr><td>%s</td><td>%d</td></tr>' % (str(p), p.value()) for p in pins] response = html % '\n'.join(rows) cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n') cl.send(response) cl.close()

这个例子是标准的 TCP 服务器四步流程:bind → listen → accept → 循环处理。服务器核心要素分析:

  • 绑定通配地址socket.getaddrinfo('0.0.0.0', 80)让服务器监听所有网络接口(无论板子从路由器获得什么 IP,客户端都能访问),bind(addr)将 socket 绑定到该地址与 80 端口。注意在 ESP8266 上绑定 80 端口前应确认没有其他服务占用该端口。
  • 监听队列s.listen(1)允许 1 个未处理的连接请求排队,多余的连接会被拒绝。源码中socket_listen(extmod/modsocket.c)会先将负的 backlog 钳制为 0。
  • accept 返回连接s.accept()阻塞等待客户端连接,返回(cl, addr)二元组——cl是专用于该连接的新 socket 对象,addr是客户端地址(源码见 extmod/modsocket.c,新 socket 继承父 socket 的地址族、类型与协议)。
  • 读取请求头cl.makefile('rwb', 0)创建与 socket 关联的文件对象('rwb'表示可读可写二进制模式,0表示无缓冲;MicroPython 会忽略 buffering 参数并视为 0)。readline()逐行读取 HTTP 请求头,遇到空行b'\r\n'即请求头结束——服务器不必解析请求内容,只需读出并丢弃请求头即可返回响应。
  • 构造并发送响应:先用p.value()实时读取每个 GPIO 引脚电平,生成 HTML 表格行,再拼接完整页面。响应报文由状态行HTTP/1.0 200 OK、响应头Content-type: text/html、空行与正文组成。
  • 关闭连接:处理完一个请求后cl.close()关闭该连接;注意makefile()返回的文件对象被关闭时会同时关闭底层 socket(socket 模块文档 中的明确提示)。

浏览器访问http://<ESP8266的IP>/即可看到带引脚的实时状态表格。在 ESP8266 上运行 HTTP 服务器时应留意内存与功耗:请求频率高时可以在循环中加入machine.idle()或适当延时,避免长时间满负荷运行。

服务器示例的测试验证

仓库中 tests/multi_net/tcp_data.py 是一个双实例 TCP 服务器/客户端对测用例,可视为本示例的精简版本:服务器端bind → listen → accept → recv → send,客户端connect → send → recv,完整覆盖了上述 API 的调用链,是理解 MicroPython 网络栈行为的绝佳参考。

进阶:TCP 编程的可靠性要点

1. 用流式接口代替原生方法

MicroPython 的 socket 对象本身就是一个流对象,read()readline()write()直接可用。官方推荐用write()替代sendall()(阻塞模式下二者都保证"无短写",非阻塞模式下sendall()行为未定义而write()会返回实际写入字节数),用read()替代recv()可享受"无短读"语义。这在处理 HTTP 这类需要完整读取一行/一段数据的协议时尤其方便。

2. 超时与非阻塞

settimeout(value)设置阻塞操作超时(单位秒):非零值超时后抛OSError0切换为非阻塞模式,None恢复阻塞模式。setblocking(flag)是其快捷方式:setblocking(True)等价settimeout(None)setblocking(False)等价settimeout(0)(源码见 extmod/modsocket.c,超时值在内部换算为毫秒存储)。

更可移植的等待方案是使用select.poll():它可以同时轮询多个流对象,poller.poll(1000)以毫秒为单位等待就绪事件,从而优雅地实现"非阻塞 + 超时"语义。ESP8266 上如果某个网络操作卡死,超时机制是保证程序健壮性的关键。

3. 异常处理

MicroPython 没有 CPython 的socket.errorsocket.gaierror,一律抛OSErrorgetaddrinfo的解析错误用负数错误码表示,与 errno 的正数错误码分属不同命名空间。因此:

  • 解析域名时用except OSError捕获(tests/net_inet/getaddrinfo.py 展示了这一模式);
  • 通用的except OSError:写法在 MicroPython 与 CPython 下都能工作,保持代码双端兼容。

4. 资源管理

socket 对象在垃圾回收时会自动关闭,但官方明确建议用完后立即显式close(),尤其在服务器场景(每个accept()产生一个新 socket,若不关闭会迅速耗尽内存)。MicroPython 的 socket 对象带有析构器(mp_obj_malloc_with_finaliser,见 extmod/modsocket.c),但依赖 GC 释放资源在内存紧张的 ESP8266 上并不明智。

5. 复用地址

服务器重启时可能遇到address in use错误,可在bind前执行s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)允许地址复用——tests/multi_net/tcp_data.py 的服务器端正是这么做的,这也是嵌入式 Web 服务器开发中的常见惯例。

总结

场景核心 API 流程关键点
TCP 客户端(telnet 拉流)getaddrinfo → socket() → connect → recv 循环无需认证,连上即收;Ctrl-C中断
HTTP 客户端(网页下载)getaddrinfo → socket → connect → send(GET) → recv 循环HTTP 报文格式:请求行 + Host 头 + 空行
HTTP 服务器(GPIO 看板)getaddrinfo('0.0.0.0') → socket → bind → listen → accept → 收发 → close请求头以空行结束;每个连接独立 socket 需关闭

通过这三个循序渐进的项目,你已经掌握了 MicroPython 在 ESP8266 上使用 TCP socket 完成"下载数据"与"提供服务"两种角色的全部要点:地址解析用getaddrinfo、客户端记住"解析-连接-收发"三步、服务器记住"绑定-监听-接受-关闭"四步、始终显式关闭 socket。这套模式适用于 HTTP、MQTT、WebSocket、SSH 等几乎所有基于 TCP 的协议,是进一步开发 ESP8266 物联网应用的基石。建议接着阅读 ESP8266 教程索引 中的 文件系统 与 引脚控制 章节,将网络能力与存储、GPIO 结合,构建更完整的嵌入式应用。

  • 嵌入式
  • 语言运行时
  • 编程语言
  • 解释器
  • 编译器
  • 物联网
  • 系统编程

【免费下载链接】micropython

MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems

项目地址:https://gitcode.com/gh_mirrors/mi/micropython
点击查看免费下载

相关推荐

上一篇:🔍 Review RTK
下一篇:CloudBeaver 与 DBeaver 对比分析:选择最适合你的数据库工具

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 11:57:37

Linux物理安全攻防:从引导绕过到全盘加密的六条攻击路径与分层防御

1. 物理接触即失守&#xff1a;Linux 设备落入他人之手后的真实威胁模型很多人对 Linux 安全有个根深蒂固的误解&#xff1a;只要系统打了补丁、开了防火墙、密码设得够复杂&#xff0c;这台机器就是安全的。这个认知在远程攻击场景下基本成立&#xff0c;但一旦设备物理落到别…

作者头像 李华
网站建设 2026/9/20 11:57:23

船舶运动仿真Matlab建模:坐标转换与回转试验验证

简介&#xff1a;基于MATLAB的船舶运动仿真资源&#xff0c;面向船舶运动控制、建模与仿真的学习者及工程师&#xff0c;可用于船舶操纵性、推进控制等场景的仿真分析&#xff0c;覆盖船舶运动模型构建、控制器设计、仿真结果可视化等完整环节。压缩包共12个文件&#xff0c;包…

作者头像 李华
网站建设 2026/9/20 11:56:48

Sage-Husa自适应卡尔曼滤波:海洋磁测海浪磁场噪声抑制实战

简介&#xff1a;这是一份基于Sage-Husa自适应卡尔曼滤波器的海浪磁场噪声抑制Matlab项目源码&#xff0c;面向从事海洋电磁探测、水下目标磁异常检测或信号处理方向的研究者&#xff0c;也适合有一定Matlab基础的新手学习。代码完整覆盖从海浪磁场噪声产生、PSD功率谱分析到自…

作者头像 李华
网站建设 2026/9/20 11:53:22

PHP+MySQL新闻系统最小可行原型解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华