понедельник, 12 мая 2014 г.

Non-blocking mode and Timeouts Sockets errors in python

 By default a socket is configured so that sending or receiving data blocks, stopping program execution until the socket is ready (setblocking(1)). Calls to send() wait for buffer space to be available for the outgoing data, and calls to recv() wait for the other program to send data that can be read. This form of I/O operation is easy to understand, but can lead to inefficient operation and even deadlocks, if both programs end up waiting for the other to send or receive data.

There are a few ways to work around this situation. One is to use a separate thread for communicating with each socket. This can introduce other complexities, though, with communication between the threads.

Another option is to change the socket to not block at all, and return immediately if it is not ready to handle the operation. Use the setblocking() method to change the blocking flag for a socket. The default value is 1, which means to block. Passing a value of 0 turns off blocking. If the socket is has blocking turned off and it is not ready for the operation, then socket.error is raised.

A compromise solution is to set a timeout value for socket operations. Use settimeout() to change the timeout of a socket to a floating point value representing the number of seconds to block before deciding the socket is not ready for the operation. When the timeout expires, a timeout exception is raised.

In the case of a non blocking socket that has no data available, recv will throw the socket.error exception and the value of the exception will have the errno of either EAGAIN or EWOULDBLOCK.


import sys
import socket
import fcntl, os
import errno
from time import sleep

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
fcntl.fcntl(s, fcntl.F_SETFL, os.O_NONBLOCK)

while True:
    try:
        msg = s.recv(4096)
    except socket.error, e:
        err = e.args[0]
        if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
            sleep(1)
            print 'No data available'
            continue
        else:
            # a "real" error occurred
            print e
            sys.exit(1)
    else:
        # got a message, do something :)


The situation is a little different in the case where you've enabled non-blocking behavior via a time out with s.settimeout(n)


import sys
import socket
from time import sleep

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
s.settimeout(2)

while True:
    try:
        msg = s.recv(4096)
    except socket.timeout, e:
        err = e.args[0]
        # this next if/else is a bit redundant, but illustrates how the
        # timeout exception is setup
        if err == 'timed out':
            sleep(1)
            print 'recv timed out, retry later'
            continue
        else:
            print e
            sys.exit(1)
    except socket.error, e:
        # Something else happened, handle error, exit, etc.
        print e
        sys.exit(1)
    else:
        if len(msg) == 0:
            print 'orderly shutdown on server end'
            sys.exit(0)
        else:
            # got a message do something :) 
But remember The socket.makefile() docs say, "the socket must be in blocking mode.
sock.settimeout(None)
Another method:

import select

mysocket.setblocking(0)

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

Комментариев нет:

Отправить комментарий