如何使用非阻塞套接字连接()?

Mik*_*ava 6 python sockets networking nonblocking

在Python中,我想socket.connect()在设置为非阻塞的套接字上使用。当我尝试这样做时,该方法总是抛出一个BlockingIOError. 当我忽略错误(如下所示)时,程序将按预期执行。当我在连接后将套接字设置为非阻塞时,没有错误。当我使用 select.select() 确保套接字可读或可写时,我仍然收到错误。

测试服务器.py

import socket
import select

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(0)

host = socket.gethostname()
port = 1234

sock.bind((host, port))
sock.listen(5)

while True:
    select.select([sock], [], [])
    con, addr = sock.accept()
    message = con.recv(1024).decode('UTF-8')
    print(message)
Run Code Online (Sandbox Code Playgroud)

测试客户端.py

import socket
import select

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(0)

host = socket.gethostname()
port = 1234

try:
    sock.connect((host, port))
except BlockingIOError as e:
    print("BlockingIOError")

msg = "--> From the client\n"

select.select([], [sock], [])
if sock.send(bytes(msg, 'UTF-8')) == len(msg):
    print("sent ", repr(msg), " successfully.")

sock.close()
Run Code Online (Sandbox Code Playgroud)

1号航站楼

$ python testserver.py
--> From the client
Run Code Online (Sandbox Code Playgroud)

2号航站楼

$ python testclient.py
BlockingIOError
sent  '--> From the client\n'  successfully.
Run Code Online (Sandbox Code Playgroud)

除了第一个 connect() 上的 BlockingIOError 之外,此代码可以正常工作。该错误的文档如下所示:Raised when an operation would block on an object (e.g. socket) set for non-blocking operation.

如何正确使用 connect() 将套接字设置为非阻塞?我可以使 connect() 非阻塞吗?或者忽略该错误是否合适?

fin*_*oot 5

socket.connect与非阻塞套接字一起使用时,BlockingIOError首先预计会得到一个。请参阅TCP 连接错误 115 操作正在进行 原因是什么?原因的解释。基本上,套接字尚未准备好并引发BlockingIOError: [Errno 115] Operation now in progress,也称为EINPROGRESS

\n

解决方案是捕获并忽略异常,或者使用socket.connect_ex代替,socket.connect因为该方法不会引发异常。特别注意 Python 文档中描述的最后一句话:

\n
\n

socket.connect_ex(address)

\n

与 类似connect(address),但返回一个错误指示符,而不是针对 C 级 connect() 调用返回的错误引发异常(其他问题,例如 \xe2\x80\x9chost 未找到,\xe2\x80\x9d 仍然会引发异常) 。如果操作成功,则错误指示符为 0,否则为 errno 变量的值。例如,这对于支持异步连接很有用。

\n
\n

来源:https ://docs.python.org/3/library/socket.html#socket.socket.connect_ex

\n

如果你想继续使用socket.connect,你可以捕获并忽略负责的EINPROGRESS错误:

\n
>>> import socket\n>>> \n>>> # bad\n>>> s = socket.socket()\n>>> s.setblocking(False)\n>>> s.connect(("127.0.0.1", 8080))\nTraceback (most recent call last):\n  File "<stdin>", line 1, in <module>\nBlockingIOError: [Errno 115] Operation now in progress\n>>> \n>>> # good\n>>> s = socket.socket()\n>>> s.setblocking(False)\n>>> try:\n...     s.connect(("127.0.0.1", 8080))\n... except OSError as exc:\n...     if exc.errno != 115:  # EINPROGRESS\n...         raise\n... \n>>> \n
Run Code Online (Sandbox Code Playgroud)\n


Gil*_*ton 2

这里的技巧是,当第一次选择完成时,需要sock.connect再次调用。在您收到来自 的成功返回状态之前,套接字不会连接connect

只需在第一次调用完成添加这两行即可select

print("first select completed")
sock.connect((host, port))
Run Code Online (Sandbox Code Playgroud)

编辑:
后续。我错误地指出需要额外致电sock.connectconnect然而,如果您希望在其自己的代码路径中处理连接失败,那么这是发现原始非阻塞调用是否成功的好方法。

这里解释了用 C 代码实现此目的的传统方法:Async connect and disconnect with epoll (Linux)

这涉及到调用getsockopt。你也可以在 python 中执行此操作,但返回的结果sock.getsockopt是一个bytes对象。如果它代表失败,则需要将其转换为整数值errno并将其映射到字符串(或异常或将问题传达给外界所需的任何内容)。再次调用已将值sock.connect映射errno到适当的异常。

解决方案 2:您也可以简单地将调用推迟sock.setblocking(0)到连接完成之后。