MicroPython socket 模块实战指南:BSD 套接字接口的地址格式、API 解析与底层实现
2026/9/21 16:11:26 网站建设 项目流程
  • 嵌入式
  • 语言运行时
  • 编程语言
  • 解释器
  • 编译器
  • 物联网
  • 系统编程

【免费下载链接】micropython

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

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

socket模块为 MicroPython 提供访问 BSD 套接字接口的能力,是嵌入式设备实现 TCP/UDP 网络通信、构建客户端与服务端程序的基础模块。本文以仓库文档 docs/library/socket.rst 为主体,结合 extmod/modsocket.c 等核心源码与tests/下的测试用例,系统讲解地址格式、全部函数与类方法、与 CPython 的差异以及错误处理约定,帮助读者写出既能在 MicroPython 上高效运行、又能保持 CPython 兼容的网络代码。

模块定位与 CPython 核心差异

MicroPython 的socket模块与 CPython 同名模块在接口上高度一致,但有一处根本性的设计差异:MicroPython 的 socket 对象直接实现了 stream(文件类)接口。也就是说,你可以直接在 socket 对象上调用read()write()readline()readinto()等方法,而 CPython 中必须先通过makefile()把 socket 转换为文件对象才能使用这些方法。

在源码层面,这一设计体现在 extmod/modsocket.c:socket 类型注册了mp_stream_p_t协议(包含readwriteioctl三个回调),并在方法表中把read/readinto/readline/write直接映射到通用流实现mp_stream_read_objmp_stream_readinto_objmp_stream_unbuffered_readline_objmp_stream_write_obj(见 extmod/modsocket.c)。

为了保证与 CPython 的兼容性,MicroPython 仍然提供makefile()方法,但它在底层是一个 no-op——直接返回 socket 对象自身(见 extmod/modsocket.c)。因此在编写需要同时运行于两端的代码时,照常调用makefile()不会出错,但也不必依赖它。

Socket 地址格式:getaddrinfo 优先

socket模块的"原生"地址格式是getaddrinfo()返回的不透明数据类型。MicroPython 官方强烈建议:无论目标是域名还是数字 IP 地址,都通过getaddrinfo()解析地址,这是最省内存、最高效也最可移植的方式:

import socket # 域名必须通过 getaddrinfo 解析 sockaddr = socket.getaddrinfo('www.micropython.org', 80)[0][-1] # 即使是数字地址也必须用 getaddrinfo 处理 sockaddr = socket.getaddrinfo('127.0.0.1', 80)[0][-1] # 现在可以直接使用该地址 sock.connect(sockaddr)

getaddrinfo()返回的 5 元组结构为(family, type, proto, canonname, sockaddr),取最后一个元素[-1]即可得到可直接传给connect()/bind()的地址。

元组格式(CPython 兼容捷径)

出于 CPython 兼容考虑,socket模块也接受元组形式的地址,但有以下限制:

  • IPv4 元组(ipv4_address, port),其中ipv4_address是点分十进制字符串(如"8.8.8.8"),port是 1~65535 的整数。元组中的地址不接受域名,必须先用socket.getaddrinfo()解析。
  • IPv6 元组(ipv6_address, port, flowinfo, scopeid),其中ipv6_address是冒号分隔的十六进制字符串(如"2001:db8::1"),port为 1~65535 的整数,flowinfo必须为 0,scopeid是链路本地地址的接口作用域标识。同样不接受域名。IPv6 是否可用取决于具体的 MicroPython 移植版本(port)。

需要注意,MicroPython 移植版(port)众多:socket模块可能内建,也可能需要从micropython-lib单独安装(例如 Unix 移植版就是后者);且部分移植版在元组格式中只接受数字地址,解析域名仍必须使用getaddrinfo。因此官方给出两条经验法则:

  • 编写可移植应用时,一律使用getaddrinfo
  • 元组格式只作为快速原型和交互式 REPL 实验的捷径,且需确认当前移植版支持。

从源码看,connect()/bind()/sendto()在底层正是通过netutils_parse_inet_addr()解析传入的地址并提取 IP 与端口(见 extmod/modsocket.c 与 extmod/modsocket.c),随后再调用 NIC 协议层完成实际操作。

模块级函数

getaddrinfo(host, port, af=0, type=0, proto=0, flags=0, /)

将 host/port 参数翻译为一组 5 元组,每个元组包含创建连接到该服务所需的所有参数。可选参数aftypeproto(含义与socket()构造函数相同)用于过滤返回的地址类型;若某参数未指定或为 0,则可能返回所有地址组合,需要调用方自行过滤。

