如何在Python的socket recv方法上设置超时时间?

175

我需要在Python的socket recv方法上设置超时。如何实现?


3
如果您选择使用超时,需要知道如何处理超时。这个SO问题讨论了当超时发生时的处理方式:https://dev59.com/NWQn5IYBdhLWcg3wj3vz - Trevor Boyd Smith
11个回答

153

通常的方法是使用select()函数等待数据可用或者超时。只有在数据确实可用时才调用recv()函数。为了保险起见,我们还将套接字设置为非阻塞模式,以确保recv()不会无限期地被阻塞。 select()函数也可以用于同时等待多个套接字。

import select

mysocket.setblocking(0)

ready = select.select([mysocket], [], [], timeout_in_seconds)
if ready[0]:
    data = mysocket.recv(4096)
如果您有很多打开的文件描述符,poll() 是比 select() 更有效的替代方法。
另一个选择是使用 socket.settimeout() 为套接字上的所有操作设置超时,但我看到您在另一个答案中明确拒绝了该解决方案。

22
使用 select 很好,但你所说的“你不能”这部分是误导性的,因为有 socket.settimeout() 方法。 - nosklo
9
在使用 select 时需要注意——如果你在 Windows 机器上运行,select 依赖于 WinSock 库,该库有一个习惯,即只要有一些数据到达,就会立即返回,但不一定是所有数据。因此,您需要加入一个循环来不断调用 select.select() 直到接收到所有数据。如何知道已经获取了所有数据(不幸的是)取决于您自己去找出来——这可能意味着查找终止符字符串、特定数量的字节,或者只是等待定义的超时时间。 - JDM
8
为什么需要设置套接字为非阻塞模式?我认为这对于 select 调用并没有影响(在此情况下它会阻塞直到可以读取描述符或超时),如果 select 得到满足,recv() 函数也不会阻塞。我使用 recvfrom() 进行了尝试,似乎在没有使用 setblocking(0) 的情况下也能正常工作。 - HankB
1
如果服务器的响应中没有正文,ready[0]是否只会为false? - matanster
1
自Python 3.4起,selectorsselect的高级替代品。 - Jarek Przygódzki
显示剩余2条评论

88

23
在我尝试时,它并没有超时接收(recv)操作。只有accept()操作会超时。 - Oren S
9
在我设置了socket.settimeout()之后,socket.recv()对我来说似乎完美地超时了,正如预期的那样。我是不是自己编造的?有其他人能够确认吗? - Aeonaut
3
我认为大部分情况下这个recv()会超时,但存在竞态条件。在socket.recv()中,Python(2.6)内部使用timeout调用select/poll,然后立即调用recv()。因此,如果您使用的是阻塞式套接字,在这两次调用之间,另一端崩溃了,就可能会无限期地挂起在recv()上。如果您使用非阻塞式套接字,则Python不会内部调用select.select,因此我认为Daniel Stutzbach的答案是正确的方式。 - emil.p.stanchev
4
我之前可能误解了select()返回的内容,请忽略我之前的评论。根据Beej's Guide,上述方法是在recv中实现超时的有效方式,可以参考此权威来源。 - emil.p.stanchev
3
我不确定为什么使用select的解决方案会更受欢迎,因为这个解决方案只有一行代码(更易于维护,在实现时出错的风险也较小),而且在底层也使用了select(实现与@DanielStuzbach答案相同)。 - Trevor Boyd Smith
连接的settimeout应该在服务器端调用。 - betontalpfa

58

如上所述,select.select()socket.settimeout()都可以使用。

请注意,您可能需要两次调用settimeout来满足您的需求,例如:

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("",0))
sock.listen(1)
# accept can throw socket.timeout
sock.settimeout(5.0)
conn, addr = sock.accept()

# recv can throw socket.timeout
conn.settimeout(5.0)
conn.recv(1024)

