6

In Python, I would like to use socket.connect() on a socket that I have set to non-blocking. When I try to do this, the method always throws a BlockingIOError. When I ignore the error (as below) the program executes as expected. When I set the socket to non-blocking after it is connected, there are no errors. When I use select.select() to ensure the socket is readable or writable, I still get the error.

testserver.py

import socket
import select

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

host = socket.gethostname()
port = 1234

sock.bind((host, port))
sock.listen(5)

while True:
    select.select([sock], [], [])
    con, addr = sock.accept()
    message = con.recv(1024).decode('UTF-8')
    print(message)

testclient.py

import socket
import select

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

host = socket.gethostname()
port = 1234

try:
    sock.connect((host, port))
except BlockingIOError as e:
    print("BlockingIOError")

msg = "--> From the client\n"

select.select([], [sock], [])
if sock.send(bytes(msg, 'UTF-8')) == len(msg):
    print("sent ", repr(msg), " successfully.")

sock.close()

terminal 1

$ python testserver.py
--> From the client

terminal 2

$ python testclient.py
BlockingIOError
sent  '--> From the client\n'  successfully.

This code works correctly except for the BlockingIOError on the first connect(). The documentation for the error reads like this: Raised when an operation would block on an object (e.g. socket) set for non-blocking operation.

How do I properly connect() with a socket set to non-blocking? Can I make connect() non-blocking? Or is it appropriate to just ignore the error?

MikeJava
  • 295
  • 1
  • 3
  • 12
  • This link might help you: http://stackoverflow.com/questions/1205863/how-can-i-get-non-blocking-socket-connects – akash12300 Mar 18 '16 at 20:19
  • @Akash1993 yeah, when I looked at that question previously I had a hard time relating it to my simple example. I'd like to avoid asyncio; it seems overkill. It does clarify that the error is thrown because connect blocks when it shouldn't (along with the blockingioerror documentation). The question is more 'is there a way to make connect() non-blocking'. – MikeJava Mar 18 '16 at 20:32

2 Answers2

4

When using socket.connect with a non-blocking socket, it is somewhat expected to get a BlockingIOError at first. See TCP Connect error 115 Operation in Progress What is the Cause? for an explanation of the cause. Basically, the socket isn't ready yet and raises BlockingIOError: [Errno 115] Operation now in progress, also known as EINPROGRESS.

The solution is to either catch and ignore the exception or to use socket.connect_ex instead of socket.connect because that method doesn't raise an exception. Especially note the last sentence from its description in the Python docs:

socket.connect_ex(address)

Like connect(address), but return an error indicator instead of raising an exception for errors returned by the C-level connect() call (other problems, such as “host not found,” can still raise exceptions). The error indicator is 0 if the operation succeeded, otherwise the value of the errno variable. This is useful to support, for example, asynchronous connects.

Source: https://docs.python.org/3/library/socket.html#socket.socket.connect_ex

If you want to keep using socket.connect, you can catch and ignore the responsible EINPROGRESS error:

>>> import socket
>>> 
>>> # bad
>>> s = socket.socket()
>>> s.setblocking(False)
>>> s.connect(("127.0.0.1", 8080))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
BlockingIOError: [Errno 115] Operation now in progress
>>> 
>>> # good
>>> s = socket.socket()
>>> s.setblocking(False)
>>> try:
...     s.connect(("127.0.0.1", 8080))
... except OSError as exc:
...     if exc.errno != 115:  # EINPROGRESS
...         raise
... 
>>> 
finefoot
  • 9,914
  • 7
  • 59
  • 102
2

The trick here is that when the select completes the first time, then you need to call sock.connect again. The socket is not connected until you have received a successful return status from connect.

Just add these two lines after the first call to select completes:

print("first select completed")
sock.connect((host, port))

EDIT:
Followup. I was wrong to have stated that an additional call to sock.connect is required. It is however a good way to discover whether the original non-blocking call to connect succeeded if you wish to handle the connection failure in its own code path.

The traditional way of achieving this in C code is explained here: Async connect and disconnect with epoll (Linux)

This involves calling getsockopt. You can do this in python too but the result you get back from sock.getsockopt is a bytes object. And if it represents a failure, you then need to convert it into an integer errno value and map that to a string (or exception or whatever you require to communicate the issue to the outside world). Calling sock.connect again maps the errno value to an appropriate exception already.

Solution 2: You can also simply defer calling sock.setblocking(0) until after the connect has completed.

Community
  • 1
  • 1
Gil Hamilton
  • 11,973
  • 28
  • 51
  • 1
    The code I posted works the way it's supposed to, and I've edited the question to make it more explicit about this. The problem is the BlockingIOError that it throws on that first connect, not on the success of the connection. If you examine the terminal outputs I posted, maybe it'll be more clear. – MikeJava Mar 19 '16 at 19:43
  • Solution 2 is absolutely correct! I actually mentioned it in my question; I'm wondering if there is a way to do this *after the socket is set to non-blocking. – MikeJava Mar 19 '16 at 20:01
  • I don't follow this. You seem to be asking "is there a way to get blocking behavior after I've asked for non-blocking behavior?" If you don't call `sock.setblocking(0)`, the default behavior is blocking. – Gil Hamilton Mar 20 '16 at 20:53
  • It's more "is there a way to get nonblocking behavior from connect()?" or "What is the appropriate way to handle blocking behavior after asking for nonblocking behavior?" Whichever is the correct question. It's becoming more clear that connect() can't be nonblocking. The correct answer should cover that. – MikeJava Mar 20 '16 at 23:57
  • Non-blocking in the context of `connect` means that you initiate the connection process but that you don't immediately wait for it to complete. Your original code does that. And it's what I discussed in considerable detail above. Your code actually does block in the `select` but it need not do so. (I assumed that was because this was sample code.) If you *also* don't want to block in `select`, you can provide the fourth `timeout` argument with a value of 0 which turns the `select` into a poll. – Gil Hamilton Mar 21 '16 at 10:48
  • But if `connect` is nonblocking in my code why am I getting the `BlockingIOError`? That's really what's throwing me off. And you're definitely right about select, I tried it. It worked without select, actually, which I thought was odd; that's another question though. – MikeJava Mar 21 '16 at 16:03