典型用法对比:

import socket s = socket.socket() # 不推荐:假定未指定 type 时会返回 SOCK_STREAM 地址,这未必成立 s.connect(socket.getaddrinfo('www.micropython.org', 80)[0][-1]) # 推荐:显式过滤,保证拿到可进行流式连接的地址 s.connect(socket.getaddrinfo('www.micropython.org', 80, 0, socket.SOCK_STREAM)[0][-1])

与 CPython 的差异(错误处理):CPython 在解析失败时抛出socket.gaierrorOSError子类);MicroPython 没有socket.gaierror,直接抛出OSError。且getaddrinfo()的错误号自成一套命名空间,与errno模块的错误号不一定对应。MicroPython 用负数表示getaddrinfo()错误、正数表示标准系统错误来加以区分(可通过异常的e.args[0]取得错误号)。负数值的约定属于临时性细节,未来可能变化。

源码实现细节(见 extmod/modsocket.c):实现会先尝试用netutils_parse_ipv4_addr()判断 host 是否已是 IP 形式;若不是,则遍历已注册的 NIC 列表,调用支持gethostbyname的 NIC 完成 DNS 解析,若没有任何可用 NIC 则抛出OSError("no available NIC")。此外,传入的过滤参数如果超出实现支持的范围(例如要求 IPv6 或非 STREAM 类型),会触发一个RuntimeWarning"unsupported getaddrinfo constraints",而不会静默失败。仓库中的 tests/net_inet/getaddrinfo.py 覆盖了不存在的域名、非法主机名、纯 IP、0.0.0.0、真实域名等多种解析场景。

inet_ntop(af, bin_addr)

将指定地址族af的二进制网络地址bin_addr转换为文本表示:

>>> socket.inet_ntop(socket.AF_INET, b"\x7f\0\0\1") '127.0.0.1'

inet_pton(af, txt_addr)

将指定地址族af的文本网络地址txt_addr转换为二进制表示:

>>> socket.inet_pton(socket.AF_INET, "1.2.3.4") b'\x01\x02\x03\x04'

这两个函数并非所有移植版都提供。例如 Unix 移植版在 ports/unix/modsocket.c 中直接基于系统的inet_pton()/inet_ntop()实现并注册到模块命名空间;而通用网络实现 extmod/modsocket.c 的模块全局表中并没有这两个函数。使用前请确认目标移植版的支持情况。

常量清单

socket模块提供以下常量:

常量说明
AF_INET/AF_INET6地址族类型;可用性取决于具体移植版
SOCK_STREAM/SOCK_DGRAM套接字类型(流 / 数据报)
IPPROTO_UDP/IPPROTO_TCPIP 协议号;可用性取决于具体移植版
SOL_*套接字选项层级,作为setsockopt()的第一个参数;具体清单取决于移植版
SO_*套接字选项,作为setsockopt()的第二个参数;具体清单取决于移植版
IPPROTO_SEC(仅 WiPy)特殊协议值,用于创建 SSL 兼容套接字

关于IPPROTO_UDP/IPPROTO_TCP有一个重要提示:调用socket.socket()通常不需要也不建议指定协议号(部分移植版甚至可能没有IPPROTO_*常量),因为套接字类型会自动选择协议——SOCK_STREAM自动选IPPROTO_TCPSOCK_DGRAM自动选IPPROTO_UDP。这两个常量的实际用途是作为setsockopt()的参数。

通用实现 extmod/modsocket.c 中实际注册的模块级常量包括:AF_INETAF_INET6SOCK_STREAMSOCK_DGRAMSOCK_RAW,以及SOL_SOCKETSO_REUSEADDRSO_BROADCASTSO_KEEPALIVESO_SNDTIMEOSO_RCVTIMEOIPPROTO_*系列在通用实现中被注释掉,仅在部分移植版中可见。这也解释了文档中"清单取决于移植版"的表述。

socket 类

构造函数:socket(af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP, /)

创建指定地址族、类型与协议号的套接字。proto多数情况下无需指定(见上文常量说明),由type自动选择协议:

# 创建 STREAM TCP 套接字 socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建 DGRAM UDP 套接字 socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

源码中的默认值是domain=AF_INET, type=SOCK_STREAM, proto=0,套接字初始处于"未绑定任何 NIC"状态(见 extmod/modsocket.c);直到执行bind()/connect()/sendto()等操作时,才会依据目标 IP 自动选择合适的网络接口(NIC)并真正打开底层套接字(socket_select_nic(),见 extmod/modsocket.c)。这也是嵌入式环境"先建对象、后绑网卡"的懒加载设计。

