185

I need to set timeout on python's socket recv method. How to do it?

1
  • 3
    FYI if you do choose to use timeouts... you need to know how to handle the timeout. this SO question talks about handling when a timeout happens: stackoverflow.com/questions/16745409 Commented Nov 21, 2017 at 16:25

11 Answers 11

154

The typical approach is to use select() to wait until data is available or until the timeout occurs. Only call recv() when data is actually available. To be safe, we also set the socket to non-blocking mode to guarantee that recv() will never block indefinitely. select() can also be used to wait on more than one socket at a time.

import select

mysocket.setblocking(0)

ready = select.select([mysocket], [], [], timeout_in_seconds)
if ready[0]:
    data = mysocket.recv(4096)

If you have a lot of open file descriptors, poll() is a more efficient alternative to select().

Another option is to set a timeout for all operations on the socket using socket.settimeout(), but I see that you've explicitly rejected that solution in another answer.

Sign up to request clarification or add additional context in comments.

8 Comments

use select is good, but the part where you say "you can't" is misleading, since there is socket.settimeout().
One caution on using select -- if you're running on a Windows machine, select relies on the WinSock library, which has a habit of returning as soon as some data has arrived, but not necessarily all of it. So you need to incorporate a loop to keep calling select.select() until all the data is received. How you know you've gotten all the data is (unfortunately) up to you to figure out -- it may mean looking for a terminator string, a certain number of bytes, or just waiting for a defined timeout.
Why is it necessary to set the socket no non-blocking? I don't think that matters to the select call (and it blocks until a descriptor can be read or the timeout expires in this case) and the recv() won't block if the select is satisfied. I tried it using recvfrom() and it seems to work correctly without setblocking(0).
Would ready[0] only be false if there's no body in the server's response?
Since Python 3.4 selectors is a high-level alternative to select.
|
92

there's socket.settimeout()

6 Comments

It doesn't timeout the recv (at least when I tried it). Only the accept() is timed out.
The socket.recv() seems to time out for me just fine after setting socket.settimeout(), exactly as intended. Am I making this up? Can anyone else confirm this?
@Aeonaut I think that this times out recv() most of the time, but there is a race condition. In socket.recv() Python (2.6) calls select/poll internally with the timeout and then recv() is called right after. So if you use a blocking socket and between these 2 calls the other end point crashes, you can end up hanging indefinitely on the recv(). If you use non-blocking socket, python doesn't call select.select internally, so I think Daniel Stutzbach's answer is the correct way.
Actually, I probably misunderstood when select() returns, so please scratch the previous comment. Beej's Guide says the above is a valid way to implement a timeout on recv: beej.us/guide/bgnet/output/html/singlepage/… so I'll trust is an authorative source.
i'm not sure why the solution that uses select is preferred when this solution is a one liner (easier to maintain, less risk in implementing wrong) and uses select under the hood (implementation is the same as the @DanielStuzbach answer).
|
61

As mentioned both select.select() and socket.settimeout() will work.

Note you might need to call settimeout twice for your needs, e.g.

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)

4 Comments

I think he is getting in the same thing I am where no matter how you poke and prod this function it hangs. I've tried 2 or 4 timeouts now and it still hangs. settimeout hangs as well.
As you call .settimeout() more than once you could call the setdefaulttimeout() method in first place.
I found it necessary to call .settimeout(), right before .accept()/.recv() otherwise the time out doesn't always work.
If you wanted different timeouts for send/recv, and you're doing send/recv in simultaneously in parallel, this would cause race conditions.
18

You could set timeout before receiving the response and after having received the response set it back to None:

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

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

Comments

13

The timeout that you are looking for is the connection socket's timeout not the primary socket's, if you implement the server side. In other words, there is another timeout for the connection socket object, which is the output of socket.accept() method. Therefore:

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.

If you implement the client side, it would be simple.

sock.connect(server_address)
sock.settimeout(3)

Comments

7

Got a bit confused from the top answers so I've wrote a small gist with examples for better understanding.


Option #1 - socket.settimeout()

Will raise an exception in case the sock.recv() waits for more than the defined timeout.

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

Option #2 - select.select()

Waits until data is sent until the timeout is reached. I've tweaked Daniel's answer so it will raise an exception

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

Comments

6

try this it uses the underlying C.

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

4 Comments

This is great, as it allows one to set different values for send and recv timeout using SO_RCVTIMEO and SO_SNDTIMEO.
Why 2 and why 100? Which is the timeout value? In what unit?
timeval = struct.pack('ll', sec, usec) s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeval) usec = 10000 means 10 ms
This won't work on Windows, as the parameter is a DWORD (64-bit) learn.microsoft.com/en-us/windows/win32/winsock/…
5

You can use socket.settimeout() which accepts a integer argument representing number of seconds. For example, socket.settimeout(1) will set the timeout to 1 second

Comments

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

Comments

3

As mentioned in previous replies, you can use something like: .settimeout() For example:

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()

I hope this helps!!

Comments

0

Shout out to: https://boltons.readthedocs.io/en/latest/socketutils.html

It provides a buffered socket, this provides a lot of very useful functionality such as:

.recv_until()    #recv until occurrence of bytes
.recv_closed()   #recv until close
.peek()          #peek at buffer but don't pop values
.settimeout()    #configure timeout (including recv timeout)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.