I'm writing a simple TCP relay server, which is gonna be deployed both on a Windows and a Linux machine (same code base). Naturally there're gonna be two sockets to work with. I would like to know which exceptions exactly do get raised for the following cases:
recv()
returns when no data is available to read.sendall()
cannot complete (dispose of the whole to-send data)socket.EWOULDBLOCK
and socket.EAGAIN
) when expecting to return from a non-blocking sockets recv()
?errno
(.args[0]
) do I get when sendall()
fails?Here's my code so far:
try:
socket1.setblocking(False)
socket2.setblocking(False)
while True:
try:
sock1_data = socket1.recv(1024)
if sock1_data:
socket2.sendall(sock1_data)
except socket.error as e:
if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
raise e
try:
sock2_data = socket2.recv(1024)
if sock2_data:
socket1.sendall(sock2_data)
except socket.error as e:
if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
raise e
except:
pass
finally:
if socket2:
socket2.close()
if socket1:
socket1.close()
My main concern is:
What socket.error.errno
do I get when sendall()
fails?
Lest the socket.error.errno
I get from a failing sendall()
is EAGAIN
or EWOULDBLOCK
, in which case it'd be troubling!!
Also, do I have to check for both EAGAIN
and EWOULDBLOCK
when handling a non-blocking recv()
?
In CPython, socket.sendall
is implemented by sock_sendall in socketmodule.c, which calls sock_send_impl indirectly via sock_call_ex (as does socket.send
too). Ultimately, this results in calling the system call send(...)
, which will set errno accordingly. sock_call_ex
will handle some of the potential errors- it may automatically retry on EINTR
, and for EWOULDBLOCK
and EAGAIN
it will retry if there's a positive timeout.
Apart from that, however, socket.sendall
can by and large produce most of the same errors that send
can. There are a few, related to malformed arguments that should be impossible, assuming no bugs in python's socket module implementation, but the rest are valid, including EAGAIN
and EWOULDBLOCK
. You can consult the man pages for a list of errors that send
can produce, or see this online list.
As for socket.recv
, the implementation is at sock_recv_impl, which calls recv
directly, resulting in a similar situation. Again, see the man pages, or online.
In short, for recv()
, you indeed need to handle both EAGAIN
and EWOULDBLOCK
for no data available, unless you know your platform so you can determine which it will raise. For sendall()
, the errors you need to handle will depend on the errors you want to support / recover from. As an extreme example- you could get ENOMEM
if you ran out of memory as it tries to fulfill the send request, however that's an extremely unlikely scenario, and you would already have bigger problems at that point.
Luckily, in your case there's an easy solution, since you're really just concerned with distinguishing the expected errors on nonblocking recv from actual errors during either recv or send. Just move the socket.sendall
out of the try
body, using the else
construct:
while True:
try:
sock1_data = socket1.recv(1024)
except socket.error as e:
if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
raise e
else: # only runs if there was no exception
if sock1_data:
socket2.sendall(sock1_data)
try:
sock2_data = socket2.recv(1024)
except socket.error as e:
if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
raise e
else: # only runs if there was no exception
if sock2_data:
socket1.sendall(sock2_data)