十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MicroPython socket 模块实战指南:BSD 套接字接口的地址格式、API 解析与底层实现

MicroPython socket 模块实战指南:BSD 套接字接口的地址格式、API 解析与底层实现 嵌入式语言运行时编程语言解释器编译器物联网系统编程【免费下载链接】micropythonMicroPython - 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.csocket 类型注册了mp_stream_p_t协议包含read、write、ioctl三个回调并在方法表中把read/readinto/readline/write直接映射到通用流实现mp_stream_read_obj、mp_stream_readinto_obj、mp_stream_unbuffered_readline_obj、mp_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.8port是 1~65535 的整数。元组中的地址不接受域名必须先用socket.getaddrinfo()解析。IPv6 元组(ipv6_address, port, flowinfo, scopeid)其中ipv6_address是冒号分隔的十六进制字符串如2001:db8::1port为 1~65535 的整数flowinfo必须为 0scopeid是链路本地地址的接口作用域标识。同样不接受域名。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, af0, type0, proto0, flags0, /)将 host/port 参数翻译为一组 5 元组每个元组包含创建连接到该服务所需的所有参数。可选参数af、type、proto含义与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 类型会触发一个RuntimeWarningunsupported 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.1inet_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_INET、AF_INET6、SOCK_STREAM、SOCK_DGRAM、SOCK_RAW以及SOL_SOCKET、SO_REUSEADDR、SO_BROADCAST、SO_KEEPALIVE、SO_SNDTIMEO、SO_RCVTIMEOIPPROTO_*系列在通用实现中被注释掉仅在部分移植版中可见。这也解释了文档中清单取决于移植版的表述。socket 类构造函数socket(afAF_INET, typeSOCK_STREAM, protoIPPROTO_TCP, /)创建指定地址族、类型与协议号的套接字。proto多数情况下无需指定见上文常量说明由type自动选择协议# 创建 STREAM TCP 套接字 socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建 DGRAM UDP 套接字 socket.socket(socket.AF_INET, socket.SOCK_DGRAM)源码中的默认值是domainAF_INET, typeSOCK_STREAM, proto0套接字初始处于未绑定任何 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_PEEK与MSG_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(bhello, 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 / setblockingsettimeout(value)注意并非每个移植版都支持此方法。value为非负浮点数秒或None非零值后续套接字操作在超时时间内未完成即抛出OSError0切换到非阻塞模式None切换到阻塞模式。源码中的映射见 extmod/modsocket.cNone映射为内部-1阻塞数值以秒为单位乘以 1000 转为毫秒传给 NIC 层。测试 tests/extmod/socket_udp_nonblock.py 验证了settimeout(0)之后在无数据到达时调用recv(1)会抛出errno.EAGAIN。setblocking(flag)flag为假值时设为非阻塞否则设为阻塞。它是settimeout()的快捷写法见 extmod/modsocket.csock.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(moderb, buffering0, /)返回与套接字关联的文件对象仅支持二进制模式rb、wb、rwbCPython 的encoding、errors、newline参数不支持。两个关键差异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 可以直接配合select、asyncio等基于 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(bGET %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.cUnix 移植版实现含inet_pton/inet_ntopports/unix/modsocket.c网络相关测试tests/net_inet/getaddrinfo.py、tests/extmod/socket_tcp_basic.py、tests/extmod/socket_udp_nonblock.py赞分享嵌入式语言运行时编程语言解释器编译器物联网系统编程【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址https://gitcode.com/gh_mirrors/mi/micropython点击查看免费下载相关推荐CPython socket 模块全解析BSD 底层网络接口、地址族与 TCP/IP 实战CPython socket 模块全解析BSD 底层网络接口、地址族与 TCP/IP 实战 socket 是 CPython 标准库中最贴近操作系统内核的模块编程语言语言运行时解释器标准库WAMR Socket API 实战指南在 WebAssembly 中完整使用 Berkeley/POSIX 套接字接口WAMR Socket API 实战指南在 WebAssembly 中完整使用 Berkeley/POSIX 套接字接口 导读 本文基于 WAMRWebAs可观测性云原生Asterinas套接字网络套接字API的实现Asterinas套接字网络套接字API的实现 概述 Asterinas是一个用Rust编写的安全、快速、通用的操作系统内核提供Linux兼容的ABIAp操作系统内核驱动系统编程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表