Does socket become unusable after connect() fails?

2020-02-13 10:35发布

问题:

In Chapter 4, paragraph 4.3 of Steven's "The Socket: Networking API, Third Edition", the author states the following

"If connect fails, the socket is no longer usable and must be closed. 
 We cannot call connect again on the socket."

Does anyone know the reason behind the above statement?

In my own experiments, i wrote a simple tcp client, that would run on host A and a simple tcp server, that would run on host B. The tcp client would attempt to connect to the tcp server on Host B forever.

So, i started the server on host B. Pulled the network wire from the host. Then i started the client on host A. After about 9 unsuccessful connect attempts on the same socket, i simply plugged the network wire back into the server host. The client connected successfully and happily sends messages at 80K/sec.

In yet another experiment, i pulled the wire from the server host, after a initial successful connect and few million messages exchanges after. Then, after few minutes, i connected the wire and message flow resumed on the same socket.

回答1:

POSIX 2001 says in an informative section:

If connect() fails, the state of the socket is unspecified. Conforming applications should close the file descriptor and create a new socket before attempting to reconnect.

So the passage you quote is congruent with this specification. The fact it works on your machine does not mean your program is portable.



回答2:

This may be rooted in the manpage of connect, where it says:

Generally, connection-based protocol sockets may successfully connect() only once; connectionless protocol sockets may use connect() multiple times to change their association.

That would imply that you cannot just re-connect a connection-based (read, TCP) socket. However, I cannot see it saying anything about a failing connect() implying we cannot recycle the FD.

Resuming connections if the connection got interrupted is a feature of TCP, which will generally try to do that.



回答3:

Are you sure you're using the same socket, rather than a new socket that you try to connect to the same address as before?

Even if this is what you're doing, the fact that the particular OS you're experimenting on allows reuse of a socket that has failed to connect doesn't mean that all other OSes that implement the socket API (or earlier/later versions of the same OS) will be similarly lenient, so you risk producing subtly non-portable code.

When you do things that the API contract does not promise you will work, there is in general no knowing what will happen. One possible reaction is that it will appear to work -- right until the moment a paying customer tries to run your code on his machine.

Is that risk really worth the cost of a close(socket); socket=socket(...); in a fairly rare error situation?



回答4:

To answer your specific question...

Quite simply, there are a great number of TCP implementations out there. While some may support doing another connect() call after one fails, others will have state information that would make doing such unreliable.

To be safe, there would have to be some sort of reset() operation that would return the socket to a pristine state. Since this was not included in the original (or any succeeding) TCP implementation, the only remaining option is to close and re-open.

The POSIX standard (and your book, which probably uses the POSIX standard as a reference) thus tell you to do exactly that in order to be be able to work with all TCP/IP supporting operating systems. To have done otherwise would have invalidated some number of existing implementations.

In addition, new implementations are free to simplify their implementation by not having to worry about initiating a new connection after a failed attempt; this results in less code and less chance of bugs creeping in.



回答5:

The socket does become unusable in Python 2 on macOS Sierra

$ python
>>> import socket
>>> s = socket.socket()
>>> s.connect(('127.0.0.1', 8888))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 228, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 61] Connection refused

>>> s.connect(('127.0.0.1', 8888))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 228, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 22] Invalid argument
>>> s.close()
>>> s.connect(('127.0.0.1', 8888))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 228, in meth
    return getattr(self._sock,name)(*args)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 174, in _dummy
    raise error(EBADF, 'Bad file descriptor')
socket.error: [Errno 9] Bad file descriptor

The situation can arise if you are e.g. waiting for a server to come up by connecting to it in a loop. In that case, on macOS you cannot reuse single socket to do it.