使用Python asyncio创建IPv6数据报端点时遇WinError 10022错误
问题:异步IPv6 UDP套接字发送报错[WinError 10022]
执行修改后的Python异步UDP客户端脚本时,出现如下错误:
Send: Hello World! Error received: [WinError 10022] An invalid argument was supplied Error received: [WinError 10022] An invalid argument was supplied Connection closed
脚本代码:
import asyncio import socket class EchoClientProtocol: def __init__(self, message, on_con_lost): self.message = message self.on_con_lost = on_con_lost self.transport = None def connection_made(self, transport): self.transport = transport print('Send:', self.message) self.transport.sendto(self.message.encode(), ("::1", 60000)) def datagram_received(self, data, addr): print("Received:", data.decode()) print("Close the socket") self.transport.close() def error_received(self, exc): print('Error received:', exc) def connection_lost(self, exc): print("Connection closed") self.on_con_lost.set_result(True) async def main(): loop = asyncio.get_running_loop() on_con_lost = loop.create_future() message = "Hello World!" sock = socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM) transport, protocol = await loop.create_datagram_endpoint( lambda: EchoClientProtocol(message, on_con_lost), sock = sock) try: await on_con_lost finally: transport.close() asyncio.run(main())
修改说明与环境
对官方示例做了两处修改:
- 预先创建IPv6套接字,通过
create_datagram_endpoint的sock参数传入(需设置特定套接字选项); - 在
transport.sendto方法中指定目标地址("::1", 60000)。
环境:Python 3.11.2 + Windows 10 21H2。IPv4版本可正常运行;非异步框架下的IPv6套接字sendto操作也能正常执行。
问题原因与修复方案
原因
Windows平台下,当预先创建的IPv6套接字未绑定任何地址/端口时,asyncio的UDP传输层调用sendto会触发[WinError 10022]参数错误。而IPv4套接字在未绑定状态下,Windows会自动隐式绑定到合适的地址,因此不会报错。
修复方案
方案一:预先绑定IPv6套接字
在创建套接字后,显式绑定到任意IPv6接口(::)和随机可用端口,这样asyncio的传输层就能正常处理sendto请求:
修改main函数中创建套接字的部分:
sock = socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM) # 新增:绑定到所有IPv6接口,端口自动分配 sock.bind(("::", 0))
方案二:通过remote_addr指定目标地址
直接在create_datagram_endpoint中传入remote_addr参数指定目标,无需在connection_made中调用sendto时传地址。这种方式下,asyncio会自动处理套接字的绑定逻辑:
修改后的main函数和connection_made方法:
# 修改后的main函数 async def main(): loop = asyncio.get_running_loop() on_con_lost = loop.create_future() message = "Hello World!" sock = socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM) transport, protocol = await loop.create_datagram_endpoint( lambda: EchoClientProtocol(message, on_con_lost), sock = sock, remote_addr=("::1", 60000)) # 新增remote_addr参数 try: await on_con_lost finally: transport.close() # 修改后的connection_made方法 def connection_made(self, transport): self.transport = transport print('Send:', self.message) # 无需再传地址,直接发送 self.transport.sendto(self.message.encode())
完整修复后的示例代码(方案一)
import asyncio import socket class EchoClientProtocol: def __init__(self, message, on_con_lost): self.message = message self.on_con_lost = on_con_lost self.transport = None def connection_made(self, transport): self.transport = transport print('Send:', self.message) self.transport.sendto(self.message.encode(), ("::1", 60000)) def datagram_received(self, data, addr): print("Received:", data.decode()) print("Close the socket") self.transport.close() def error_received(self, exc): print('Error received:', exc) def connection_lost(self, exc): print("Connection closed") self.on_con_lost.set_result(True) async def main(): loop = asyncio.get_running_loop() on_con_lost = loop.create_future() message = "Hello World!" sock = socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM) # 显式绑定IPv6套接字 sock.bind(("::", 0)) transport, protocol = await loop.create_datagram_endpoint( lambda: EchoClientProtocol(message, on_con_lost), sock = sock) try: await on_con_lost finally: transport.close() asyncio.run(main())
内容的提问来源于stack exchange,提问作者user18342360
相关产品推荐
相关产品推荐