3
我认为他和我一样遇到了同样的问题,在尝试多种方法后,无论你如何调试这个函数,它都会卡住。我已经尝试了2或4个超时设置,但还是卡住了。settimeout也无法解决此问题。 - Cate Daniel
6
由于您多次调用 .settimeout() 方法,建议您首先调用 setdefaulttimeout() 方法。该方法可设置全局默认超时时间。 - mvarge
在调用.accept()/.recv()之前,我发现有必要调用.settimeout(),否则超时功能不总是起作用。 - Ben

18

您可以在接收响应之前设置超时时间,在接收到响应后将其设置为无:

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

sock.settimeout(5.0)
data = sock.recv(1024)
sock.settimeout(None)

12

如果您在实施服务器端,则寻找的超时时间是连接套接字的超时时间,而不是主要套接字的超时时间。换句话说,连接套接字对象有另一个超时时间,它是socket.accept()方法的输出。

sock.listen(1)
connection, client_address = sock.accept()
connection.settimeout(5)    # This is the one that affects recv() method.
connection.gettimeout()     # This should result 5
sock.gettimeout()           # This outputs None when not set previously, if I remember correctly.
如果你实现客户端,那会很简单。
sock.connect(server_address)
sock.settimeout(3)

7

我有点被顶部回答给搞糊涂了,所以我写了一个小的代码片段来帮助更好地理解。


选项#1-socket.settimeout()

如果sock.recv()等待的时间超过定义的超时时间,它将引发异常。

import socket

sock = socket.create_connection(('neverssl.com', 80))
timeout_seconds = 2
sock.settimeout(timeout_seconds)
sock.send(b'GET / HTTP/1.1\r\nHost: neverssl.com\r\n\r\n')
data = sock.recv(4096)
data = sock.recv(4096) # <- will raise a socket.timeout exception here

选项 #2 - select.select()

等待数据发送直到超时时间被触发。我对Daniel的回答进行了修改,使其会抛出异常。

import select
import socket

def recv_timeout(sock, bytes_to_read, timeout_seconds):
    sock.setblocking(0)
    ready = select.select([sock], [], [], timeout_seconds)
    if ready[0]:
        return sock.recv(bytes_to_read)

    raise socket.timeout()

sock = socket.create_connection(('neverssl.com', 80))
timeout_seconds = 2
sock.send(b'GET / HTTP/1.1\r\nHost: neverssl.com\r\n\r\n')
data = recv_timeout(sock, 4096, timeout_seconds)
data = recv_timeout(sock, 4096, timeout_seconds) # <- will raise a socket.timeout exception here

5

试试这个,它使用底层的 C 语言。

timeval = struct.pack('ll', 2, 100)
s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeval)

这很棒,因为它允许使用 SO_RCVTIMEOSO_SNDTIMEO 设置不同的发送和接收超时值。 - jtpereyda
为什么是 2100?超时值是哪个?使用什么单位表示? - Alfe
timeval = struct.pack('ll', sec, usec) s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeval) 其中usec = 10000表示10毫秒。 - Tavy
这在Windows上不起作用,因为参数是DWORD(64位)https://learn.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options - Cherona

5

您可以使用 socket.settimeout() 方法,该方法接受一个整数参数表示超时时间(秒)。例如,socket.settimeout(1) 将超时时间设置为 1 秒。


4
#! /usr/bin/python3.6

# -*- coding: utf-8 -*-
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.settimeout(5)
PORT = 10801

s.bind(('', PORT))
print('Listening for broadcast at ', s.getsockname())
BUFFER_SIZE = 4096
while True:
    try:
        data, address = s.recvfrom(BUFFER_SIZE)
    except socket.timeout:
        print("Didn't receive data! [Timeout 5s]")
        continue

3

如之前回复中提到的,您可以使用以下代码:.settimeout() 例如:

import socket

s = socket.socket()

s.settimeout(1) # Sets the socket to timeout after 1 second of no activity

host, port = "somehost", 4444
s.connect((host, port))

s.send("Hello World!\r\n")

try:
    rec = s.recv(100) # try to receive 100 bytes
except socket.timeout: # fail after 1 second of no activity
    print("Didn't receive data! [Timeout]")
finally:
    s.close()

我希望这可以帮助您!


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