连接与服务端方法

  • close():关闭套接字并释放全部资源,之后对该对象的一切操作都会失败;若协议支持,对端会收到 EOF 指示。套接字在垃圾回收时会被自动关闭,但官方建议用完立即显式close()。底层由通用流关闭回调实现(mp_stream_close_obj,见 extmod/modsocket.c)。
  • bind(address):将套接字绑定到address,套接字不得重复绑定。
  • listen([backlog]):使服务端套接字进入监听状态。backlog若指定则必须不小于 0(更小会被强制设为 0),表示系统在拒绝新连接前允许排队的未接受连接数;不指定时使用合理默认值。源码中默认值来自MICROPY_PY_SOCKET_LISTEN_BACKLOG_DEFAULT,且负值会被截为 0(见 extmod/modsocket.c)。
  • accept():接受一个连接。套接字必须已绑定并处于监听状态。返回(conn, address)二元组:conn是可用于收发数据的新套接字对象,address是对端绑定的地址。源码实现中,新套接字会继承父套接字的地址族、类型与协议,并复用父套接字的 NIC(见 extmod/modsocket.c)。
  • connect(address):连接到远端address。源码会先解析地址、自动选择 NIC,然后调用 NIC 的connect并把状态置为MOD_NETWORK_SS_CONNECTED(见 extmod/modsocket.c)。

一个最小可用的 TCP 服务端骨架:

import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(socket.getaddrinfo('0.0.0.0', 8080)[0][-1]) s.listen(5) while True: conn, addr = s.accept() # 处理连接 ... conn.close()

数据收发方法

  • send(bytes):向已连接的远端发送数据,返回实际发送的字节数——可能小于数据长度("短写" short write)。

  • sendall(bytes):逐块连续发送,保证把数据全部发出,行为与send()不同。在非阻塞套接字上其行为未定义,因此 MicroPython 官方建议改用write()方法:它在阻塞套接字上同样保证"无短写",在非阻塞套接字上则返回实际发送的字节数。

    源码实现印证了这一点(见 extmod/modsocket.c):阻塞模式下sendall进入while (bufinfo.len != 0)循环反复调用 NIC 的send并推进缓冲区指针,直至全部发完;非阻塞模式(timeout == 0)下只发一次,若未能一次发完则直接抛出MP_EAGAIN

  • recv(bufsize, [flags]):接收数据,返回 bytes 对象,最多接收bufsize字节。多数移植版支持可选flags参数,其常量与 CPython 含义相同;所有支持flags的移植版都支持MSG_PEEKMSG_DONTWAIT

  • sendto(bytes, address):向address指定的目标发送数据,套接字本身不应已连接(典型 UDP 用法)。

  • recvfrom(bufsize, [flags]):接收数据,返回(bytes, address)二元组;flags说明同recv。源码实现会把(数据, (IP, 端口))封装成二元组返回(见 extmod/modsocket.c)。

一个最小的 UDP 收发示例:

import socket # 接收端 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(socket.getaddrinfo('0.0.0.0', 9000)[0][-1]) data, addr = s.recvfrom(1024) # 发送端 s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s2.sendto(b'hello', socket.getaddrinfo('127.0.0.1', 9000)[0][-1])

套接字选项:setsockopt(level, optname, value)

设置指定套接字选项的值,所需符号常量(SO_*等)定义于模块中。value可以是整数,也可以是表示缓冲区的 bytes-like 对象。源码实现支持三种取值形态:整数直接按 4 字节传递;SO_*为 20 且值为None时传递空指针;可调用对象则作为回调传递(见 extmod/modsocket.c)。典型用途如:

s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 地址重用

超时与阻塞模式:settimeout / setblocking

settimeout(value):注意并非每个移植版都支持此方法。value为非负浮点数(秒)或None

  • 非零值:后续套接字操作在超时时间内未完成即抛出OSError
  • 0:切换到非阻塞模式;
  • None:切换到阻塞模式。

源码中的映射(见 extmod/modsocket.c):None映射为内部-1(阻塞),数值以秒为单位乘以 1000 转为毫秒传给 NIC 层。测试 tests/extmod/socket_udp_nonblock.py 验证了settimeout(0)之后,在无数据到达时调用recv(1)会抛出errno.EAGAIN

