首页 > 解决方案 > Python socket.recv 与 MSG_DONTWAIT

问题描述

我几乎总是在阻塞模式下接收套接字,这工作正常。偶尔我不想等待 - 如果套接字上有数据我现在就想要它,否则我稍后再试。

我以为我可以使用 flags 参数来做到这一点socket.recv(),但它似乎不起作用。我可以使用socket.setblocking()andsocket.settimeout()调用来达到我想要的效果,但这似乎很笨拙。

python 套接字文档中, flags 参数与 Unix recv具有相同的含义:

MSG_DONTWAIT (since Linux 2.2)
    Enables nonblocking operation; if the operation would block, the 
    call fails with the error EAGAIN or EWOULDBLOCK. This provides 
    similar behavior to setting the O_NONBLOCK flag (via the fcntl(2) 
    F_SETFL operation), but differs in that MSG_DONTWAIT is a per-call 
    option, whereas O_NONBLOCK is a setting on the open file description 
    (see open(2)), which will affect all threads in the calling process 
    and as well as other processes that hold file descriptors referring 
    to the same open file description.

我读到这意味着我可以通过socket.MSG_DONTWAIT仅对该调用进行非阻塞操作。可能这是不正确的 - 我也可以阅读它,因为它总是会返回错误,因为原则上调用会阻塞。在这种情况下,这一切都无关紧要。

一些示例代码:

import socket
import time

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.settimeout(0.5)

starttime = time.time()
try:
    m = sock.recv(100)
except socket.timeout as e:
    pass
endtime = time.time()
print(f'sock.recv(100) took {endtime-starttime}s')  # 0.5s

starttime = time.time()
try:
    m = sock.recv(100, socket.MSG_DONTWAIT)
except socket.timeout as e:
    pass
endtime = time.time()
print(f'sock.recv(100, socket.MSG_DONTWAIT) took {endtime-starttime}s')  # 0.5s 

starttime = time.time()
timeout = sock.gettimeout()
sock.setblocking(0)
try:
    m = sock.recv(100)
except BlockingIOError as e:
    pass
sock.settimeout(timeout)
endtime = time.time()
print(f'sock.recv(100) with non-blocking set took {endtime-starttime}s')  # 4.96e-5s

问题:

  1. 我对 MSG_DONTWAIT 的使用是错误的吗?它应该以我尝试使用它的方式工作吗?
  2. 有没有更好的方法来切换对 recv() 的阻塞和非阻塞调用

标签: python-3.xsockets

解决方案


关于“1.我对使用的错误MSG_DONTWAIT吗?它应该以我尝试使用它的方式工作吗?”:

不,您没有错,但是您的测试方式存在一个小问题。具体来说,您的
MSG_DONTWAIT测试是针对超时为0.5s. 这是因为您sock.settimeout(0.5)在第一次测试之前就有了(也许您忽略了这会影响您的第二次测试)。

如果我在您的测试中更新异常类型MSG_DONTWAIT(这是套接字阻塞的另一个迹象)并在“干净”会话中尝试,我会得到您期望得到的结果:

>>> import socket
>>> import time
>>> 
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
>>> starttime = time.time()
>>> try:
...     m = sock.recv(100, socket.MSG_DONTWAIT)
... except BlockingIOError as e:
...     pass
... 
>>> endtime = time.time()
>>> print(f'sock.recv(100, socket.MSG_DONTWAIT) took {endtime-starttime}s')  
sock.recv(100, socket.MSG_DONTWAIT) took 0.0007114410400390625s

如果我“忘记”排除sock.settimeout(0.5),我会在socket.timeout之后得到一个异常0.5s

>>> import socket
>>> import time
>>> 
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
>>> sock.settimeout(0.5)  # <= see this
>>> 
>>> starttime = time.time()
>>> try:
...     m = sock.recv(100, socket.MSG_DONTWAIT)
... except socket.timeout as e:
...     pass
... 
>>> endtime = time.time()
>>> 
>>> print(f'sock.recv(100, socket.MSG_DONTWAIT) took {endtime-starttime}s')
sock.recv(100, socket.MSG_DONTWAIT) took 0.501746416091919s

关于“2。是否有更好的方法来切换阻塞和非阻塞调用recv()”:根据您的应用程序的需要,您可能需要查看select (以及套接字编程中的“非阻塞套接字”部分HOWTO这个


推荐阅读