Python中使用MSG_DONTWAIT的socket.recv函数

3
我几乎总是以阻塞模式在套接字上接收数据,这很好用。但有时我不想等待——如果套接字上有数据,我现在就要获取它,否则稍后再试。
我认为可以使用socket.recv()的flags参数来实现此操作,但似乎不起作用。我可以使用socket.setblocking()socket.settimeout()函数来实现所需的效果,但这看起来很麻烦。
Python socket文档中得知,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以获取非阻塞操作。 可能这不正确-我也可以理解为它总是返回错误,因为该调用原则上将是阻塞的。如果是这样,那么这一切都不相关。
一些示例代码:
  • 一个简单的阻塞调用。如预期的那样,大约需要0.5秒。
  • MSG_DONTWAIT:我希望是非阻塞调用,非常快地返回。实际上,这也需要约0.5秒。
  • 通过重新配置端口进行非阻塞。实际上,这只需约50微秒,因此其阻塞方式与前两个调用不同。
  • 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()的阻塞和非阻塞调用?
    1个回答

    2

    关于“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),那么在0.5s后我会收到一个socket.timeout异常:
    >>> 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(以及Socket Programming HOWTO中的“非阻塞套接字”部分和this)。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接