setblocking(flag)flag为假值时设为非阻塞,否则设为阻塞。它是settimeout()的快捷写法(见 extmod/modsocket.c):

  • sock.setblocking(True)等价于sock.settimeout(None)
  • sock.setblocking(False)等价于sock.settimeout(0)

跨移植版建议:若目标移植版不支持settimeout,更通用可移植的替代方案是使用select.poll,它可以同时等待多个对象(不止套接字,还包括支持轮询的通用 stream 对象):

import select # 不推荐(依赖 settimeout): # s.settimeout(1.0) # s.read(10) # 可能超时 # 推荐: poller = select.poll() poller.register(s, select.POLLIN) res = poller.poll(1000) # 单位:毫秒 if not res: # s 仍无数据可读,即视为超时 pass

与 CPython 的差异:CPython 超时时抛出socket.timeoutOSError子类);MicroPython 直接抛OSError。因此用except OSError:捕获异常,代码可在两端同时工作。

文件类接口方法

  • makefile(mode='rb', buffering=0, /):返回与套接字关联的文件对象,仅支持二进制模式('rb''wb''rwb');CPython 的encodingerrorsnewline参数不支持。两个关键差异:MicroPython 不支持缓冲流,buffering值被忽略视为 0(无缓冲);关闭makefile()返回的文件对象会同时关闭原套接字。实现上该方法直接返回套接字自身(见 extmod/modsocket.c)。
  • read([size]):读取至多size字节并返回 bytes 对象。不指定size时读到 EOF(套接字关闭前不会返回);遵循"无短读"策略,尽力读满请求长度,非阻塞套接字可能返回较少数据。
  • readinto(buf[, nbytes]):读入buf,最多len(buf)字节(指定nbytes则至多该值),同样遵循"无短读";返回读入字节数。
  • readline():读取一行(以换行符结尾),返回该行。底层由mp_stream_unbuffered_readline_obj实现。
  • write(buf):写入整个缓冲区("无短写"),非阻塞套接字可能写入部分并返回实际写入字节数。

这些方法让 socket 可以直接配合selectasyncio等基于 stream 协议的框架使用,是嵌入式开发中非常实用的能力。

异常约定:用 OSError 统一处理

MicroPython没有socket.error异常。CPython 曾提供现已废弃的socket.error,它是OSError的别名;在 MicroPython 中请直接使用OSError

综合来看,MicroPython 的套接字编程错误处理可以归结为一个统一原则:所有套接字错误(包括超时、DNS 解析失败、未连接时的操作)都是OSError,这与 CPython 中"捕获OSError也能兼容"的做法一致。仓库测试 tests/extmod/socket_tcp_basic.py 验证了在全新未连接套接字上调用recv(1)会抛出errno.ENOTCONN

实战综合示例:TCP 客户端

综合上文所有要点,一个完整、可移植的 TCP 客户端如下:

import socket def http_get(host, path="/"): # 1. 解析地址:始终使用 getaddrinfo,显式过滤出 STREAM 地址 addr = socket.getaddrinfo(host, 80, 0, socket.SOCK_STREAM)[0][-1] # 2. 创建并连接(type 自动选择 TCP 协议) s = socket.socket() s.connect(addr) # 3. 发送请求:write 保证无短写 s.write(b"GET %s HTTP/1.0\r\nHost: %s\r\n\r\n" % (path, host)) # 4. 读取响应:readline 按行读取 resp = b"" while True: line = s.readline() if not line: break resp += line s.close() return resp print(http_get("micropython.org", "/"))

如需超时保护且目标移植版不支持settimeout,可将第 2 步之后替换为select.poll轮询方案(见上文"超时与阻塞模式"小节)。

结语

MicroPython 的socket模块以 BSD 套接字接口为蓝本,针对资源受限环境做了务实取舍:地址解析统一收敛到getaddrinfo(),套接字直接具备 stream 文件接口,错误处理统一为OSError。本文覆盖了地址格式、模块函数、常量、类方法与底层实现;更深入的内容可继续阅读:

  • 官方文档原文:docs/library/socket.rst
  • 通用实现源码:extmod/modsocket.c
  • Unix 移植版实现(含inet_pton/inet_ntop):ports/unix/modsocket.c
  • 网络相关测试:tests/net_inet/getaddrinfo.py、tests/extmod/socket_tcp_basic.py、tests/extmod/socket_udp_nonblock.py
  • 嵌入式
  • 语言运行时
  • 编程语言
  • 解释器
  • 编译器
  • 物联网
  • 系统编程

【免费下载链接】micropython

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

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询